1. SpringIoC容器和核心概念
1.1. Spring IoC容器和容器实现
1.1.1. SpringIoc容器接口
BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,是SpringIoC容器标准化超接口!
ApplicationContext 是 BeanFactory 的子接口。它扩展了以下功能:
- 更容易与 Spring 的 AOP 功能集成
- 消息资源处理(用于国际化)
- 特定于应用程序给予此接口实现,例如Web 应用程序的 WebApplicationContext
简而言之, BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。 ApplicationContext 是 BeanFactory 的完整超集!
ApplicationContext容器实现类:
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
AnnotationConfigApplicationContext | 通过读取Java配置类创建 IOC 容器对象 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
1.1.2. Spring IoC容器管理配置方式
Spring框架提供了多种配置方式:XML配置方式、注解方式和Java配置类方式
1.2. Spring IoC / DI概念总结
- IOC容器:负责实例化、配置和组装 bean(组件)核心容器。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令
- IOC控制反转:IoC 主要是针对对象的创建和调用控制而言的。当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。
- DI依赖注入:DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理。DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。
2. Spring IoC实践和应用
2.1. Spring IoC / DI实现步骤
- 配置元数据
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="..." [1] class="..." [2]> <!-- collaborators and configuration for this bean go here --></bean><bean id="..." class="..."><!-- collaborators and configuration for this bean go here --></bean><!-- more bean definitions go here -->
</beans>
标签 == 组件信息声明
- id 属性是标识单个 Bean 定义的字符串。
- class 属性定义 Bean 的类型并使用完全限定的类名。
- 实例化IoC容器
提供给 ApplicationContext 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH 等)加载配置元数据
// 实例化ioc容器,读取外部配置文件,最终会在容器内进行ioc和di动作
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
- 获取Bean
ApplicationContext 是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class requiredType)
//创建ioc容器对象,指定配置文件,ioc也开始实例组件对象
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
//获取ioc容器的组件对象
PetStoreService service = context.getBean("petStore", PetStoreService.class);
//使用组件对象
List<String> userList = service.getUsernameList();
2.2. 基于XML配置方式组件管理
2.2.1. 组件(Bean)信息声明配置(IOC)
通过定义XML配置文件,声明组件类信息,交给 Spring 的 IoC 容器进行组件管理!
- 基于无参数构造函数
- 准备组件类
public class HappyComponent {//默认包含无参数构造函数public void doWork() {System.out.println("HappyComponent.doWork");}
}
- xml配置文件编写
<!-- 实验一 [重要]创建bean -->
<bean id="happyComponent" class="com.wyb.ioc.HappyComponent"/>
- bean标签:通过配置bean标签告诉IOC容器需要创建对象的组件信息
- id属性:bean的唯一标识,方便后期获取Bean!
- class属性:组件类的全限定符!
- 注意:要求当前组件类必须包含无参数构造函数!
- 基于静态工厂方法实例化
- 准备组件类
public class ClientService {private static ClientService clientService = new ClientService();private ClientService() {}public static ClientService createInstance() {return clientService;}
}
- xml配置文件编写
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
- class属性:指定工厂类的全限定符!
- factory-method: 指定静态工厂方法,注意,该方法必须是static方法。
- 基于实例工厂方法实例化
- 准备组件类
public class DefaultServiceLocator {private static ClientServiceImplclientService = new ClientServiceImpl();public ClientService createClientServiceInstance() {return clientService;}
}
- xml配置文件编写
<!-- 将工厂类进行ioc配置 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
</bean>
<!-- 根据工厂对象的实例工厂方法进行实例化组件对象 -->
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
- factory-bean属性:指定当前容器中工厂Bean 的名称。
- factory-method: 指定实例工厂方法名。注意,实例方法必须是非static的!
2.2.2. 组件(Bean)依赖注入配置(DI)
通过配置文件,实现IoC容器中Bean之间的引用(依赖注入DI配置)。
主要涉及注入场景:基于构造函数的依赖注入和基于 Setter 的依赖注入。
- 基于构造函数的依赖注入(单个构造函数)
- 准备组件类
public class UserDao {
}
public class UserService {private UserDao userDao;public UserService(UserDao userDao) {this.userDao = userDao;}
}
- 编写配置文件
<beans><!-- 引用类bean声明 --><bean id="userService" class="x.y.UserService"><!-- 构造函数引用 --><constructor-arg ref="userDao"/></bean><!-- 被引用类bean声明 --><bean id="userDao" class="x.y.UserDao"/>
</beans>
- constructor-arg标签:可以引用构造参数 ref 引用其他bean的标识。
- 基于构造函数的依赖注入(多构造参数解析)
基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项。
- 准备组件类
public class UserDao {
}
public class UserService {private UserDao userDao;private int age;private String name;public UserService(int age , String name ,UserDao userDao) {this.userDao = userDao;this.age = age;this.name = name;}
}
- 编写配置文件
<!-- 场景1: 多参数,可以按照相应构造函数的顺序注入数据 -->
<beans><bean id="userService" class="x.y.UserService"><!-- value直接注入基本类型值 --><constructor-arg value="18"/><constructor-arg value="wyb"/><constructor-arg ref="userDao"/></bean><!-- 被引用类bean声明 --><bean id="userDao" class="x.y.UserDao"/>
</beans><!-- 场景2: 多参数,可以按照相应构造函数的名称注入数据 -->
<beans><bean id="userService" class="x.y.UserService"><!-- value直接注入基本类型值 --><constructor-arg name="name" value="wyb"/><constructor-arg name="userDao" ref="userDao"/><constructor-arg name="age" value="18"/></bean><!-- 被引用类bean声明 --><bean id="userDao" class="x.y.UserDao"/>
</beans><!-- 场景2: 多参数,可以按照相应构造函数的角标注入数据 index从0开始 构造函数(0,1,2....) -->
<beans><bean id="userService" class="x.y.UserService"><!-- value直接注入基本类型值 --><constructor-arg index="1" value="wyb"/><constructor-arg index="2" ref="userDao"/><constructor-arg index="0" value="18"/></bean><!-- 被引用类bean声明 --><bean id="userDao" class="x.y.UserDao"/>
</beans>
- constructor-arg标签:指定构造参数和对应的值
- constructor-arg标签:name属性指定参数名、index属性指定参数角标、value属性指定普通属性值
- 基于Setter方法依赖注入
除了构造函数注入(DI)更多的使用的Setter方法进行注入!
- 准备组件类
public Class MovieFinder{
}
public class SimpleMovieLister {private MovieFinder movieFinder;private String movieName;public void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}public void setMovieName(String movieName){this.movieName = movieName;}// business logic that actually uses the injected MovieFinder is omitted...
}
- 编写配置文件
<bean id="simpleMovieLister" class="examples.SimpleMovieLister"><!-- setter方法,注入movieFinder对象的标识id name = 属性名 ref = 引用bean的id值 --><property name="movieFinder" ref="movieFinder" /><!-- setter方法,注入基本数据类型movieName name = 属性名 value= 基本类型值 --><property name="movieName" value="消失的她"/>
</bean>
<bean id="movieFinder" class="examples.MovieFinder"/>
- property 标签: 可以给 setter 方法对应的属性赋值
- property 标签: name属性代表set方法标识、ref 代表引用 bean 的标识 id、value 属性代表基本属性值
- 总结:依赖注入(DI)包含引用类型和基本数据类型,同时注入的方式也有多种!主流的注入方式为setter方法注入和构造函数注入,两种注入语法都需要掌握!
- 注意:引用其他 bean,使用 ref 属性。直接注入基本类型值,使用 value 属性。
2.2.3. IoC容器创建和使用
- 容器实例化
//方式1:实例化并且指定配置文件
//参数:String...locations 传入一个或者多个配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
//方式2:先实例化,再指定配置文件,最后刷新容器触发Bean实例化动作 [springmvc源码和contextLoadListener源码方式]
ApplicationContext context = new ClassPathXmlApplicationContext();
//设置配置配置文件,方法参数为可变参数,可以设置一个或者多个配置
iocContainer1.setConfigLocations("services.xml", "daos.xml");
//后配置的文件,需要调用refresh方法,触发刷新配置
iocContainer1.refresh();
- Bean对象读取
//方式1: 根据id获取
//没有指定类型,返回为Object,需要类型转化!
HappyComponent happyComponent = (HappyComponent) iocContainer.getBean("bean的id标识");
//使用组件对象
happyComponent.doWork();//方式2: 根据类型获取
//根据类型获取,但是要求,同类型(当前类,或者之类,或者接口的实现类)只能有一个对象交给IoC容器管理
//配置两个或者以上出现: org.springframework.beans.factory.NoUniqueBeanDefinitionException 问题
HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class);
happyComponent.doWork();//方式3: 根据id和类型获取
HappyComponent happyComponent = iocContainer.getBean("bean的id标识", HappyComponent.class);
happyComponent.doWork();
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。
2.2.4. 高级特性:组件(Bean)作用域和周期方法配置
- 组件周期方法配置
- 周期方法声明
public class BeanOne {//周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表public void init() {// 初始化逻辑}
}
public class BeanTwo {public void cleanup() {// 释放资源逻辑}
}
- 周期方法配置
<beans><bean id="beanOne" class="examples.BeanOne" init-method="init" /><bean id="beanTwo" class="examples.BeanTwo" destroy-method="cleanup" />
</beans>
- 组件作用域配置
- Bean作用域
<bean 标签声明Bean,只是将Bean的信息配置给SpringIoC容器!
在IoC容器中,<bean标签对应的信息转成Spring内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id,class,属性等等)!SpringIoC容器可以可以根据 BeanDefinition 对象反射创建多个 Bean 对象实例。
具体创建多少个 Bean 的实例对象,由 Bean 的作用域 Scope 属性指定! - 作用域可选值
- singleton:在 IOC 容器中,这个 bean 的对象始终为单实例
- prototype:这个 bean 在 IOC 容器中有多个实例
- 作用域配置
<!-- bean的作用域 准备两个引用关系的组件类即可!! -->
<!-- scope属性:取值singleton(默认值),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象 -->
<!-- scope属性:取值prototype,bean在IOC容器中可以有多个实例,getBean()时创建对象 -->
<bean id="happyMachine8" scope="prototype" class="com.wyb.ioc.HappyMachine"><property name="machineName" value="happyMachine"/>
</bean>
<bean id="happyComponent8" scope="singleton" class="com.wyb.ioc.HappyComponent"><property name="componentName" value="happyComponent"/>
</bean>
- 作用域测试
@Test
public void testExperiment08() {ApplicationContext iocContainer = new ClassPathXmlApplicationContext("配置文件名");HappyMachine bean = iocContainer.getBean(HappyMachine.class);HappyMachine bean1 = iocContainer.getBean(HappyMachine.class);//多例对比 falseSystem.out.println(bean == bean1);HappyComponent bean2 = iocContainer.getBean(HappyComponent.class);HappyComponent bean3 = iocContainer.getBean(HappyComponent.class);//单例对比 trueSystem.out.println(bean2 == bean3);
}
2.2.5. 高级特性:FactoryBean特性和使用
- FactoryBean简介
FactoryBean 接口用于配置复杂的Bean对象,可以将创建过程存储在FactoryBean 的getObject方法!提供三种方法:- T getObject(): 返回此工厂创建的对象的实例。该返回值会被存储到IoC容器!
- boolean isSingleton(): 如果此 FactoryBean 返回单例,则返回 true(默认) ,否则返回 false。
- Class<?> getObjectType(): 返回 getObject() 方法返回的对象类型,如果事先不知道类型,则返回 null 。
- FactoryBean使用场景
- 代理类的创建
- 第三方框架整合
- 复杂对象实例化等
- Factorybean应用
- 准备FactoryBean实现类
// 实现FactoryBean接口时需要指定泛型
// 泛型类型就是当前工厂要生产的对象的类型
public class HappyFactoryBean implements FactoryBean<HappyMachine> {private String machineName;public String getMachineName() {return machineName;}public void setMachineName(String machineName) {this.machineName = machineName;}@Overridepublic HappyMachine getObject() throws Exception {// 方法内部模拟创建、设置一个对象的复杂过程HappyMachine happyMachine = new HappyMachine();happyMachine.setMachineName(this.machineName);return happyMachine;}@Overridepublic Class<?> getObjectType() {// 返回要生产的对象的类型return HappyMachine.class;}
}
- 配置FactoryBean实现类
<!-- FactoryBean机制 -->
<!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 -->
<bean id="happyMachine7" class="com.wyb.ioc.HappyFactoryBean"><!-- property标签仍然可以用来通过setXxx()方法给属性赋值 --><property name="machineName" value="iceCreamMachine"/>
</bean>
- 测试读取FactoryBean和FactoryBean.getObject对象
@Test
public void testExperiment07() {ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-07.xml");//注意: 直接根据声明FactoryBean的id,获取的是getObject方法返回的对象HappyMachine happyMachine = iocContainer.getBean("happyMachine7",HappyMachine.class);System.out.println("happyMachine = " + happyMachine);//如果想要获取FactoryBean对象, 直接在id前添加&符号即可! &happyMachine7 这是一种固定的约束Object bean = iocContainer.getBean("&happyMachine7");System.out.println("bean = " + bean);
}
- FactoryBean和BeanFactory区别
- FactoryBean:创建 bean 的接口,提供了更加灵活的初始化定制功能
- BeanFactory:管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。
2.2.6. 基于XML方式整合三层架构组件
搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用XML方式进行组件管理!
- springioc配置文件
<?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 https://www.springframework.org/schema/context/spring-context.xsd"><!-- 导入外部属性文件 --><context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置数据源 --><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${wyb.url}"/><property name="driverClassName" value="${wyb.driver}"/><property name="username" value="${wyb.username}"/><property name="password" value="${wyb.password}"/></bean><!-- 配置 JdbcTemplate --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><!-- 装配数据源 --><property name="dataSource" ref="druidDataSource"/></bean>
</beans>
- 基于jdbcTemplate的CRUD使用
public class JdbcTemplateTest {/*** 使用jdbcTemplate进行DML动作*/@Testpublic void testDML(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml");JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);//TODO 执行插入一条学员数据String sql = "insert into students (id,name,gender,age,class) values (?,?,?,?,?);";/*参数1: sql语句参数2: 可变参数,占位符的值*/int rows = jdbcTemplate.update(sql, 9,"十一", "男", 18, "二年三班");System.out.println("rows = " + rows);}/*** 查询单条实体对象* public class Student {* private Integer id;* private String name;* private String gender;* private Integer age;* private String classes;*/@Testpublic void testDQLForPojo(){String sql = "select id , name , age , gender , class as classes from students where id = ? ;";ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml");JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);//根据id查询Student student = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {//自己处理结果映射Student stu = new Student();stu.setId(rs.getInt("id"));stu.setName(rs.getString("name"));stu.setAge(rs.getInt("age"));stu.setGender(rs.getString("gender"));stu.setClasses(rs.getString("classes"));return stu;}, 2);System.out.println("student = " + student);}/*** 查询实体类集合*/@Testpublic void testDQLForListPojo(){String sql = "select id , name , age , gender , class as classes from students ;";ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml");JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);/*query可以返回集合!BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可*/List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));System.out.println("studentList = " + studentList);}
}
- 三层架构搭建和实现
- 持久层
//接口
public interface StudentDao {/*** 查询全部学生数据* @return*/List<Student> queryAll();//实现类
public class StudentDaoImpl implements StudentDao {private JdbcTemplate jdbcTemplate;public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}/*** 查询全部学生数据* @return*/@Overridepublic List<Student> queryAll() {String sql = "select id , name , age , gender , class as classes from students ;";/*query可以返回集合!BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可*/List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));return studentList;}
}
- 业务层
//接口
public interface StudentService {/*** 查询全部学员业务* @return*/List<Student> findAll();
}
//实现类
public class StudentServiceImpl implements StudentService {private StudentDao studentDao;public void setStudentDao(StudentDao studentDao) {this.studentDao = studentDao;}/*** 查询全部学员业务* @return*/@Overridepublic List<Student> findAll() {List<Student> studentList = studentDao.queryAll();return studentList;}
}
- 表述层
public class StudentController {private StudentService studentService;public void setStudentService(StudentService studentService) {this.studentService = studentService;}public void findAll(){List<Student> studentList = studentService.findAll();System.out.println("studentList = " + studentList);}
}
- 三层架构IoC配置
<?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 https://www.springframework.org/schema/context/spring-context.xsd"><!-- 导入外部属性文件 --><context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置数据源 --><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${wyb.url}"/><property name="driverClassName" value="${wyb.driver}"/><property name="username" value="${wyb.username}"/><property name="password" value="${wyb.password}"/></bean><!-- 配置 JdbcTemplate --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><!-- 装配数据源 --><property name="dataSource" ref="druidDataSource"/></bean><bean id="studentDao" class="com.wyb.dao.impl.StudentDaoImpl"><property name="jdbcTemplate" ref="jdbcTemplate" /></bean><bean id="studentService" class="com.wyb.service.impl.StudentServiceImpl"><property name="studentDao" ref="studentDao" /></bean<bean id="studentController" class="com.wyb.controller.StudentController"><property name="studentService" ref="studentService" /></bean>
</beans>
- 运行测试
public class ControllerTest {@Testpublic void testRun(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml");StudentController studentController = applicationContext.getBean(StudentController.class);studentController.findAll();}
}
- XMLIoC方式问题总结
- 注入的属性必须添加setter方法、代码结构乱!
- 配置文件和Java代码分离、编写不是很方便!
- XML配置文件解析效率低
2.3. 基于注解方式管理 Bean
2.3.1. Bean注解标记和扫描 (IoC)
- 组件添加标记注解
Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。
注解 | 说明 |
---|---|
@Component | 描述 Spring 中的 Bean,表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。 |
@Repository | 用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Service | 作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Controller | 作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
- 普通组件
/*** projectName: com.wyb.components* description: 普通的组件*/
@Component
public class CommonComponent {
}
- Controller组件
/*** projectName: com.wyb.components* description: controller类型组件*/
@Controller
public class XxxController {
}
- Service组件
/*** projectName: com.wyb.components* description: service类型组件*/
@Service
public class XxxService {
}
- Dao组件
/*** projectName: com.wyb.components* description: dao类型组件*/
@Repository
public class XxxDao {
}
- 配置文件确定扫描范围
- 基本扫描配置
<?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 https://www.springframework.org/schema/context/spring-context.xsd"><!-- 配置自动扫描的包 --><!-- 1.包要精准,提高性能! 2.会扫描指定的包和子包内容 3.多个包可以使用,分割 例如: com.wyb.controller,com.atguigu.service等 --><context:component-scan base-package="com.wyb.components"/>
</beans>
- 指定排除组件
<!-- 情况三:指定不扫描的组件 -->
<context:component-scan base-package="com.wyb.components"><!-- context:exclude-filter标签:指定排除规则 --><!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 --><!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 --><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 指定扫描组件
<!-- 情况四:仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.wyb.ioc.components" use-default-filters="false"><!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 --><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 组件BeanName问题
默认情况:类名首字母小写就是 bean 的 id。
- 使用value属性指定
@Controller(value = "tianDog")
public class SoldierController {
}
当注解中只设置一个属性时,value属性的属性名可以省略
- 总结
- 注解方式IoC只是标记哪些类要被Spring管理
- 最终,我们还需要XML方式或者后面讲解Java配置类方式指定注解生效的包
- 现阶段配置方式为 注解 (标记)+ XML(扫描)
2.3.2. 组件(Bean)作用域和周期方法注解
- 组件周期方法配置
- 周期方法声明
public class BeanOne {//周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表@PostConstruct //注解制指定初始化方法public void init() {// 初始化逻辑}
}
public class BeanTwo {@PreDestroy //注解指定销毁方法public void cleanup() {// 释放资源逻辑}
}
- 组件作用域配置
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例 二选一
public class BeanOne {//周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表@PostConstruct //注解制指定初始化方法public void init() {// 初始化逻辑}
}
2.3.3. Bean属性赋值:引用类型自动装配 (DI)
- 设定场景
-
SoldierController 需要 SoldierService
-
SoldierService 需要 SoldierDao
同时在各个组件中声明要调用的方法。
-
- SoldierController中声明方法
import org.springframework.stereotype.Controller;
@Controller(value = "tianDog")
public class SoldierController {private SoldierService soldierService;public void getMessage() {soldierService.getMessage();}
}
- SoldierService中声明方法
@Service("smallDog")
public class SoldierService {private SoldierDao soldierDao;public void getMessage() {soldierDao.getMessage();}
}
- SoldierDao中声明方法
@Repository
public class SoldierDao {public void getMessage() {System.out.print("I am a soldier");}
}
- 自动装配实现
前提:参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。
@Autowired注解:在成员变量上直接标记@Autowired注解即可
- 给Controller装配Service
@Controller(value = "tianDog")
public class SoldierController {@Autowiredprivate SoldierService soldierService;public void getMessage() {soldierService.getMessage();}
}
- 给Service装配Dao
@Service("smallDog")
public class SoldierService {@Autowiredprivate SoldierDao soldierDao;public void getMessage() {soldierDao.getMessage();}
}
- @Autowired注解细节
- 标记位置
- 成员变量:他不需要有set方法
@Service("smallDog")
public class SoldierService {@Autowiredprivate SoldierDao soldierDao;public void getMessage() {soldierDao.getMessage();}
}
- 构造器
@Controller(value = "tianDog")
public class SoldierController {private SoldierService soldierService;@Autowiredpublic SoldierController(SoldierService soldierService) {this.soldierService = soldierService;}
- setXxx()方法
@Controller(value = "tianDog")
public class SoldierController {private SoldierService soldierService;@Autowiredpublic void setSoldierService(SoldierService soldierService) {this.soldierService = soldierService;}
- 工作流程
- 首先根据所需要的组件类型到 IOC 容器中查找
- 能够找到唯一的 bean:直接执行装配
- 如果完全找不到匹配这个类型的 bean:装配失败
- 和所需类型匹配的 bean 不止一个
- 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
- 能够找到:执行装配
- 找不到:装配失败
- 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
- 能够找到:执行装配
- 找不到:装配失败
- 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
@Controller(value = "tianDog")
public class SoldierController {@Autowired@Qualifier(value = "maomiService222")// 根据面向接口编程思想,使用接口类型引入Service组件private ISoldierService soldierService;
- 注解@Resource
@Resource注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型装配
@Resource注解用在属性上、setter方法上。
@Controller
public class XxxController {/*** 1. 如果没有指定name,先根据属性名查找IoC中组件xxxService* 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找* 3. 可以指定name名称查找! @Resource(name='test') == @Autowired + @Qualifier(value='test')*/@Resourceprivate XxxService xxxService;//@Resource(name = "指定beanName")//private XxxService xxxService;public void show(){System.out.println("XxxController.show");xxxService.show();}
}
2.3.4. Bean属性赋值:基本类型属性赋值 (DI)
@Value 通常用于注入外部化属性
- 声明外部配置:application.properties
catalog.name=MovieCatalog
- xml引入外部配置
<!-- 引入外部配置文件-->
<context:property-placeholder location="application.properties" />
- @Value注解读取配置
package com.wyb.components;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** projectName: com.atguigu.components* description: 普通的组件*/
@Component
public class CommonComponent {/*** 情况1: ${key} 取外部配置key对应的值!* 情况2: ${key:defaultValue} 没有key,可以给与默认值*/@Value("${catalog:hahaha}")private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
2.3.5. 基于注解+XML方式整合三层架构组件
- 三层架构搭建和实现
- 持久层
//接口
public interface StudentDao {/*** 查询全部学生数据* @return*/List<Student> queryAll();
}
//实现类
@Repository
public class StudentDaoImpl implements StudentDao {@Autowiredprivate JdbcTemplate jdbcTemplate;/*** 查询全部学生数据* @return*/@Overridepublic List<Student> queryAll() {String sql = "select id , name , age , gender , class as classes from students ;";/*query可以返回集合!BeanPropertyRowMapper就是封装好RowMapper的实现,要求属性名和列名相同即可*/List<Student> studentList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Student.class));return studentList;}
}
- 业务层
//接口
public interface StudentService {/*** 查询全部学员业务* @return*/List<Student> findAll();
}
//实现类
@Service
public class StudentServiceImpl implements StudentService {@Autowiredprivate StudentDao studentDao;/*** 查询全部学员业务* @return*/@Overridepublic List<Student> findAll() {List<Student> studentList = studentDao.queryAll();return studentList;}
}
- 表述层
@Controller
public class StudentController {@Autowiredprivate StudentService studentService;public void findAll(){List<Student> studentList = studentService.findAll();System.out.println("studentList = " + studentList);}
}
- 三层架构IoC配置
<?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 https://www.springframework.org/schema/context/spring-context.xsd"><!-- 导入外部属性文件 --><context:property-placeholder location="classpath:jdbc.properties" /><!-- 配置数据源 --><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${wyb.url}"/><property name="driverClassName" value="${wyb.driver}"/><property name="username" value="${wyb.username}"/><property name="password" value="${wyb.password}"/></bean><bean class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="druidDataSource" /></bean><!-- 扫描Ioc/DI注解 --><context:component-scan base-package="com.wyb.dao,com.wyb.service,com.wyb.controller" />
</beans>
- 运行测试
public class ControllerTest {@Testpublic void testRun(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc.xml");StudentController studentController = applicationContext.getBean(StudentController.class);studentController.findAll();}
}
- 注解+XML IoC方式问题总结
- 自定义类可以使用注解方式,但是第三方依赖的类依然使用XML方式!
- XML格式解析效率低!
2.4. 基于 配置类 方式管理 Bean
2.4.1. 完全注解开发理解
Spring 完全注解配置是指通过 Java配置类 代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。
2.4.2. 配置类和扫描注解
- xml+注解方式:
- 配置文件application.xml
<?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 https://www.springframework.org/schema/context/spring-context.xsd"><!-- 配置自动扫描的包 --><!-- 1.包要精准,提高性能! 2.会扫描指定的包和子包内容 3.多个包可以使用,分割 例如: com.wyb.controller,com.wyb.service等 --><context:component-scan base-package="com.wyb.components"/><!-- 引入外部配置文件--><context:property-placeholder location="application.properties" />
</beans>
- 测试创建IoC容器
// xml方式配置文件使用ClassPathXmlApplicationContext容器读取ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
- 配置类+注解方式(完全注解方式)
- 配置类:使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
//标注当前类是配置类,替代application.xml
@Configuration
//使用注解读取外部配置,替代 <context:property-placeholder标签
@PropertySource("classpath:application.properties")
//使用@ComponentScan注解,可以配置扫描包,替代<context:component-scan标签
@ComponentScan(basePackages = {"com.wyb.components"})
public class MyConfiguration {
}
- 测试创建IoC容器
// AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext(MyConfiguration.class);
可以使用 no-arg 构造函数实例化 AnnotationConfigApplicationContext ,然后使用 register() 方法对其进行配置。此方法在以编程方式生成AnnotationConfigApplicationContext 时特别有用。
// AnnotationConfigApplicationContext-IOC容器对象
ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext();
//外部设置配置类
iocContainerAnnotation.register(MyConfiguration.class);
//刷新后方可生效!!
iocContainerAnnotation.refresh();
- 总结:
- @Configuration指定一个类为配置类,可以添加配置注解,替代配置xml文件
- @ComponentScan(basePackages = {“包”,“包”}) 替代<context:component-scan标签实现注解扫描
- @PropertySource(“classpath:配置文件地址”) 替代 <context:property-placeholder标签
- 配合IoC/DI注解,可以进行完整注解开发!
2.4.3. @Bean定义组件
场景需求:将Druid连接池对象存储到IoC容器
需求分析:第三方jar包的类,添加到ioc容器,无法使用@Component等相关注解!因为源码jar包内容为只读模式!
- xml方式实现:
<?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 https://www.springframework.org/schema/context/spring-context.xsd"><!-- 引入外部属性文件 --><context:property-placeholder location="classpath:jdbc.properties"/><!-- 实验六 [重要]给bean的属性赋值:引入外部属性文件 --><bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url" value="${jdbc.url}"/><property name="driverClassName" value="${jdbc.driver}"/><property name="username" value="${jdbc.user}"/><property name="password" value="${jdbc.password}"/></bean>
</beans>
- 配置类方式实现
@Bean 注释用于指示方法实例化、配置和初始化要由 Spring IoC 容器管理的新对象。对于那些熟悉 Spring 的<beans/>
XML 配置的人来说, @Bean 注释与<bean/>
元素起着相同的作用。
//标注当前类是配置类,替代application.xml
@Configuration
//引入jdbc.properties文件
@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
@ComponentScan(basePackages = {"com.wyb.components"})
public class MyConfiguration {//如果第三方类进行IoC管理,无法直接使用@Component相关注解//解决方案: xml方式可以使用<bean标签//解决方案: 配置类方式,可以使用方法返回值+@Bean注解@Beanpublic DataSource createDataSource(@Value("${jdbc.user}") String username, @Value("${jdbc.password}") String password, @Value("${jdbc.url}") String url, @Value("${jdbc.driver}") String driverClassName){//使用Java代码实例化DruidDataSource dataSource = new DruidDataSource();dataSource.setUsername(username);dataSource.setPassword(password);dataSource.setUrl(url);dataSource.setDriverClassName(driverClassName);//返回结果即可return dataSource;}
}
2.4.4. 高级特性:@Bean注解细节
- @Bean 初始化和销毁方法指定
@Bean 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 元素上的 init-method 和 destroy-method 属性
public class BeanOne {public void init() {// initialization logic}
}
public class BeanTwo {public void cleanup() {// destruction logic}
}
@Configuration
public class AppConfig {@Bean(initMethod = "init")public BeanOne beanOne() {return new BeanOne();}@Bean(destroyMethod = "cleanup")public BeanTwo beanTwo() {return new BeanTwo();}
}
- @Bean Scope作用域
默认作用域为 singleton ,但可以使用 @Scope 注释覆盖此范围
@Configuration
public class MyConfiguration {@Bean@Scope("prototype")public Encryptor encryptor() {}
}
- @Bean方法之间依赖
- 准备组件
public class HappyMachine {private String machineName;public String getMachineName() {return machineName;}public void setMachineName(String machineName) {this.machineName = machineName;}
}
public class HappyComponent {//引用新组件private HappyMachine happyMachine;public HappyMachine getHappyMachine() {return happyMachine;}public void setHappyMachine(HappyMachine happyMachine) {this.happyMachine = happyMachine;}public void doWork() {System.out.println("HappyComponent.doWork");}
}
- Java配置类实现:
直接调用方法返回 Bean 实例:在一个 @Bean 方法中直接调用其他 @Bean 方法来获取 Bean 实例,虽然是方法调用,也是通过IoC容器获取对应的Bean
@Configuration
public class JavaConfig {@Beanpublic HappyMachine happyMachine(){return new HappyMachine();}@Beanpublic HappyComponent happyComponent(){HappyComponent happyComponent = new HappyComponent();//直接调用方法即可! happyComponent.setHappyMachine(happyMachine());return happyComponent;}
}
参数引用法:通过方法参数传递 Bean 实例的引用来解决 Bean 实例之间的依赖关系
package com.wyb.config;
import com.wyb.ioc.HappyComponent;
import com.wyb.ioc.HappyMachine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*** projectName: com.wyb.config* description: 配置HappyComponent和HappyMachine关系*/
@Configuration
public class JavaConfig {@Beanpublic HappyMachine happyMachine(){return new HappyMachine();}/*** 可以直接在形参列表接收IoC容器中的Bean!* 情况1: 直接指定类型即可* 情况2: 如果有多个bean,(HappyMachine 名称 ) 形参名称等于要指定的bean名称!* 例如:* @Bean* public Foo foo1(){* return new Foo();* }* @Bean* public Foo foo2(){* return new Foo()* }* @Bean* public Component component(Foo foo1 / foo2 通过此处指定引入的bean)*/@Beanpublic HappyComponent happyComponent(HappyMachine happyMachine){HappyComponent happyComponent = new HappyComponent();//赋值happyComponent.setHappyMachine(happyMachine);return happyComponent;}
}
2.4.5. 高级特性:@Import扩展
@Import 注释允许从另一个配置类加载 @Bean 定义
@Configuration
public class ConfigA {@Beanpublic A a() {return new A();}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {@Beanpublic B b() {return new B();}
}
现在,在实例化上下文时不需要同时指定 ConfigA.class 和 ConfigB.class ,只需显式提供 ConfigB
public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);A a = ctx.getBean(A.class);B b = ctx.getBean(B.class);
}
2.4.6. 基于注解+配置类方式整合三层架构组件
搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用注解+配置类方式进行组件管理!
- 三层架构IoC配置类
@Configuration
@ComponentScan(basePackages = "com.wyb")
@PropertySource("classpath:jdbc.properties")
public class JavaConfig {@Value("${wyb.url}")private String url;@Value("${wyb.driver}")private String driver;@Value("${wyb.username}")private String username;@Value("${wyb.password}")private String password;@Bean(destroyMethod = "close")public DruidDataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(url);dataSource.setDriverClassName(driver);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}
}
- 运行测试
public class ControllerTest {@Testpublic void testRun(){AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);StudentController studentController = applicationContext.getBean(StudentController.class);studentController.findAll();}
}
- 注解+配置类 IoC方式总结
- 完全摒弃了XML配置文件
- 自定义类使用IoC和DI注解标记
- 第三方类使用配置类声明方法+@Bean方式处理
- 完全注解方式(配置类+注解)是现在主流配置方式
2.5. 三种配置方式总结
2.5.1. XML方式配置方式总结
- 所有内容写到xml格式配置文件中
- 声明bean通过<bean标签
- <bean标签包含基本信息(id,class)和属性信息 <property name value / ref
- 引入外部的properties文件可以通过<context:property-placeholder
- IoC具体容器实现选择ClassPathXmlApplicationContext对象
2.5.2. XML + 注解方式配置总结
- 注解负责标记IoC的类和进行属性装配
- xml文件依然需要,需要通过<context:component-scan标签指定注解范围
- 标记IoC注解:@Component,@Service,@Controller,@Repository
- 标记DI注解:@Autowired @Qualifier @Resource @Value
- IoC具体容器实现选择ClassPathXmlApplicationContext对象
2.5.3. 完全注解方式配置总结
- 完全注解方式指的是去掉xml文件,使用配置类 + 注解实现
- xml文件替换成使用@Configuration注解标记的类
- 标记IoC注解:@Component,@Service,@Controller,@Repository
- 标记DI注解:@Autowired @Qualifier @Resource @Value
- <context:component-scan标签指定注解范围使用@ComponentScan(basePackages = {“com.atguigu.components”})替代
- <context:property-placeholder引入外部配置文件使用@PropertySource({“classpath:application.properties”,“classpath:jdbc.properties”})替代
- <bean 标签使用@Bean注解和方法实现
- IoC具体容器实现选择AnnotationConfigApplicationContext对象
2.6. 整合Spring5-Test5搭建测试环境
- 整合测试环境作用
- 不需要自己创建IOC容器对象了
- 任何需要的bean都可以在测试类中直接享受自动装配
- 整合测试注解使用
//@SpringJUnitConfig(locations = {"classpath:spring-context.xml"}) //指定配置文件xml
@SpringJUnitConfig(value = {BeanConfig.class}) //指定配置类
public class Junit5IntegrationTest {@Autowiredprivate User user;@Testpublic void testJunit5() {System.out.println(user);}
}
3. Spring AOP面向切面编程
3.1. 代理模式
通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
- 静态代理
主动创建代理类:
public class CalculatorStaticProxy implements Calculator {// 将被代理的目标对象声明为成员变量private Calculator target;public CalculatorStaticProxy(Calculator target) {this.target = target;}@Overridepublic int add(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println("参数是:" + i + "," + j);// 通过目标对象来实现核心业务逻辑int addResult = target.add(i, j);System.out.println("方法内部 result = " + result);return addResult;}……
- 动态代理
- JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!
- cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!
public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxy(){/*** newProxyInstance():创建一个代理实例* 其中有三个参数:* 1、classLoader:加载动态生成的代理类的类加载器* 2、interfaces:目标对象实现的所有接口的class对象所组成的数组* 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法*/ClassLoader classLoader = target.getClass().getClassLoader();Class<?>[] interfaces = target.getClass().getInterfaces();InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** proxy:代理对象* method:代理对象需要实现的方法,即其中需要重写的方法* args:method所对应方法的参数*/Object result = null;try {System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));result = method.invoke(target, args);System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);} catch (Exception e) {e.printStackTrace();System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());} finally {System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");}return result;}};return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);}
}
- 测试代码
@Test
public void testDynamicProxy(){ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl());Calculator proxy = (Calculator) factory.getProxy();proxy.div(1,0);
}
- 代理总结
代理方式可以解决附加功能代码干扰核心代码和不方便统一维护的问题! 主要是将附加功能代码提取到代理中执行,不干扰目标核心代码!
但是无论使用静态代理和动态代理(jdk,cglib),程序员的工作都比较繁琐,需要自己编写代理工厂等!在实际开发中,不需要编写代理代码,可以使用[Spring AOP]框架,会简化动态代理的实现
3.2. 面向切面编程思维(AOP)
-
AOP思想主要的应用场景
AOP(面向切面编程)是一种编程范式,通过将通用的横切关注点(如日志、事务、权限控制等)与业务逻辑分离,使得代码更加清晰、简洁、易于维护。AOP可以应用于各种场景:
- 日志记录
- 事务处理
- 安全控制
- 性能监控
- 异常处理
- 缓存控制
- 动态代理
AOP可以应用于各种场景,它的作用是将通用的横切关注点与业务逻辑分离,使得代码更加清晰、简洁、易于维护。
-
AOP术语名词介绍
-
横切关注点
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,可以使用多个横切关注点对相关方法进行多个不同方面的增强。有十个附加功能,就有十个横切关注点。
-
通知(增强)
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行
- 异常通知:在被代理的目标方法异常结束后执行
- 后置通知:在被代理的目标方法最终结束后执行
- 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
-
连接点 joinpoint
指那些被拦截到的点。在 Spring 中,可以被动态代理拦截目标类的方法
-
切入点 pointcut
定位连接点的方式,被选中的连接点,是一个表达式,比如execution(* com.spring.service.impl.*.*(..))
。符合条件的每个方法都是一个具体的连接点。 -
切面 aspect
切入点和通知的结合。是一个类。 -
目标 target
被代理的目标对象。 -
代理 proxy
向目标对象应用通知之后创建的代理对象。 -
织入 weave
把通知应用到目标上,生成代理对象的过程。可以在编译期织入,也可以在运行期织入,Spring采用后者。
-
3.3. Spring AOP框架介绍和关系梳理
- AOP一种区别于OOP的编程思维,用来完善和解决OOP的非核心代码冗余和不方便统一维护问题!
- 代理技术(动态代理|静态代理)是实现AOP思维编程的具体技术,但是自己使用动态代理实现代码比较繁琐!
- Spring AOP框架,基于AOP编程思维,封装动态代理技术,简化动态代理技术实现的框架!SpringAOP内部帮助我们实现动态代理,我们只需写少量的配置,指定生效范围即可,即可完成面向切面思维编程的实现!
3.4. Spring AOP基于注解方式实现和细节
3.4.1. Spring AOP底层技术组成
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口
- cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口。
- AspectJ:早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。
3.4.2. 初步实现
- 准备接口
public interface Calculator {int add(int i, int j); int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}
- 实现类
package com.wyb.proxy;
/*** 实现计算接口,单纯添加 + - * / 实现! 掺杂其他功能!*/
@Component
public class CalculatorPureImpl implements Calculator {@Overridepublic int add(int i, int j) {int result = i + j;return result;}@Overridepublic int sub(int i, int j) {int result = i - j;return result;}@Overridepublic int mul(int i, int j) {int result = i * j;return result;}@Overridepublic int div(int i, int j) {int result = i / j;return result;}
}
- 切面类
package com.wyb.advice;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {// @Before注解:声明当前方法是前置通知方法// value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上@Before(value = "execution(public int com.wyb.proxy.CalculatorPureImpl.add(int,int))")public void printLogBeforeCore() {System.out.println("[AOP前置通知] 方法开始了");}@AfterReturning(value = "execution(public int com.wyb.proxy.CalculatorPureImpl.add(int,int))")public void printLogAfterSuccess() {System.out.println("[AOP返回通知] 方法成功返回了");}@AfterThrowing(value = "execution(public int com.wyb.proxy.CalculatorPureImpl.add(int,int))")public void printLogAfterException() {System.out.println("[AOP异常通知] 方法抛异常了");}@After(value = "execution(public int com.wyb.proxy.CalculatorPureImpl.add(int,int))")public void printLogFinallyEnd() {System.out.println("[AOP后置通知] 方法最终结束了");}
}
- 开启aspectj注解支持
- xml方式
<?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"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 进行包扫描--><context:component-scan base-package="com.wyb" /><!-- 开启aspectj框架注解支持--><aop:aspectj-autoproxy />
</beans>
- 配置类方式
@Configuration
@ComponentScan(basePackages = "com.wyb")
//作用等于 <aop:aspectj-autoproxy /> 配置类上开启 Aspectj注解支持!
@EnableAspectJAutoProxy
public class MyConfig {
}
- 测试效果
//@SpringJUnitConfig(locations = "classpath:spring-aop.xml")
@SpringJUnitConfig(value = {MyConfig.class})
public class AopTest {@Autowiredprivate Calculator calculator;@Testpublic void testCalculator(){calculator.add(1,1);}
}
输出结果:
[AOP前置通知] 方法开始了
[AOP返回通知] 方法成功返回了
[AOP后置通知] 方法最终结束了
3.4.3. 获取通知细节信息
- JoinPoint接口
需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。
- 要点1:JoinPoint 接口通过 getSignature() 方法获取目标方法的签名(方法声明时的完整信息)
- 要点2:通过目标方法签名对象获取方法名
- 要点3:通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组
// @Before注解标记前置通知方法
// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
@Before(value = "execution(public int com.wyb.aop.api.Calculator.add(int,int))")
public void printLogBeforeCore(JoinPoint joinPoint) {// 1.通过JoinPoint对象获取目标方法签名对象// 方法的签名:一个方法的全部声明信息Signature signature = joinPoint.getSignature();// 2.通过方法的签名对象获取目标方法的详细信息String methodName = signature.getName();System.out.println("methodName = " + methodName);int modifiers = signature.getModifiers();System.out.println("modifiers = " + modifiers);String declaringTypeName = signature.getDeclaringTypeName();System.out.println("declaringTypeName = " + declaringTypeName);// 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表Object[] args = joinPoint.getArgs();// 4.由于数组直接打印看不到具体数据,所以转换为List集合List<Object> argList = Arrays.asList(args);System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList);
}
- 方法返回值
在返回通知中,通过@AfterReturning注解的returning属性获取目标方法的返回值!
// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(value = "execution(public int com.wyb.aop.api.Calculator.add(int,int))",returning = "targetMethodReturnValue"
)
public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {String methodName = joinPoint.getSignature().getName();System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);
}
- 异常对象捕捉
在异常通知中,通过@AfterThrowing注解的throwing属性获取目标方法抛出的异常对象
// @AfterThrowing注解标记异常通知方法
// 在异常通知中获取目标方法抛出的异常分两步:
// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
@AfterThrowing(value = "execution(public int com.wyb.aop.api.Calculator.add(int,int))",throwing = "targetMethodException"
)
public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {String methodName = joinPoint.getSignature().getName();System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());
}
3.4.4. 切点表达式语法
- 切点表达式作用
AOP切点表达式是一种用于指定切点的语言,它可以通过定义匹配规则,来选择需要被切入的目标对象。
- 切点表达式语法
- 第一位:execution( ) 固定开头
- 第二位:方法访问修饰符
public private 直接描述对应修饰符即可
- 第三位:方法返回值
int String void 直接描述返回值类型
- 第四位:指定包的地址
固定的包: com.wyb.api | service | dao单层的任意命名: com.wyb.* = com.wyb.api com.wyb.dao * = 任意一层的任意命名任意层任意命名: com.. = com.wyb.api.erdaye com.a.a.a.a.a.a.a ..任意层,任意命名 用在包上!注意: ..不能用作包开头 public int .. 错误语法 com..找到任何包下: *..
- 第五位:指定类名称
固定名称: UserService
任意类名: *
部分任意: com..service.impl.*Impl
任意包任意类: *..*
- 第六位:指定方法名称
语法和类名一致
任意访问修饰符,任意类的任意方法: * ….* - 第七位:方法参数
具体值: (String,int) != (int,String) 没有参数 ()模糊值: 任意参数 有 或者 没有 (..) ..任意参数的意识部分具体和模糊:第一个参数是字符串的方法 (String..)最后一个参数是字符串 (..String)字符串开头,int结尾 (String..int)包含int类型(..int..)
3.4.5. 重用(提取)切点表达式
- 重用切点表达式优点
// @Before注解:声明当前方法是前置通知方法
// value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
@Before(value = "execution(public int com.wyb.proxy.CalculatorPureImpl.add(int,int))")
public void printLogBeforeCore() {System.out.println("[AOP前置通知] 方法开始了");
}
@AfterReturning(value = "execution(public int com.wyb.proxy.CalculatorPureImpl.add(int,int))")
public void printLogAfterSuccess() {System.out.println("[AOP返回通知] 方法成功返回了");
}
@AfterThrowing(value = "execution(public int com.wyb.proxy.CalculatorPureImpl.add(int,int))")
public void printLogAfterException() {System.out.println("[AOP异常通知] 方法抛异常了");
}
@After(value = "execution(public int com.wyb.proxy.CalculatorPureImpl.add(int,int))")
public void printLogFinallyEnd() {System.out.println("[AOP后置通知] 方法最终结束了");
}
所有增强方法的切点表达式相同,出现了冗余,如果需要切换也不方便统一维护
可以将切点提取,在增强上进行引用即可
2. 同一类内部引用
// 切入点表达式重用
@Pointcut("execution(public int com.wyb.aop.api.Calculator.add(int,int)))")
public void declarPointCut() {}
注意:提取切点注解使用@Pointcut(切点表达式) , 需要添加到一个无参数无返回值方法上即可!
- 引用
@Before(value = "declarPointCut()")
public void printLogBeforeCoreOperation(JoinPoint joinPoint) {}
- 切点统一管理
建议:将切点表达式统一存储到一个类中进行集中管理和维护!
@Component
public class WybPointCut {@Pointcut(value = "execution(public int *..Calculator.sub(int,int))")public void wybGlobalPointCut(){}@Pointcut(value = "execution(public int *..Calculator.add(int,int))")public void wybSecondPointCut(){}@Pointcut(value = "execution(* *..*Service.*(..))")public void transactionPointCut(){}
}
3.4.6. 环绕通知
环绕通知对应整个 try…catch…finally 结构,包括前面四种通知的所有功能。
// 使用@Around注解标明环绕通知方法
@Around(value = "com.wyb.aop.aspect.AtguiguPointCut.transactionPointCut()")
public Object manageTransaction(// 通过在通知方法形参位置声明ProceedingJoinPoint类型的形参,// Spring会将这个类型的对象传给我们ProceedingJoinPoint joinPoint) {// 通过ProceedingJoinPoint对象获取外界调用目标方法时传入的实参数组Object[] args = joinPoint.getArgs();// 通过ProceedingJoinPoint对象获取目标方法的签名对象Signature signature = joinPoint.getSignature();// 通过签名对象获取目标方法的方法名String methodName = signature.getName();// 声明变量用来存储目标方法的返回值Object targetMethodReturnValue = null;try {// 在目标方法执行前:开启事务(模拟)log.debug("[AOP 环绕通知] 开启事务,方法名:" + methodName + ",参数列表:" + Arrays.asList(args));// 过ProceedingJoinPoint对象调用目标方法// 目标方法的返回值一定要返回给外界调用者targetMethodReturnValue = joinPoint.proceed(args);// 在目标方法成功返回后:提交事务(模拟)log.debug("[AOP 环绕通知] 提交事务,方法名:" + methodName + ",方法返回值:" + targetMethodReturnValue);}catch (Throwable e){// 在目标方法抛异常后:回滚事务(模拟)log.debug("[AOP 环绕通知] 回滚事务,方法名:" + methodName + ",异常:" + e.getClass().getName());}finally {// 在目标方法最终结束后:释放数据库连接log.debug("[AOP 环绕通知] 释放数据库连接,方法名:" + methodName);} return targetMethodReturnValue;
}
3.4.7. 切面优先级设置
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
- 优先级高的切面:外面
- 优先级低的切面:里面
使用 @Order 注解可以控制切面的优先级:
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
3.4.8. CGLib动态代理生效
- 如果目标类有接口,选择使用jdk动态代理
- 如果目标类没有接口,选择cglib动态代理
- 如果有接口,接口接值
- 如果没有接口,类进行接值
3.4.9. 注解实现小结
3.5. Spring AOP对获取Bean的影响理解
3.5.1. 根据类型装配 bean
-
情景一
- bean 对应的类没有实现任何接口
- 根据 bean 本身的类型获取 bean
- 测试:IOC容器中同类型的 bean 只有一个
正常获取到 IOC 容器中的那个 bean 对象 - 测试:IOC 容器中同类型的 bean 有多个
会抛出 NoUniqueBeanDefinitionException 异常,表示 IOC 容器中这个类型的 bean 有多个
- 测试:IOC容器中同类型的 bean 只有一个
-
情景二
- bean 对应的类实现了接口,这个接口也只有这一个实现类
- 测试:根据接口类型获取 bean
- 测试:根据类获取 bean
- 结论:上面两种情况其实都能够正常获取到 bean,而且是同一个对象
- bean 对应的类实现了接口,这个接口也只有这一个实现类
-
情景三
- 声明一个接口
- 接口有多个实现类
- 接口所有实现类都放入 IOC 容器
- 测试:根据接口类型获取 bean
会抛出 NoUniqueBeanDefinitionException 异常,表示 IOC 容器中这个类型的 bean 有多个 - 测试:根据类获取bean
正常
- 测试:根据接口类型获取 bean
-
情景四
- 声明一个接口
- 接口有一个实现类
- 创建一个切面类,对上面接口的实现类应用通知
- 测试:根据接口类型获取bean
正常 - 测试:根据类获取bean
无法获取
- 测试:根据接口类型获取bean
原因分析:
- 应用了切面后,真正放在IOC容器中的是代理类的对象
- 目标类并没有被放到IOC容器中,所以根据目标类的类型从IOC容器中是找不到的
-
情景五
- 声明一个类
- 创建一个切面类,对上面的类应用通知
- 测试:根据类获取 bean,能获取到
3.5.2. 使用总结
4. Spring 声明式事务
4.1. 声明式事务概念
4.1.1. 编程式事务
编程式事务是指通过编写代码的方式直接控制事务的提交和回滚。在 Java 中,通常使用事务管理器(如 Spring 中的 PlatformTransactionManager
)来实现编程式事务。
编程式事务的主要优点是灵活性高,可以按照自己的需求来控制事务的粒度、模式等等。但是,编写大量的事务控制代码容易出现问题,对代码的可读性和可维护性有一定影响。
Connection conn = ...;
try {// 开启事务:关闭事务的自动提交conn.setAutoCommit(false);// 核心操作// 业务代码// 提交事务conn.commit();
}catch(Exception e){// 回滚事务conn.rollBack();
}finally{// 释放数据库连接conn.close();
}
编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
4.1.2. 声明式事务
声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。
开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作!
使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。
区别:
- 编程式事务需要手动编写代码来管理事务
- 而声明式事务可以通过配置文件或注解来控制事务。
4.1.3. Spring事务管理器
- Spring声明式事务对应依赖
- spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
- spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
- spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
- Spring声明式事务对应事务管理器接口
现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现!
DataSourceTransactionManager类中的主要方法:
- doBegin():开启事务
- doSuspend():挂起事务
- doResume():恢复挂起的事务
- doCommit():提交事务
- doRollback():回滚事务
4.2. 基于注解的声明式事务
4.2.1. 准备工作
- 外部配置文件
wyb.url=jdbc:mysql://localhost:3306/studb
wyb.driver=com.mysql.cj.jdbc.Driver
wyb.username=root
wyb.password=root
- spring配置文件
@Configuration
@ComponentScan("com.wyb")
@PropertySource("classpath:jdbc.properties")
public class JavaConfig {@Value("${wyb.driver}")private String driver;@Value("${wyb.url}")private String url;@Value("${wyb.username}")private String username;@Value("${wyb.password}")private String password;//druid连接池@Beanpublic DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Bean//jdbcTemplatepublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}
}
- 准备dao/service层
- dao
@Repository
public class StudentDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void updateNameById(String name,Integer id){String sql = "update students set name = ? where id = ? ;";int rows = jdbcTemplate.update(sql, name, id);}public void updateAgeById(Integer age,Integer id){String sql = "update students set age = ? where id = ? ;";jdbcTemplate.update(sql,age,id);}
}
- service
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;public void changeInfo(){studentDao.updateAgeById(100,1);System.out.println("-----------");studentDao.updateNameById("test1",1);}
}
- 测试环境搭建
/*** projectName: com.wyb.test* description:*/
@SpringJUnitConfig(JavaConfig.class)
public class TxTest {@Autowiredprivate StudentService studentService;@Testpublic void testTx(){studentService.changeInfo();}
}
4.2.2. 基本事务控制
- 配置事务管理器
- 数据库相关的配置
/*** projectName: com.wyb.config* description: 数据库和连接池配置类*/
@Configuration
@ComponenScan("com.wyb")
@PropertySource(value = "classpath:jdbc.properties")
@EnableTransactionManagement
public class DataSourceConfig {/*** 实例化dataSource加入到ioc容器* @param url* @param driver* @param username* @param password* @return*/@Beanpublic DataSource dataSource(@Value("${wyb.url}")String url,@Value("${wyb.driver}")String driver,@Value("${wyb.username}")String username,@Value("${wyb.password}")String password){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}/*** 实例化JdbcTemplate对象,需要使用ioc中的DataSource* @param dataSource* @return*/@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}/*** 装配事务管理实现对象* @param dataSource* @return*/@Beanpublic TransactionManager transactionManager(DataSource dataSource){return new DataSourceTransactionManager(dataSource);}
}
- 使用声明事务注解@Transactional
/*** projectName: com.wyb.service*/
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;@Transactionalpublic void changeInfo(){studentDao.updateAgeById(100,1);System.out.println("-----------");int i = 1/0;studentDao.updateNameById("test1",1);}
}
- 测试事务效果
/*** projectName: com.wyb.test* description:*/
//@SpringJUnitConfig(locations = "classpath:application.xml")
@SpringJUnitConfig(classes = DataSourceConfig.class)
public class TxTest {@Autowiredprivate StudentService studentService;@Testpublic void testTx(){studentService.changeInfo();}
}
4.2.3. 事务属性:只读
- @Transactional注解放在类上
- 生效原则
如果一个类中每一个方法上都使用了 @Transactional 注解,那么就可以将 @Transactional 注解提取到类上。
@Transactional 注解在类级别标记,会影响到类中的每一个方法。同时,类级别标记的 @Transactional 注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上又设置了 @Transactional 注解。
对一个方法来说,离它最近的 @Transactional 注解中的事务属性设置生效。 - 用法举例
在类级别@Transactional注解中设置只读,这样类中所有的查询方法都不需要设置@Transactional注解了。因为对查询操作来说,其他属性通常不需要设置,所以使用公共设置即可。
然后在这个基础上,对增删改方法设置@Transactional注解 readOnly 属性为 false。
- 生效原则
@Service
@Transactional(readOnly = true)
public class EmpService {// 为了便于核对数据库操作结果,不要修改同一条记录@Transactional(readOnly = false)public void updateTwice(……) {……}// readOnly = true把当前事务设置为只读// @Transactional(readOnly = true)public String getEmpName(Integer empId) {……}
}
4.2.4. 事务属性:超时时间
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;/*** timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!*/@Transactional(readOnly = false, timeout = 3)public void changeInfo(){studentDao.updateAgeById(100,1);//休眠4秒,等待方法超时!try {Thread.sleep(4000);} catch (InterruptedException e) {throw new RuntimeException(e);}studentDao.updateNameById("test1",1);}
}
4.2.5. 事务属性:事务异常
- 默认情况:
默认只针对运行时异常回滚,编译时异常不回滚。
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;/*** timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!* rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!* noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!*/@Transactional(readOnly = false,timeout = 3)public void changeInfo() throws FileNotFoundException {studentDao.updateAgeById(100,1);//主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx");studentDao.updateNameById("test1", 1);}
}
- 设置回滚异常
rollbackFor属性:指定哪些异常类才会回滚,默认是 RuntimeException and Error 异常方可回滚!
/*** timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!* rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!* noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!*/
@Transactional(readOnly = false, timeout = 3, rollbackFor = Exception.class)
public void changeInfo() throws FileNotFoundException {studentDao.updateAgeById(100,1);//主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内! new FileInputStream("xxxx");studentDao.updateNameById("test1",1);
}
- 设置不回滚的异常
在默认设置和已有设置的基础上,再指定一个异常类型,碰到它不回滚。
noRollbackFor属性:指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;/*** timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!* rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!* noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!*/@Transactional(readOnly = false,timeout = 3, rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class)public void changeInfo() throws FileNotFoundException {studentDao.updateAgeById(100,1);//主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!new FileInputStream("xxxx");studentDao.updateNameById("test1",1);}
}
4.2.6. 事务属性:事务隔离级别
-
事务隔离级别
数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:- 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
- 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
- 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
- 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。
-
事务隔离级别设置
package com.wyb.service;
import com.wyb.dao.StudentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/*** projectName: com.wyb.service*/
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;/*** timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!* rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!* noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!* isolation = 设置事务的隔离级别,mysql默认是repeatable read!*/@Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class,noRollbackFor = FileNotFoundException.class,isolation = Isolation.REPEATABLE_READ)public void changeInfo() throws FileNotFoundException {studentDao.updateAgeById(100,1);//主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!new FileInputStream("xxxx");studentDao.updateNameById("test1",1);}
}
4.2.7. 事务属性:事务传播行为
@Transactional
public void MethodA(){// ...MethodB();// ...
}
//在被调用的子方法中设置传播行为,代表如何处理调用的事务! 是加入,还是新事务等!
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodB(){// ...
}
- propagation属性
@Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是:Propagation propagation() default Propagation.REQUIRED;
propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供:
名称 | 含义 |
---|---|
REQUIRED 默认值 | 如果父方法有事务,就加入,如果没有就新建自己独立! |
REQUIRES_NEW | 不管父方法是否有事务,我都新建事务,都是独立的! |
- 测试
- 声明两个业务方法
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;/*** timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!* rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!* noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!* isolation = 设置事务的隔离级别,mysql默认是repeatable read!*/@Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class,noRollbackFor = FileNotFoundException.class,isolation = Isolation.REPEATABLE_READ)public void changeInfo() throws FileNotFoundException {studentDao.updateAgeById(100,1);//主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!new FileInputStream("xxxx");studentDao.updateNameById("test1",1);}/*** 声明两个独立修改数据库的事务业务方法*/@Transactional(propagation = Propagation.REQUIRED)public void changeAge(){studentDao.updateAgeById(99,1);}@Transactional(propagation = Propagation.REQUIRED)public void changeName(){studentDao.updateNameById("test2",1);int i = 1/0;}
}
- 声明一个整合业务方法
@Service
public class TopService {@Autowiredprivate StudentService studentService;@Transactionalpublic void topService(){studentService.changeAge();studentService.changeName();}
}
- 添加传播行为测试
@SpringJUnitConfig(classes = AppConfig.class)
public class TxTest {@Autowiredprivate StudentService studentService;@Autowiredprivate TopService topService;@Testpublic void testTx() throws FileNotFoundException {topService.topService();}
}
注意:
在同一个类中,对于@Transactional注解的方法调用,事务传播行为不会生效。这是因为Spring框架中使用代理模式实现了事务机制,在同一个类中的方法调用并不经过代理,而是通过对象的方法调用,因此@Transactional注解的设置不会被代理捕获,也就不会产生任何事务传播行为的效果。
5. Spring核心掌握总结
核心点 | 掌握目标 |
spring框架理解 | spring家族和spring framework框架 |
spring核心功能 | ioc/di , aop , tx |
spring ioc / di | 组件管理、ioc容器、ioc/di , 三种配置方式 |
spring aop | aop和aop框架和代理技术、基于注解的aop配置 |
spring tx | 声明式和编程式事务、动态事务管理器、事务注解、属性 |