Spring详解,代码事例,IOC,AOP,事务。整合MyBatis,JUnit

在这里插入图片描述

Spring核心

核心概念

  • 代码书写现状
    • 耦合度偏高
  • 解决方案
    • 使用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象
  • IOC ( Inversion of Control )控制反转
    • 对象的创建控制权由程序转移到外部,这种思想称为控制反转
    • 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
  • Spring技术对IoC思想进行了实现
    • Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的外部)
    • IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
  • DI ( Dependency Injection)依赖注入
    • 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入

IOC 入门

思路

  1. 管理什么? ( Service与Dao )
  2. 如何将被管理的对象告知IoC容器?(配置)
  3. 被管理的对象交给IoC容器,如何获取到Ioc容器?(接口)
  4. IOC容器得到后,如何从容器中获取bean ?(接口方法)
  5. 使用Spring导入哪些坐标?( pom.xml )
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("BookDaoImpl.......");
    }
}
import com.blm.dao.BookDao;
import com.blm.service.BookService;

public class BookServiceImpl implements BookService {
    BookDao bookDao = new BookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void save() {
        System.out.println("BookServiceImpl........");
        bookDao.save();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookService" class="com.blm.service.impl.BookServiceImpl"/>
</beans>
public class App {
    public static void main(String[] args) {
        // 获取IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取bean
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

DI 入门

  1. 基于IoC管理bean
  2. Service中使用new形式创建的Dao对象是否保留 ?(否)
  3. Service中需要的Dao对象如何进入到Service中 ?(提供方法)
  4. Service与Dao间的关系如何描述 ?(配置)
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("BookDaoImpl.......");
    }
}
public class BookServiceImpl implements BookService {
    // 删除业务层中使用new 的方式创建的对象
    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void save() {
        System.out.println("BookServiceImpl........");
        bookDao.save();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookDao" class="com.blm.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.blm.service.impl.BookServiceImpl">
        <!--配置service 和 dao 的关系-->
        <!--property:当前bean 的属性
            name:设置哪个一个bean的属性
            ref 参照哪个bean-->
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>
public class App {
    public static void main(String[] args) {
        // 获取IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取bean

        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

基础配置

Bean 别名

<bean id="bookService" name="service bookService2 bookEbi" class="com.blm.service.impl.BookServiceImpl">
       <property name="bookDao" ref="bookDao"/>
 </bean>

在这里插入图片描述

bean 的作用范围

在这里插入图片描述

为什么bean默认为单例?
  • 适合交给容器进行管理的bean
    • 表现层对象
    • 业务层对象
    • 数据层对象
    • 工具对象
  • 不适合交给容器进行管理的bean
    • 封装实体的域对象

bean 实例化过程

构造方法

spring调的是无参构造方法,public ,private都可以掉到(反射)

无参构造方法如果不存在,将抛出异常BeanCreationException

静态工厂

package com.blm.factory;

import com.blm.dao.OrderDao;
import com.blm.dao.impl.OrderDaoImpl;

public class OrderDaoFactory {
    public static OrderDao getOrderDao() {
        return new OrderDaoImpl();
    }
}
<bean id="orderDao" class="com.blm.factory.OrderDaoFactory" factory-method="getOrderDao"></bean>
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
        orderDao.test();
    }
}

实例工厂

public class OrderDaoFactory {
    public OrderDao getOrderDao() {
        return new OrderDaoImpl();
    }
}
<bean id="orderDaoFactory" class="com.blm.factory.OrderDaoFactory"/>
<bean id="orderDao" factory-method="getOrderDao" factory-bean="orderDaoFactory"></bean>

使用 FactoryBean 实例化

public class OrderDaoFactoryBean implements FactoryBean<OrderDao> {
    @Override
    public OrderDao getObject() throws Exception {
        return new OrderDaoImpl();
    }

    @Override
    public Class<?> getObjectType() {
        return OrderDao.class;
    }
    
    @Override
    public boolean isSingleton() {
        return true; // true: 单例,false: 非单例
    }
}
<bean id="orderDao" class="com.blm.factory.OrderDaoFactoryBean"/>
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
        orderDao.test();
    }
}

bean 生命周期

  • 生命周期:从创建到消亡的完整过程
  • bean生命周期:bean从创建到销毁的整体过程
  • bean生命周期控制:在bean创建后到销毁前做一些事情
public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("BookDaoImpl.......");
    }

    // 表示 bean 初始化对应的操作
    public void init() {
        System.out.println("init....");
    }

    // 表示 bean 销毁前对应的操作
    public void destory () {
        System.out.println("destory....");
    }
}
<bean id="bookDao" class="com.blm.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
public class App {
    public static void main(String[] args) {
         // 获取IOC容器
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ctx.registerShutdownHook(); // 设置  关闭虚拟机前,销毁容器 (可以在任何位置)

        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
        // ctx.close(); // 必须要关闭容器,才调用 销毁前对应的操作生命周期
    }
}

方法二:

public class BookDaoImpl implements BookDao, InitializingBean, DisposableBean {
    @Override
    public void save() {
        System.out.println("BookDaoImpl.......");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("BookDao destroy");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BookDao init");
    }
}
<bean id="bookDao" class="com.blm.dao.impl.BookDaoImpl"/>

依赖注入

思考: 依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?

  • 引用类型
  • 简单类型( 基本数据类型与string )

普通方法注入(setter注入)

引用类型 & 简单类型
public class BookServiceImpl implements BookService {
    // 删除业务层中使用new 的方式创建的对象
    private BookDao bookDao;
    private OrderDao orderDao;
    private String name;
    private int num;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public void save() {
        System.out.println("BookServiceImpl........" + name + "," + num);
        bookDao.save();
        orderDao.save();
    }
}
	<bean id="bookDao" class="com.blm.dao.impl.BookDaoImpl"/>
    <bean id="orderDao" class="com.blm.factory.OrderDaoFactoryBean"/>
    <bean id="bookService" name="service bookService2 bookEbi" class="com.blm.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/> <!-- 引用类型 -->
        <property name="orderDao" ref="orderDao"/>  <!-- 引用类型 -->
        <property name="name" value="hello"/>  <!-- 简单类型 -->
        <property name="num" value="100000000"/>  <!-- 简单类型 -->
    </bean>

构造器注入

引用类型 & 简单类型
public class BookServiceImpl implements BookService {
    // 删除业务层中使用new 的方式创建的对象
    private BookDao bookDao;
    private OrderDao orderDao;
    private String name;
    private int num;

    public BookServiceImpl(BookDao bookDao, OrderDao orderDao, String name, int num) {
        this.bookDao = bookDao;
        this.orderDao = orderDao;
        this.name = name;
        this.num = num;
    }

    @Override
    public void save() {
        System.out.println("BookServiceImpl........" + name + "," + num);
        bookDao.save();
        orderDao.save();
    }
}

	<bean id="bookDao" class="com.blm.dao.impl.BookDaoImpl"/>
    <bean id="orderDao" class="com.blm.factory.OrderDaoFactoryBean"/>
    <bean id="bookService" name="bookService" class="com.blm.service.impl.BookServiceImpl">
        <constructor-arg index="0" ref="bookDao"/>
        <constructor-arg index="1" ref="orderDao"/>
        <constructor-arg index="2" value="Hello"/>
        <constructor-arg index="3" value="99999999"/>
    </bean>

or

<bean id="bookService" name="bookService" class="com.blm.service.impl.BookServiceImpl">
<!--        <constructor-arg index="0" ref="bookDao"/>-->
<!--        <constructor-arg index="1" ref="orderDao"/>-->
<!--        <constructor-arg index="2" value="Hello"/>-->
<!--        <constructor-arg index="3" value="99999999"/>-->
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="orderDao" ref="orderDao"/>
        <constructor-arg name="name" value="Hello"/>
        <constructor-arg name="num" value="99999999"/>
    </bean>

依赖注入方式的选择

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致nu11对象出现
  2. 可选依赖使用setter注入进行,灵活性强
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入

依赖自动装配

public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    private OrderDao orderDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }

    @Override
    public void save() {
        System.out.println("BookServiceImpl........");
        bookDao.save();
        orderDao.save();
    }
}
<bean id="bookService" name="bookService" class="com.blm.service.impl.BookServiceImpl" autowire="byType"/>
<!--byType: 按类型装配-->
特征
  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  • 使用按类型装配时( byType ) 必须保障容器中相同类型的bean唯一,推荐使用
  • 使用按名称装配时 ( byName )必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
  • 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

集合注入

public class OrderDaoImpl implements OrderDao {
    private int[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String, String> map;
    private Properties properties;

    public void setArray(int[] array) {
        this.array = array;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setSet(Set<String> set) {
        this.set = set;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public void save() {
        System.out.println("OrderDaoImpl.........");
        System.out.println("array:" + array);
        System.out.println("list:" + list);
        System.out.println("set:" + set);
        System.out.println("map:" + map);
        System.out.println("properties:" + properties);
    }
}
	<bean id="orderDao" class="com.blm.dao.impl.OrderDaoImpl">
        <property name="array">
            <array>
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="country" value="china"/>
                <entry key="province" value="yunnan"/>
                <entry key="city" value="dali"/>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="country">china</prop>
                <prop key="province">yunnan</prop>
                <prop key="city">dali</prop>
            </props>
        </property>
    </bean>

数据源对象管理

<!-- 管理DruidDataSource -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/spring_db"/>
    <property name="username" value="root"/>
    <property name="password" value="blm123"/>
</bean>
import com.blm.dao.impl.OrderDaoImpl;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;

public class App2 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = (DataSource)ctx.getBean("dataSource");
        System.out.println(dataSource);
    }
}

加载 properties 文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!-- 1. 开启 context 命名空间 -->
    <!--
		xmlns:context="http://www.springframework.org/schema/context"
		http://www.springframework.org/schema/context
		 http://www.springframework.org/schema/context/spring-context.xsd
	-->
    <!-- 2. 使用 context 加载 properties 文件 -->
    <!-- system-properties-mode="NEVER" 不加载系统属性 -->
    <context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
    <!-- 3. ${} 读取properties 文件中的属性 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

jdbc.properties 文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=blm123

多个 properties 文件加载

  1. 不加载系统属性
<context: property-placeholder location="jdbc. properties" system-properties-mode="NEVER"/>
  1. 加载所有properties文件
<context:property-placeholder location="jdbc.properties,msg.properties" />
  1. 加载所有properties文件
<context:property-placeholder location="*.properties" />
  1. 加载properties文件标准格式
<context:property-placeholder location="classpath:*.properties" />
  1. 从类路径或jar包中搜索并加载properties文件
<context:property-placeholder location="classpath*:*.properties" />

容器

加载

// 1. 从类路径下加载容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2. 从文件系统下加载容器
ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\\java\\20230515_ss\\ddup\\spring\\src\\main\\resources\\applicationContext.xml");
OrderDaoImpl orderDaoImpl = (OrderDaoImpl)ctx.getBean("orderDao");
OrderDaoImpl orderDaoImpl = ctx.getBean("orderDao", OrderDaoImpl.class);
OrderDaoImpl orderDaoImpl = ctx.getBean(OrderDaoImpl.class); // 按类型获取

创建容器

// 方式一︰类路径加载配置文件
ApplicationContext ctx1 = new classPathXmlApplicationContext( "applicationContext.xml" );
// 方式二︰文件路径加载配置文件
ApplicationContext ctx2 = new FileSystemXmIApplicationContext("D:\\applicationContext.xml" );
// 加载多个配置文件
ApplicationContext ctx3 = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xm1");

bean 相关

在这里插入图片描述

注解开发定义 bean

@Component // 省略名称,按类型获取并
public class Com implements BookDao {}

@Component("com")
public class Com implements BookDao {
    @Override
    public void save() {
        System.out.println("com-----------------");
    }
}

// 和 @Component 功能样
@Service // 业务层
@Repository // 数据程
@Controller // 表现层
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.blm"/>

</beans>

纯注解开发

  • Spring3.0开启了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道
  • Java类代替Spring核心配置文件,

@Configuration注解用于设定当前类为配置类121212

  • @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
@Component
@Scope("singleton")
// @Scope("prototype") // 非单例
public class Com implements BookDao {
    @Override
    public void save() {
        System.out.println("com-----------------");
    }
}
@ComponentScan({com.itheima.service" , " com.itheima.dao"})
package com.blm.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.blm")
public class SpringConfig {
}
public class App3 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        Com com = (Com)ctx.getBean("com");
        com.save();
    }
}

bean作用范围 和 生命周期

package com.blm.dao.impl;

import com.blm.dao.BookDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;


@Repository
@Scope("singleton")
// @Scope("prototype") // 非单例
public class Com implements BookDao {
    @Override
    public void save() {
        System.out.println("com-----------------");
    }
    @PostConstruct // 构造方法后
    public void init () {
        System.out.println("init.....");
    }

    @PreDestroy // 销毁前
    public void destroy () {
        System.out.println("destroy.....");
    }
}
public class App3 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        Com com = ctx.getBean(Com.class);
        com.save();
        ctx.close();
    }
}

自动装配

按类型装配

package com.blm.dao.impl;

import com.blm.dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("UserDaoImpl..........");
    }
}
package com.blm.service.impl;

import com.blm.dao.UserDao;
import com.blm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;
    @Override
    public void save() {
        System.out.println("UserServiceImpl....");
        userDao.save();
    }
}
public class App3 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserServiceImpl userService = ctx.getBean(UserServiceImpl.class);
        userService.save();
    }
}

按名字装配

userDao

有多个相同类型时,需要 按名字装配

@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("UserDaoImpl..........");
    }
}
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;

    @Override
    public void save() {
        System.out.println("UserServiceImpl....");
        userDao.save();
    }
}
  • 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
  • 注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法

简单类型装配

@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Value("Hello")
    private String name;
}

加载 properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=blm123
@Configuration
@ComponentScan("com.blm")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig { }
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Value("${jdbc.username}")
    private String name;
}

第三方 bean 管理

package com.blm.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;

public class JdbcConfig {
    // 1. 定义一个方法获得要管理的对象
    // 2. @Bean, 表示当前方法的返回值是一个bean
    @Bean
    public DataSource dataSource () {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://127.0.0.1:3306/spring_db");
        ds.setUsername("root");
        ds.setPassword("123456");
        return ds;
    }
}
@Configuration
@Import(JdbcConfig.class)
public class SpringConfig { }
public class App3 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        DataSource dataSource = ctx.getBean(DataSource.class);
    }
}

第三方 Bean 注入资源

public class JdbcConfig {
    @Value("com.mysql.jdbc.Driver")
    private String className;
    @Value("jdbc:mysql://127.0.0.1:3306/spring_db")
    private String username;
    @Value("root")
    private String url;
    @Value("123456")
    private String password;
    @Bean
    public DataSource dataSource (UserDao userDao) { //
        System.out.println(">>userDao<<" + userDao);
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(className);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

XML 配置对比注解配置

在这里插入图片描述

Spring 整合 MyBatis

导包

		<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>

com.blm.config

package com.blm.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ComponentScan("com.blm")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}
package com.blm.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}
package com.blm.config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.blm.domain"); // 类型别名
        ssfb.setDataSource(dataSource);
        return ssfb;
    }
    //定义bean,返回MapperScannerConfigurer对象
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.blm.dao"); // Mapper 映射
        return msc;
    }
}

com.blm.domain

package com.blm.domain;

import java.io.Serializable;

public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private String gender;
    private String addr;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", gender='" + gender + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }
}

com.blm.dao

package com.blm.dao;

import com.blm.domain.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserDao {
    @Select("select * from tb_user where id = #{id}")
    User getUserById(Integer id);

    @Select("select * from tb_user")
    List<User> getUserAll();
}

com.blm.service

package com.blm.service;

import com.blm.domain.User;

import java.util.List;

public interface UserService {
    User getUserById(Integer id);

    List<User> getUserAll();
}
package com.blm.service.impl;

import com.blm.dao.UserDao;
import com.blm.domain.User;
import com.blm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public User getUserById(Integer id) {
        return userDao.getUserById(id);
    }

    @Override
    public List<User> getUserAll() {
        return userDao.getUserAll();
    }
}

app

package com.blm;

import com.blm.config.SpringConfig;
import com.blm.domain.User;
import com.blm.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.List;

public class app {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annt = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = annt.getBean(UserService.class);
        User user = userService.getUserById(1);
        System.out.println(user);
        List<User> users = userService.getUserAll();
        for (User u : users) {
            System.out.println(u);
        }
    }
}

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/db
jdbc.username=root
jdbc.password=123456

Spring 整合 JUnit

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class) // Spring 配置类
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testGetUserById() {
        System.out.println(userService.getUserById(2));
    }
}

AOP

简介

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
    • OOP(Object Oriented Programming)面向对象编程
    • 作用:在不惊动原始设计的基础上为其进行功能增强
    • Spring理念:无入侵式 / 无侵入式

在这里插入图片描述

  • 连接点(JoinPoint ):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    • 在SpringAOP中,理解为方法的执行
  • 切入点 (Pointcut ):匹配连接点的式子
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:com.blm.dao包下的BookDao接口中的无形参无返回值的save方法
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
  • 通知 (Advice):在切入点处执行的操作,也就是共性功能
    • 在springAOP中,功能最终以方法的形式呈现
  • 通知类: 定义通知的类
  • 切面( Aspect):描述通知与切入点的对应关系

入门

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
package com.blm.config;
import org.springframework.context.annotation.*;

@Configuration
@ComponentScan("com.blm")
@EnableAspectJAutoProxy  // 声明注解开发 AOP
public class SpringConfig {
}
package com.blm.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component // Spring Bean
@Aspect // AOP 注解
public class MyAdvice {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.blm.dao.UserDao.getUserById(..)))")
    private void pt() {}
	// 通知
    @Before("pt()")
    public void method () {
        System.out.println("time" + System.currentTimeMillis());
        System.out.println("time + System.currentTimeMillis()");
    }
}

AOP 工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点3.
    • 匹配失败,创建对象
    • 匹配成功,创建原始对象( 目标对象)的代理对象
  4. 获取bean执行方法
    • 获取bean,调用方法并执行,完成操作
    • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
    • 目标对象(Target): 原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
    • 代理(Proxy ):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

AOP 切入点表示式

  • 切入点:要进行增强的方法切入点

  • 表达式:要进行增强的方法的描述方式

  • 描述方式一: 执行com.blm.dao包下的BookDao接口中的无参数update方法
    execution(void com.blm.dao.BookDao.update()

  • 描述方式二: 执行com.itheima.dao.impl包下的BookDaoImpl类中的无参数update方法
    execution(void com.itheima.dao.impl.BookDaoImpl.update())

  • 切入点表达式标准格式:动作关键字( 访问修饰符 返回值包名类/接名方法名(参数异常名)

    @execution (public User com.itheima.service.UserService.findById (int))
    
    • 动作关键字: 描述切入点的行为动作,例如execution表示执行到指定切入点

    • 访问修饰符 : public,private等,可以省略

    • 返回值

    • 包名

    • 类/接口名

    • 方法名

    • 参数

    • 异常名: 方法定义中抛出指定异常,可以省略

    • 可以使用通配符描述切入点,快速描述

      • *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

        execution (public * com.itheima.*.UserService.find* (*) )
        

        匹配comitheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

      • …:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

        execution (public User com..UserService.findById (..) )
        
      • 匹配com包下的任意包中的UserService类或接口中所有名称为findByld的方法+:专用于匹配子类类型

        execution(**..*Service+.*(..))
        

书写技巧

  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点通常描述接口,而不描述实现类
  • 访问控制修饰符针对接口开发均采用public描述( 可省略访问控制修饰符描述
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用通配快速描述
  • 包名书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的采用匹配,例如UserService书写成Service,绑定业务层接口名
  • 方法名书写以动词进行精准匹配,名词采用*匹配,例如getByld书写成getBy,selectAll书写成selectAll
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常不使用异常作为匹配规则

AOP通知类型

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
  • AOP通知共分为5种类型
    • 前置通知
    • 后置通知
    • 环绕通知(重点)
    • 返回后通知(了解)
    • 抛出异常后通知(了解 )
package com.blm.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect // AOP 注解
public class MyAdvice {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.blm.dao.UserDao.getUserById(..)))")
    private void pt() {}

    @Before("pt()")
    public void  before() {
        System.out.println("前置通知");
    }

    @After("pt()")
    public void  after() {
        System.out.println("后置通知");
    }

    @Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知-前置");
        // 表示对原始方法的调用
        pjp.proceed();
        System.out.println("环绕通知-后置");
    }

    @AfterReturning("pt()")
    public void afterReturning() {
        System.out.println("返回后通知");
    }

    @AfterThrowing("pt()")
    public void afterThrowing() {
        System.out.println("抛出异常后通知");
    }
}

环绕通知,可以对返回结果做一些处理

@Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知发-前置");
        // 表示对原始方法的调用
        Object ret = pjp.proceed();
        System.out.println("环绕通知-后置" + ret);
        return ret;
    }

@Around 注意事项

  1. 环绕通知必须依赖形参ProceedingloinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
  2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
  3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
  4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
  5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象

案例(业务接口10000次接口执行时间)

package com.blm.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect // AOP 注解
public class ProjectAdvice {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.blm.service.*Service.*(..)))")
    private void servicePt() {}

    @Around("servicePt()")
    public Object runSpeed(ProceedingJoinPoint pjp) throws Throwable {
        long starTime = System.currentTimeMillis();
        Object ret = null;
        for (int i = 0; i < 10000; i ++) {
            ret = pjp.proceed();
        }
        long endTime = System.currentTimeMillis();
        System.out.println(pjp.getSignature() + "万次执行时间" + (endTime - starTime) + "ms");
        return ret;
    }
}

AOP 通知获取数据

  • 获取切入点方法的参数
    • JoinPoint :适用于前置、后置、返回后、抛出异常后通知
    • proceedJointPoint :适用于环绕通知
  • 获取切入点方法返回值
    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息
    • 抛出异常后通知
    • 环绕通知
package com.blm.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect // AOP 注解
public class MyAdvice {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.blm.dao.UserDao.getUserById(..)))")
    private void pt() {}

//    @Before("pt()")
    public void  before(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(args);
        System.out.println("前置通知");
    }

//    @After("pt()")
    public void after(ProceedingJoinPoint pjp) {

        System.out.println("后置通知");
    }

//    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        args[0] = 4; // 修改参数
        System.out.println(args);
        Object obj = pjp.proceed(args); // 调用原始方法
        System.out.println("环绕通知-后置");
        return obj;
    }

//    @AfterReturning(value = "pt()", returning = "ret")
    public void afterReturning(Object ret) {
        System.out.println(ret); // 返回值
        System.out.println("返回后通知");
    }

    @AfterThrowing(value = "pt()", throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("抛出异常后通知" + t); // Throwable 异常对象
    }
}

AOP总结

  • 切入点表达式标准格式:动作关键字( 访问修饰符 返回值包名/接名方法名(参数)异常名)

    • execution(* com.itheima.service.Service.(…))
      切入点表达式描述通配符
    • 作用:用于快速描述,范围描述
      • : 配任意符号( 常用 )
    • … : 匹配多个连续的任意符号( 常用 )
    • +:匹配子类类型
  • 切入点表达式书写技巧

    1. 按标准规范开发
    2. 查询操作的返回值建议使用*匹配
    3. 减少使用…的形式描述包
    4. 对接口进行描述,使用表示模块名,例如UserService的匹配描述为Service
    5. 方法名书写保留动词,例如get,使用表示名词,例如getById匹配描述为getBy
    6. 参数根据实际情况灵活调整
  • 通知类型

    • 前置通知

    • 后置通知

      • 环绕通知( 重点)
      • 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
      • 环绕通知可以隔离原始方法的调用执行
      • 环绕通知返回值设置为object类型
      • 环绕通知中可以对原始方法调用过程中出现的异常进行处理
    • 返回后通知

    • 抛出异常后通知

  • 获取切入点方法的参数

    • JoinPoint : 适用于前置、后置、返回后、抛出异常后通知,设置为方法的第一个形参
    • ProceedJointPoint:适用于环绕通知
  • 获取切入点方法返回值

    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息

    • 抛出异常后通知
    • 环绕通知

Spring 事物

  • 事务作用: 在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或业务层保障一系列的数据库操作 同成功 同失败

转账案例

需求:实现任意两个账户间转账操作

需求微缩: A账户减钱,B账户加钱

分析:

  1. 数据层提供基础操作,指定账户减钱 ( outMoney )指定账户加钱(inMoney )
  2. 业务层提供转账操作 ( transfer ),i调用减钱与加钱的操作
  3. 提供2个账号和操作金额执行转账操作
  4. 基于Spring整合MyBatis环境搭建上述操作

实现

  1. config
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

    // 事物管理器
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
public class MybatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.blm.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }
    //定义bean,返回MapperScannerConfigurer对象
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.blm.dao");
        return msc;
    }
}
@Configuration
@ComponentScan("com.blm")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class}) 
@EnableTransactionManagement // 注解事物驱动
public class SpringConfig { }
public interface AccountDao {
    @Update("update bill set fee = fee - #{fee} where id = #{id}")
    void outMoney(@Param("id") Integer id, @Param("fee") Double fee);

    @Update("update bill set fee = fee + #{fee} where id = #{id}")
    void inMoney(@Param("id") Integer id, @Param("fee") Double fee);
}
public interface AccountService {
    @Transactional // 注解开启事务管理
    void transfer(Integer outId, Integer inId, Double fee);
}
@Service
public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer(Integer outId, Integer inId, Double fee) {
        accountDao.outMoney(outId, fee);
        accountDao.inMoney(inId, fee);
    }
}
  1. Test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    AccountService accountService;

    @Test
    public void updateTest() {
        accountService.transfer(1,2, 200.0);
    }
}

注意

  1. Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
  2. 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

事务角色

  • 事务管理员发起事务方在Spring中通常指代业务层开启事务的方法.
  • 加入事务方事务协调员在Spring中通常指代数据层方法,也可以是业务层方法.

转账日志记录

CREATE TABLE log (
	id INT PRIMARY KEY auto_increment,
	info VARCHAR(100) NOT NULL,
	create_date datetime DEFAULT CURRENT_TIMESTAMP
);
public interface LogDao {

    @Insert("insert into log(info, status) values(#{info}, #{status})")
    void log(@Param("info") String info, @Param("status") Integer status);
}
public interface LogService {	
	@Transactional(propagation = Propagation.REQUIRES_NEW) // 事物传播行为
 	void log(String info, Integer status);
}
public class LogServiceImpl implements LogService {
    @Autowired
    private LogDao logDao;
    @Override
    public void log(String info, Integer status) {
        logDao.log(info, status);
    }
}
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private LogService logService;

    @Override
    public void transfer(Integer outId, Integer inId, Double fee) {
        try {
            accountDao.outMoney(outId, fee);
            int i = 1/0;
            accountDao.inMoney(inId, fee);
        } finally {
            String info = outId + "-->" + inId + ":" + fee;
            logService.log(info, 1);
        }
    }
}
 	@Autowired
    LogService logService;
    @Test
    public void updateTest() {
        accountService.transfer(1,2, 200D);
    }

事务相关配置

在这里插入图片描述

@Transactional(readOnly = true, timeout = -1, rollbackFor = {IOException.class})
void transfer(Integer outId, Integer inId, Double fee);

事物传播行为配置

在这里插入图片描述

相关推荐

  1. spring 事务详解

    2024-03-24 08:40:03       10 阅读
  2. 010 spring整合mybatis(事务)(xml)

    2024-03-24 08:40:03       13 阅读
  3. Spring事务传播行为 详解

    2024-03-24 08:40:03       33 阅读
  4. Spring事务管理与Spring AOP详解

    2024-03-24 08:40:03       6 阅读
  5. 关于Spring @Transactional事务传播机制详解

    2024-03-24 08:40:03       34 阅读
  6. Spring Boot 事务传播机制详解

    2024-03-24 08:40:03       7 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-24 08:40:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-24 08:40:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-24 08:40:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-24 08:40:03       18 阅读

热门阅读

  1. Python学习(二):python常用命令

    2024-03-24 08:40:03       15 阅读
  2. Superset二次开发之 配置Docker

    2024-03-24 08:40:03       15 阅读
  3. Kafka系列之:Kafka Connect REST API

    2024-03-24 08:40:03       18 阅读
  4. go实现协程池

    2024-03-24 08:40:03       17 阅读
  5. 前端框架是什么

    2024-03-24 08:40:03       16 阅读
  6. 上位机开发 halcon坐标转轴坐标

    2024-03-24 08:40:03       14 阅读
  7. 区块链与智能合约

    2024-03-24 08:40:03       17 阅读
  8. 机器翻译.

    2024-03-24 08:40:03       17 阅读
  9. 深度学习在遥感图像处理中的应用

    2024-03-24 08:40:03       18 阅读
  10. 从架构角度结合分布式缓存和本地缓存

    2024-03-24 08:40:03       16 阅读
  11. python函数

    2024-03-24 08:40:03       18 阅读
  12. 继承和深拷贝封装

    2024-03-24 08:40:03       19 阅读
  13. 大模型: 提示词工程(prompt engineering)

    2024-03-24 08:40:03       18 阅读