本文将详细介绍Spring5注解驱动开发的细节。其中Spring AOP的源码分析见【Spring】Spring5 AOP源码分析。
IoC 容器
导入maven依赖
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.5</version> </dependency>
|
IoC容器部分案例汇总:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @PropertySource(value = {"classpath:/person.properties"}) @Conditional({WindowsCondition.class}) @ComponentScan(value = "com.zhao", excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) }) @ComponentScans( value = { @ComponentScan(value = "com.zhao", excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) }) , @ComponentScan(value = "com.zhao", includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Repository.class}), @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}), @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) }, useDefaultFilters = false) } ) @Import({User.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) @Configuration(proxyBeanMethods = true) public class SpringConfig { @Scope("singleton") @Lazy @Conditional({WindowsCondition.class}) @Bean("user") public User user(initMethod = "init", destroyMethod = "destroy") { return new User("zhangsan", 18); } @Bean public UserService userService(@Autowired UserDao userDao) { return new UserService(userDao); } @Bean public StudentFactoryBean studentFactoryBean(){ return new StudentFactoryBean(); } }
|
使用AnnotationConfigApplicationContext
类获取IoC容器中的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Test public void test() { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String name : beanDefinitionNames) { System.out.println(name); } User user = context.getBean(User.class); System.out.println(user);
Object studentFactoryBean = context.getBean("studentFactoryBean"); System.out.println("bean的类型: " + studentFactoryBean.getClass()); }
|
@Configuration
在一个类上添加@Configuration注解,则该类就将作为Spring的配置类,可在其中注册组件。
1 2 3 4 5 6 7
| @Configuration() public class SpringConfig { @Bean("user") public User user(initMethod = "init", destroyMethod = "destroy") { return new User("zhangsan", 18); } }
|
组件注册
在Spring IoC容器中注册组件有三种方法
@Bean
:编写相应方法返回组件(可用于导入的第三方包中的组件)
@ComponentScan
:包扫描+组件标注注解
@Import
:调用无参构造快速导入组件(可用于导入的第三方包中的组件)
@Bean:在 SpringConfig 类里编写相应方法返回组件
方法返回值类型为组件类型,方法名为默认组件id,也可以在@Bean()
中自定义组件id
1 2 3 4 5 6 7
| @Configuration public class SpringConfig { @Bean("user") public User user() { return new User("zhangsan", 18); } }
|
@ComponentScan:在 SpringConfig 类上添加包扫描
指定排除哪些组件
1 2 3
| @ComponentScan(value = "com.zhao", excludeFilters = { @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}) })
|
指定包含哪些组件(注意:需要设置useDefaultFilters = false
)
1 2 3
| @ComponentScan(value="com.zhao", includeFilters={ @Filter(type= FilterType.ANNOTATION,classes={Controller.class, Service.class}) }, useDefaultFilters = false)
|
若想指定多个@ComponentScan
,可以使用@ComponentScans
:
1 2 3 4 5 6 7 8 9 10 11 12
| @ComponentScans( value = { @ComponentScan(value = "com.zhao", excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) }) , @ComponentScan(value = "com.zhao", includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Repository.class}), @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}), @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) }, useDefaultFilters = false) } )
|
@Filter:组件注册过滤器
@Filter
可以指定的类型:
FilterType.ANNOTATION
:过滤指定的注解
FilterType.ASSIGNABLE_TYPE
:过滤指定的类
FilterType.CUSTOM
:自定义过滤器类,指定包下的所有类都会经过该过滤器类,并判断是否需要被过滤
FilterType.CUSTOM
需要传入一个自定义过滤器类MyTypeFilter
,其需要实现TypeFilter
接口的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class MyTypeFilter implements TypeFilter {
@Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); ClassMetadata classMetadata = metadataReader.getClassMetadata(); Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName(); System.out.println("-----> " + className);
if (className.contains("er")) { return true; } return false; } }
|
@Import:快速导入组件
@Import(xxx.class
):注册xxx类,id默认是全类名
@Import(ImportSelector)
:实现ImportSelector
接口,在其方法中返回需要注册的组件全类名数组
@Import(ImportBeanDefinitionRegistrar)
:实现ImportBeanDefinitionRegistrar
接口,在其方法中调用BeanDefinitionRegistry
类对象的registerBeanDefinition()
方法手动注册组件(详细源码见【Spring】Spring5源码)
1 2 3
| @Import({User.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) public class SpringConfig { }
|
ImportSelector
接口实现类:MyImportSelector
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class MyImportSelector implements ImportSelector {
@Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"com.zhao.bean.Student", "com.zhao.bean.User"}; } }
|
ImportBeanDefinitionRegistrar
接口实现类MyImportBeanDefinitionRegistrar
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){ boolean definition = registry.containsBeanDefinition("student"); if (!definition) { RootBeanDefinition beanDefinition = new RootBeanDefinition(Student.class); registry.registerBeanDefinition("student", beanDefinition); } } }
|
@Scope:设置组件作用域
@Scope
常用的两种作用域:
- singleton:单实例(默认值)。IoC容器启动时就会调用方法创建对象到IoC容器中,之后获取就直接从容器中拿(map.get())。关闭工厂 时,所有的对象都会销毁。
- prototype:多实例。IoC容器启动时不会调用方法创建对象到IoC容器中,每次获取的时候才会调用方法创建对象。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收。
1 2 3 4 5
| @Scope("prototype") @Bean("user") public User user() { return new User("zhangsan", 18); }
|
@Lazy:懒加载
单实例bean默认在容器启动时就创建对象,而使用了@Lazy后,在容器启动时并不会创建对象,而会等到第一次获取bean时才创建对象并初始化。
1 2 3 4 5
| @Lazy @Bean("user") public User user() { return new User("zhangsan", 18); }
|
@Conditional:按照一定条件进行判断,满足条件的给容器中注册 bean
在方法上添加 @Conditional
1 2 3 4 5 6 7 8 9 10
| @Configuration public class SpringConfig {
@Conditional({WindowsCondition.class}) @Bean("user") public User user() { return new User("zhangsan", 18); }
}
|
需要实现Condition接口的matches(ConditionContext context, AnnotatedTypeMetadata metadata)方法。当前组件只有满足该条件才会被注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class WindowsCondition implements Condition {
@Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ClassLoader classLoader = context.getClassLoader(); Environment environment = context.getEnvironment(); BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name"); return property.contains("Windows"); } }
|
在配置类上添加@Conditional
满足条件,这个类中配置的所有bean注册才能生效
1 2 3 4 5 6 7 8
| @Conditional({WindowsCondition.class}) @Configuration public class SpringConfig { @Bean("user") public User user() { return new User("zhangsan", 18); } }
|
使用 FactoryBean 注册组件
在Spring
和其他框架整合时,大量使用FactoryBean
注册组件。
- 创建
FactoryBean
接口的实现类StudentFactoryBean
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class StudentFactoryBean implements FactoryBean<Student> {
@Override public Student getObject() throws Exception { return new Student(); }
@Override public Class<?> getObjectType() { return Student.class; }
@Override public boolean isSingleton() { return false; } }
|
- 在配置类中声明该工厂类对象
1 2 3 4 5 6 7 8
| @Configuration public class SpringConfig {
@Bean public StudentFactoryBean studentFactoryBean(){ return new StudentFactoryBean(); } }
|
- 获取
FactoryBean
调用getObject()
创建的对象(并非FactoryBean
组件,而是其内生产的组件)
1 2 3 4
| AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Object studentFactoryBean = context.getBean("studentFactoryBean"); System.out.println("bean的类型: " + studentFactoryBean.getClass());
|
若想获取FactoryBean
组件,需要:
1
| Object studentFactoryBean = context.getBean("&studentFactoryBean");
|
组件注册小结
组件注册相关注解使用汇总案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @PropertySource(value = {"classpath:/person.properties"}) @Conditional({WindowsCondition.class}) @Configuration @ComponentScan(value = "com.zhao", excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) }) @ComponentScans( value = { @ComponentScan(value = "com.zhao", excludeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) }) , @ComponentScan(value = "com.zhao", includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = {Repository.class}), @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}), @Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) }, useDefaultFilters = false) } ) @Import({User.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class}) public class SpringConfig { @Scope("singleton") @Lazy @Conditional({WindowsCondition.class}) @Bean("user") public User user() { return new User("zhangsan", 18); } @Bean public StudentFactoryBean studentFactoryBean(){ return new StudentFactoryBean(); } }
|
其中在@Import
、@Filter
、@Conditional
注解中指定的类对象均实现了特定的接口,并会在容器加载时调用这些接口实现类的指定方法,从而使得不同的注解实现不同的效果(注册bean、过滤、判断条件等)。
生命周期
生命周期 :从对象创建到对象销毁的过程。bean 的生命周期有七步 (正常生命周期为五步,而配置后置处理器后为七步)
- 通过构造器创建 bean 实例(无参数构造)
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 把 bean 实例传递给后置处理器的方法
postProcessBeforeInitialization
- 调用 bean 的初始化的方法(需要在配置初始化方法
init-method
)
- 把 bean 实例传递给后置处理器的方法
postProcessAfterInitialization
- bean 可以使用了(对象获取到了)
- 当容器关闭时候,调用 bean 的销毁方法(需要配置销毁的方法
destroy-method
)
整个生命周期中,3和5由自定义的后置处理器BeanPostProcessor实现;4和7由自定义初始化和销毁方法实现;其余阶段默认由Spring容器完成。
大致流程:
- constructor构造器执行
- set方法执行
- BeanPostProcessor.postProcessBeforeInitialization方法执行
- init-method执行
- BeanPostProcessor.postProcessAfterInitialization方法执行
- destroy-method执行
补充:在开启AOP功能后,容器中会注册一个特殊的后置处理器:AnnotationAwareAspectJAutoProxyCreator,该处理器会在每个bean实例化的前后进行拦截,将带有切入点的bean包装成代理对象加入到容器中,该过程在上述描述的1-6步骤前后执行。
自定义初始化和销毁方法
bean的生命周期由容器管理,可以自定义初始化和销毁方法:
- 方式一:通过
@Bean
注解指定initMethod
和destroyMethod
- 方式二:通过让bean实现
InitializingBean
定义初始化逻辑和DisposableBean
定义销毁逻辑
- 方式三:使用JSR250中
@PostConstruct
和@PreDestroy
注解
初始化方法在bean对象创建好并赋值后被调用,销毁方法在对象被销毁前被调用。
销毁时:
- 单实例:容器关闭时销毁
- 多实例:容器不会管理这个bean,容器不会调用销毁方法,需要手动调用
方式一:@Bean 注解指定 initMethod 和 destroyMethod
1 2 3 4 5 6 7 8
| @Configuration public class SpringConfigOfLifeCycle {
@Bean(initMethod = "init", destroyMethod = "destroy") public User user(){ return new User(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component("user") public class User { private String name; private int age;
public User() { System.out.println("======> User constructor ...."); }
public void init(){ System.out.println("======> User init ...."); }
public void destroy(){ System.out.println("======> User destory ...."); }
}
|
方式二:bean 实现 InitializingBean 和 DisposableBean 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Cat implements InitializingBean, DisposableBean {
public Cat(){ System.out.println("======> Cat constructor ...."); }
@Override public void destroy() throws Exception { System.out.println("======> Cat destory ...."); }
@Override public void afterPropertiesSet() throws Exception { System.out.println("======> Cat afterPropertiesSet ...."); } }
|
1 2 3 4
| @ComponentScan("com.zhao.bean") @Configuration public class SpringConfigOfLifeCycle { }
|
方式三:使用 JSR250 中 @PostConstruct 和 @PreDestroy 注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Dog { public Dog(){ System.out.println("=====> Dog constructor ...."); }
@PostConstruct public void init(){ System.out.println("=====> Dog init ...."); } @PreDestroy public void destroy(){ System.out.println("=====> Dog init ...."); } }
|
@PostConstruct
和@PreDestroy
的注解信息会被InitDestroyAnnotationBeanPostProcessor
获取到,并据此执行相应的初始化和销毁方法。
后置处理器 BeanPostProcessor
BeanPostProcessor
接口用于在bean的初始化前后进行一些处理工作:
postProcessBeforeInitialization()
:在初始化方法之前工作
postProcessAfterInitialization()
:在初始化方法之后工作
自定义后置处理器实现BeanPostProcessor
的方法,并将其注册到容器中。之后所有的bean对象在初始化方法前后都会进入该类的方法中执行相应处理。即使一些bean没有自定义初始化方法,也不会影响后置处理器方法的执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("====> postProcessBeforeInitialization ... " + beanName + ": " + bean);
return bean; }
@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("====> postProcessAfterInitialization ... " + beanName + ": " + bean);
return bean; } }
|
BeanPostProcessor 执行流程源码分析
- 容器调用bean的构造方法创建对象
populateBean()
:容器调用bean的set方法为bean对象的属性赋值(红色框)
initializeBean()
:容器为bean做初始化操作(黄色框)
方法栈位置:AbstractAutowireCapableBeanFactory.java
======> 进入黄色框的initializeBean()
方法:
- 执行
invokeAwareMethods()
方法(绿色框)
- 执行
applyBeanPostProcessorsBeforeInitialization()
方法(红色框)
- 执行
invokeInitMethods()
方法完成初始化(黄色框)
- 执行
applyBeanPostProcessorsAfterInitialization()
方法(红色框)
======> 进入红色框的applyBeanPostProcessorsBeforeInitialization()
方法:
遍历得到容器中所有的BeanPostProcessor
,并一执行BeanPostProcessor
的postProcessBeforeInitialization()
方法,将bean对象逐一经过每个BeanPostProcessor
处理器。一旦返回null,跳出for循环后续不再执行。
Spring 底层对 BeanPostProcessor 的应用
使用BeanPostProcessor
接口的实现类,可以实现:bean赋值、注入其他组件、@Autowired
属性注入、生命周期注解功能等。
BeanPostProcessor
接口的实现类
ApplicationContextAwareProcessor
ApplicationContextAwareProcessor
是BeanPostProcessor
的一个实现类。其原理同上述自定义的MyBeanPostProcessor
类一样,都会在如下for循环中被获取到,并执行其实现的postProcessBeforeInitialization()
方法。
ApplicationContextAwareProcessor
类实现的postProcessBeforeInitialization()
方法如下:
每个bean在进入该方法后都会判断是否符合黄色框中接口的实现类,若符合,则会执行invokeAwareInterfaces()
方法,根据其实现的接口类型调用相应的方法设置相应的容器上下文属性。
AutowiredAnnotationBeanPostProcessor
实现@Autowired自动注入功能。在对象创建完后,获取被@Autowired
注解修饰的属性,从而实现属性注入。
BeanValidationPostProcessor
BeanValidationPostProcessor
常用在JavaWeb中用于数据校验。
其类实现的postProcessBeforeInitialization()
方法如下:
在其doValidate()
方法中提供了数据校验的功能。因此可以在bean初始化前后完成校验工作。
InitDestroyAnnotationBeanPostProcessor
InitDestroyAnnotationBeanPostProcessor
用于获取bean对象的@PostConstruct
和@PreDestroy
注解信息,并据此执行相应的初始化和销毁方法。
BeanPostProcessor 总结
每个bean对象在被注册到容器的过程中,会在执行invokeInitMethods()
方法初始化bean之前,执行用户自定义注册的BeanPostProcessor
实现类和容器默认注册的BeanPostProcessor
实现类的postProcessBeforeInitialization()
方法。不同实现类的postProcessBeforeInitialization()
方法功能不同,可以自定义地为其设置不同的功能,包括输出日志、添加上下文容器、添加环境属性、添加资源解析器等。
若想让某些bean获得上述功能,只需要该类实现BeanPostProcessor
接口的postProcessBeforeInitialization()
方法即可。
使用BeanPostProcessor
接口的实现类,可以实现:bean赋值、注入其他组件、@Autowired
属性注入、生命周期注解功能等。
完整的生命周期
组件属性赋值
@Value
@Value注解可以赋值的类型:
- 基本数据类型值
- SpEL:#{},可以在其中进行数值运算
- ${}取出配置文件中的值(在运行环境变量里的值),读取配置文件时需要在配置类上添加注解
@PropertySource
1 2 3 4 5 6 7 8 9
| @PropertySource(value = {"classpath:/person.properties"}) @Configuration public class SpringConfigOfPropertyValue {
@Bean public Person person(){ return new Person(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component public class Person {
@Value("张三") private String name;
@Value("#{20-2}") private Integer age;
@Value("${person.phone}") private String phone;
}
|
组件自动装配
Spring利用依赖注入DI,完成对IoC容器中各个组件的依赖关系赋值。
Spring提供的自动装配注解:
@Autowired
:根据属性类型进行自动装配
@Qualifier
:根据属性名称进行自动装配
@Primary
:根据设置为默认首选的bean进行自动装配
Java规范提供的注解:
@Resource
:可以根据类型注入,也可以根据名称注入,但不支持@Primary
和@Autowired(required=false)
@Inject
:功能和@Autowired
一样,没有required=false
功能
@Autowired:按照属性类型自动装配
如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找,若找不到指定名称的组件,则程序报错,此时可以添加required=false
属性使得当前组件即使找不到程序也不会报错。
1 2 3 4 5 6 7 8 9 10 11 12
| @Service public class UserService {
@Autowired(required=false) private UserDao userDao; public void add() { System.out.println("service add......."); userDao.add(); } }
|
@Autowired
也可以修饰方法、有参构造器、方法参数上。
有参构造器说明:若组件只有一个有参构造器,这个有参构造器的@Autowired
可以省略,参数位置的组件仍然能从容器中获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Service public class UserService { private UserDao userDao; @Autowired public UserService(UserDao userDao){ this.userDao = userDao; } @Autowired public void setUserDao(UserDao userDao){ this.userDao = userDao; } public UserService(@Autowired UserDao userDao){ this.userDao = userDao; } public void setUserDao(@Autowired UserDao userDao){ this.userDao = userDao; } }
|
在配置类中使用@Autowired
修饰方法参数(可以省略)
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration class SpringConfig{ @Bean public UserService userService(@Autowired UserDao userDao){ return new UserService(userDao); } @Bean public UserService userService(UserDao userDao){ return new UserService(userDao); } }
|
@Qualifier:按照属性名称自动装配
@Qualifier
:根据对象名称进行装配,可以和@Autowired
一起使用(目的在于区别同一接口下有多个实现类)
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Service public class UserService {
@Autowired @Qualifier(value = "userDao1") private UserDao userDao; public void add() { System.out.println("service add......."); userDao.add(); } }
|
@Primary:根据设置为默认首选的 bean 进行自动装配
被@Primary
注解修饰的bean会在自动装配时被设置为首选项。但也可以继续使用@Qualifier
指定需要装配的bean。
1 2 3 4 5 6 7 8 9
| @Configuration public class SpringConfigOfPropertyValue {
@Primary() @Bean("userDao1") public UserDao userDao(){ return new UserDao(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| @Service public class UserService { @Autowired private UserDao userDao; public void add() { System.out.println("service add......."); userDao.add(); } }
|
@Profile:根据环境动态注册组件
@Profile
可以指定组件在哪个运行环境下才能被注册到容器中,若不指定则任何环境都能注册这个组件。可用于在开发环境、测试环境和生产环境中动态地注册组件。
加了环境标识的组件,只有当这个环境被激活时才能注册到容器中。默认环境是default
。没有添加@Profile
的组件在任何环境下都会被注册。
@Profile
既可以修饰方法,又可以修饰配置类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Profile("default") @Configuration public class SpringConfigOfPropertyValue {
@Profile("test") @Bean() public DataSource dataSourceTest(){ } @Profile("develop") @Bean() public DataSource dataSourceTest(){ } }
|
激活环境的方式:
- 在IDEA中设置
VM arguments:-Dspring.profiles.active=test
- 在代码中手动激活环境
1 2 3 4
| AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.getEnvironment().setActiveProfiles("test", "dev"); context.register(SpringConfigOfPropertyValue.class); context.refresh();
|
AOP
AOP 基本概念
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Spring AOP的源码分析见【Spring】Spring5 AOP源码分析。
- 面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得 业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率;
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。
- 使用登录例子说明 AOP:
AOP 底层原理
Spring AOP的底层原理分析见【Spring】Spring5 AOP源码分析。
AOP 底层使用动态代理 ,动态代理有两种情况:
第一种 有接口情况,使用JDK 动态代理 ;创建接口实现类代理对象,增强类的方法
第二种 没有接口情况,使用CGLIB 动态代理创建子类的代理对象,增强类的方法(该方法不需要实现接口,由CGLIB创建代理对象)
AOP 术语
- 连接点(JointPoint):类里面哪些方法可以被增强,这些方法称为连接点,每一个方法的每一个位置(开始位置,返回位置,异常位置等)都是一个连接点。
- 切入点(PointCut):切面通知执行的“地点”的定义,实际被真正增强的方法称为切入点。
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ….
- 切面(Aspect):横切关注点被模块化的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。包含前置通知,后置通知,环绕通知 ,异常通知和最终通知。
- 目标(Target):被通知的对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
AOP 操作
采用动态代理的设计模式,在程序运行期间动态地将某段代码切入到指定方法(切入点)指定位置进行运行的编程方式。
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.12.RELEASE</version> </dependency>
|
定义一个业务逻辑类(MathCalculator
)。试图在业务逻辑运行的时候将日志进行打印(方法之前,方法运行结束,方法出现异常等)
1 2 3 4 5
| public class MathCalculator { public int div(int i, int j){ return i/j; } }
|
定义一个日志切面类(LogAspects
),在切面类里需要动态感知MathCalculator.div()
方法运行到什么阶段并执行相应通知方法。通知方法:
- 前置通知(
@Before
):在切入点(PointCut
)运行之前运行
- 后置通知(
@After
):在切入点运行结束之后运行(无论方法是否正常结束)
- 返回通知(
@AfterReturning
):在切入点正常返回之后运行(异常不执行)
- 异常通知(
@AfterThrowing
):在切入点出现异常之后运行
- 环绕通知(
@Around
):动态代理的方式手动推进切入点运行(joinPoint.procced()
),是最底层的通知,其可以实现上述四个通知效果
通知方法的执行顺序:(Spring 5的顺序与Spring 4有所不同)
- 环绕通知(
@Around
)joinPoint.procced()
方法之前的代码
- 前置通知(
@Before
)
- 业务代码
- 返回通知(
@AfterReturning
)/ 若有异常,此时执行异常通知(@AfterThrowing
)
- 后置通知(
@After
)
- 环绕通知(
@Around
)joinPoint.procced()
方法以及其之后的代码
多个切面的情况下,先执行前置通知的后执行返回通知和后置通知,后执行前置通知的先执行返回通知和后置通知。类似方法栈先进后出。执行顺序由切面类的字母顺序排序,也可以通过@Order(1)
设置优先级
AOP 使用案例
配置类需要添加@EnableAspectJAutoProxy以开启注解版的AOP自动代理。整个AOP就是从@EnableAspectJAutoProxy注解开始执行的。(Spring中有很多的@EnableXXX
注解,其作用是代替xml文件中的一些配置开启某些功能)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @EnableAspectJAutoProxy @Configuration public class SpringConfigAOP { @Bean public MathCalculator calculator(){ return new MathCalculator(); }
@Bean public LogsAspects logsAspects(){ return new LogsAspects(); } }
|
切面类LogsAspects
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| @Aspect @Order(1) public class LogsAspects {
@Pointcut("execution(* com.zhao.aop.MathCalculator.*(..))") public void pointCut(){ }
@Before("execution(int com.zhao.aop.MathCalculator.div(int, int))") public void logStart(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); System.out.println("前置通知@Before.... "); }
@After("execution(* com.zhao.aop.MathCalculator.*(..))") public void logEnd(){ System.out.println("后置通知@After...."); }
@AfterReturning(value = "pointCut()", returning = "result") public void logReturn(Object result){ System.out.println("返回通知@AfterReturning.... "); }
@AfterThrowing(value = "pointCut()", throwing = "exception") public void logException(Exception exception){ System.out.println("异常通知@AfterThrowing.... "); }
@Around("pointCut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("签名: " + proceedingJoinPoint.getSignature()); Object[] args = proceedingJoinPoint.getArgs();
try { System.out.println("【环绕前置通知】.... "); Object result = proceedingJoinPoint.proceed(args); System.out.println("【环绕返回通知】.... "); } catch (Exception exception){ System.out.println("【环绕异常通知】.... "); } finally { System.out.println("【环绕后置通知】.... "); }
return result; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class AOPTest { AnnotationConfigApplicationContext context;
@Test public void testOfAop() { context = new AnnotationConfigApplicationContext(SpringConfigAOP.class); System.out.println("容器创建完成....");
MathCalculator bean = context.getBean(MathCalculator.class); bean.div(1, 1);
context.close(); } }
|
控制台打印:
1 2 3 4 5 6 7 8 9 10
| 签名: int com.zhao.aop.MathCalculator.div(int,int) 【环绕前置通知】.... 前置通知@Before.... div方法执行... 返回通知@AfterReturning.... 后置通知@After.... 【环绕返回通知】.... 【环绕后置通知】...
Process finished with exit code 0
|
声明式事务
使用案例
- 导入相关依赖:数据源、数据库驱动、SpringJDBC模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.12.RELEASE</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
<dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency>
|
-
配置数据源、JdbcTemplate操作数据库(Spring提供的简化数据库操作的工具)
-
添加 @EnableTransactionManagement 注解开启基于注解的事务管理功能
-
配置事务管理器来控制事务(事务管理器操作数据源,进行事务管理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @EnableTransactionManagement @Configuration public class TxConfig {
@Bean public DataSource dataSource() throws PropertyVetoException { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser("root"); dataSource.setPassword("123456"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); return dataSource; }
@Bean public JdbcTemplate jdbcTemplate() throws PropertyVetoException { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource()); return jdbcTemplate; }
@Bean public PlatformTransactionManager transactionManager() throws PropertyVetoException { return new DataSourceTransactionManager(dataSource()); } }
|
- 在类或方法上添加 @Transactional() 注解表明该方法需要添加事务
- 添加到类上,这个类里面所有的方法都添加事务
- 添加到方法上,只有这个方法添加事务
1 2 3 4
| @Transactional(propagation = Propagation.REQUIRED) public void add(){ update(); }
|
事务细节参数
read-only
:设置事务为只读事务,不需要增删改操作。可以提高查询速度。
timeout
:超时,事务超出指定执行时长后自动终止并回滚。
isolation
:设置隔离级别
运行时异常(非检查异常)发生时默认回滚,编译时异常(检查异常)默认不回滚
rollBackFor
:可以让原来默认不回滚的异常回滚
noRollBackFor
:可以让原来默认回滚的异常不回滚
声明式事务传播特性
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring支持7种事务传播行为:
propagation_required
(需要事务,有就加入,没有就新建):如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。(如果设置为required,则事务的其他属性继承于大事务)好男人。
propagation_supports
(支持事务,有就加入,没有就非事务):支持当前事务,如果没有当前事务,就以非事务方法执行。懒男人
propagation_mandatory
(强制使用当前事务,有就加入,没有就抛异常):使用当前事务,如果没有当前事务,就抛出异常。
上述三种类型都支持当前事务,当前如果有事务就加入。
propagation_required_new
(必须新建事务,当前有就抛挂起):新建事务,如果当前存在事务,把当前事务挂起。挑剔男
propagation_not_supported
(不支持事务,当前有就挂起):以非事务方式执行操作,如果当前存在事务,就把当前事务挂起(挂起指自己新建一个数据库连接,不再使用之前的数据库连接,在代码中体现为两个方法的connection不相同,详细介绍见上文)。减肥男
propagation_never
(强制非事务,当前有就抛异常):以非事务方式执行操作,如果当前事务存在则抛出异常IllegalTransactionStateException
,该方法内的代码无法运行。神经病
上述三种类型都不支持当前事务,当前如果有事务,要么挂起,要么抛异常。
propagation_nested
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED
,它适合于绝大多数的情况。
假设 ServiveX#methodX()
都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3()
,那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Service public class AccountServiceImpl implements AccountService { @Autowired JdbcTemplate jdbcTemplate;
@Override @Transactional(propagation = Propagation.REQUIRED_NEW) public void addAccount(String name, int initMoney) { String accountId = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date()); jdbc.Template.update("INSERT INTO `account` (accountName, user, money) VALUES(?,?,?)", accountId, name, initMoney); int i = 1 / 0; } }
|
1 2 3 4 5
| @Transactional(propagation = Propagation.REQUIRED) public void createUser(String name) { jdbc.Template.update("INSERT INTO `user` (name) VALUES(?)", name); accountService.addAccount(name, 10000); }
|
使用过上述案例进行实验,1代表插入成功,2代表插入失败:
- 场景1:两个方法都没事务,都是普通方法,因此就算抛出异常,也不影响插入数据
- 场景2:
createUser()
没有事务,其仍然能插入数据;addAccount()
有事务,其出现异常不能成功插入数据
- 场景3:
createUser()
有事务,出现异常后其不能插入数据;addAccount()
没有声明事务,但其被createUser()调用,仍然会被事务包裹,出现异常不能成功插入数据。若某个方法包含事务,其调用的其他方法也会包含事务
- 场景4:
addAccount()
将createUser()
的事务挂起,挂起指自己新建一个数据库连接,不再使用之前的数据库连接,在代码中体现为两个方法的connection不相同,详细介绍见上文。因此addAccount()
插入成功(因为没有事务,异常也能插入),createUser()
插入失败(因为addAccount()
抛出了异常,被重新恢复的事务所捕获从而插入失败)
- 场景5:
addAccount()
不支持事务,直接抛出IllegalTransactionStateException
。所以直接无法运行该方法内插入的语句,所以插入失败;createUser()
因为有事务,所以捕获到addAccount()
抛出的异常后回滚,插入失败
- 场景6:见下文场景分析
场景6详细分析:假设Spring IoC中有组件AccountServiceImpl
,该组件中的addAccount()
方法被@Transactional
注解修饰,代表该方法将开启事务。
Spring容器启动时将使用事务后置处理器AutoProxyRegistrar会为该组件创建一个动态代理对象accountProxy
,该对象将被注入到容器中,其他程序在调用getBean()
获取该类的对象时,将获取到该类的动态代理对象,而非原始对象。此时在调用该代理对象accountProxy
的addAccount()
时,将有事务包裹。
而若不调用该代理对象的addAccount()
,而是将该方法直接写在本类中,直接调用本类里的该方法,则不会交由Spring事务管理器拦截,此时的方法和普通方法一样。
结论:只有Spring事务代理对象的方法才能被事务拦截器所拦截。直接调用方法无法被拦截(即使该方法被@Transactional
注解修饰)。
声明式事务原理
@EnableTransactionManagement 注解向容器中添加AutoProxyRegistrar和ProxyTransactionManagementConfiguration组件,二者作用分别为:
- AutoProxyRegistrar:类似于AOP中的AspectJAutoProxyRegistrar,用于向容器中注册InfrastructureAdvisorAutoProxyCreator组件(类似于AOP里的自动代理器,一种后置处理器)来为普通组件进行代理包装,创建代理对象
- ProxyTransactionManagementConfiguration:用于注册事务增强器,该增强器内设置有事务拦截器,将在代理对象执行目标方法时进行拦截,并调用其
invoke()
方法,由事务管理器控制事务的提交与回滚。
Spring事务原理与AOP原理十分相似,都包含有后置处理器和拦截器思想,在组件创建后包装出代理对象、在代理对象执行目标方法时进行拦截,使用事务管理器控制事务的提交与回滚。
详细的源码分析见文章【Spring】Spring5 事务源码分析。