【Spring】Spring5 注解驱动开发

image-20210629094903500

本文将详细介绍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}) // 满足当前条件,这个类中配置的所有bean注册才能生效
@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") // prototype:多实例的,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() {
// 加载配置类 SpringConfig
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()); //返回的是Student类对象
}

@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 {

/**
* 自定义Filter,每个自定义组件的信息都会被该方法获取,再判断是否过滤掉该组件
* @param metadataReader:读取到的当前正在扫描的类的信息
* @param metadataReaderFactory:可以获取到其他任何类信息的工厂
* @return
* @throws IOException
*/
@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:快速导入组件

  1. @Import(xxx.class):注册xxx类,id默认是全类名
  2. @Import(ImportSelector):实现ImportSelector接口,在其方法中返回需要注册的组件全类名数组
  3. @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 {

/**
* 该方法返回的全类名将被注册到容器中
* @param importingClassMetadata:当前标注Import注解的类(即SpringConfig类)的所有注解信息
* @return 返回值就是要导入到容器中的组件全类名,不能返回null
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 返回要导入到容器中的组件全类名,方法不能返回null
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 {

/**
* 实现registerBeanDefinitions接口以手动注册bean
* @param importingClassMetadata:当前类的注解信息
* @param registry:BeanDefinition注册类
* 把所有要添加到容器中的bean调用registry.registerBeanDefinition()手动注册类
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry){
boolean definition = registry.containsBeanDefinition("student");
if (!definition) {
// 指定Bean定义信息(bean类型,作用域等)
RootBeanDefinition beanDefinition = new RootBeanDefinition(Student.class);
// 注册一个bean,指定bean名
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 {

/**
* 根据条件判断是否注册组件
* @param context:判断条件能否使用的上下文(环境)
* @param metadata:标记了@Condition注解的注释信息
* @return true: 注册 false:不注册
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取到ioc使用的工厂
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 获取类加载器
ClassLoader classLoader = context.getClassLoader();
// 获取当前环境信息
Environment environment = context.getEnvironment();
// 获取到bean定义的注册类,所有bean的定义都在这里注册
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注册组件。

  1. 创建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> {

// 返回一个Student对象,该对象会添加到容器中
@Override
public Student getObject() throws Exception {
return new Student();
}

@Override
public Class<?> getObjectType() {
return Student.class;
}

@Override
public boolean isSingleton() {
return false;
}
}
  1. 在配置类中声明该工厂类对象
1
2
3
4
5
6
7
8
@Configuration
public class SpringConfig {

@Bean
public StudentFactoryBean studentFactoryBean(){
return new StudentFactoryBean();
}
}
  1. 获取FactoryBean调用getObject()创建的对象(并非FactoryBean组件,而是其内生产的组件)
1
2
3
4
AnnotationConfigApplicationContext  context = new AnnotationConfigApplicationContext(SpringConfig.class);

Object studentFactoryBean = context.getBean("studentFactoryBean");
System.out.println("bean的类型: " + studentFactoryBean.getClass()); //返回的是Student类对象

若想获取FactoryBean组件,需要:

1
Object studentFactoryBean = context.getBean("&studentFactoryBean");

组件注册小结

组件注册相关注解使用汇总案例:

image-20210629153052661

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}) // 满足当前条件,这个类中配置的所有bean注册才能生效
@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") // prototype:多实例的,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 的生命周期有七步 (正常生命周期为五步,而配置后置处理器后为七步)

  1. 通过构造器创建 bean 实例(无参数构造)
  2. 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
  3. 把 bean 实例传递给后置处理器的方法 postProcessBeforeInitialization
  4. 调用 bean 的初始化的方法(需要在配置初始化方法init-method
  5. 把 bean 实例传递给后置处理器的方法 postProcessAfterInitialization
  6. bean 可以使用了(对象获取到了)
  7. 当容器关闭时候,调用 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注解指定initMethoddestroyMethod
  • 方式二:通过让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获取到,并据此执行相应的初始化和销毁方法。

image-20210629194708129

后置处理器 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 {
// bean:当前被处理的bean对象
System.out.println("====> postProcessBeforeInitialization ... " + beanName + ": " + bean);

// 可以对bean进行一些包装
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("====> postProcessAfterInitialization ... " + beanName + ": " + bean);

// 可以对bean进行一些包装
return bean;
}
}

BeanPostProcessor 执行流程源码分析

  1. 容器调用bean的构造方法创建对象
  2. populateBean():容器调用bean的set方法为bean对象的属性赋值(红色框)
  3. initializeBean():容器为bean做初始化操作(黄色框)

方法栈位置:AbstractAutowireCapableBeanFactory.java

image-20210629163559536

======> 进入黄色框的initializeBean()方法:

  • 执行invokeAwareMethods()方法(绿色框)
  • 执行applyBeanPostProcessorsBeforeInitialization()方法(红色框)
  • 执行invokeInitMethods()方法完成初始化(黄色框)
  • 执行applyBeanPostProcessorsAfterInitialization()方法(红色框)

image-20210630214340913

======> 进入红色框的applyBeanPostProcessorsBeforeInitialization()方法:

遍历得到容器中所有的BeanPostProcessor,并一执行BeanPostProcessorpostProcessBeforeInitialization()方法,将bean对象逐一经过每个BeanPostProcessor处理器。一旦返回null,跳出for循环后续不再执行。

image-20210629164433894

Spring 底层对 BeanPostProcessor 的应用

使用BeanPostProcessor接口的实现类,可以实现:bean赋值、注入其他组件、@Autowired属性注入、生命周期注解功能等。

BeanPostProcessor接口的实现类

image-20210629190629663

ApplicationContextAwareProcessor

ApplicationContextAwareProcessorBeanPostProcessor的一个实现类。其原理同上述自定义的MyBeanPostProcessor类一样,都会在如下for循环中被获取到,并执行其实现的postProcessBeforeInitialization()方法。

image-20210629164433894

ApplicationContextAwareProcessor类实现的postProcessBeforeInitialization()方法如下:

image-20210629192447682

每个bean在进入该方法后都会判断是否符合黄色框中接口的实现类,若符合,则会执行invokeAwareInterfaces()方法,根据其实现的接口类型调用相应的方法设置相应的容器上下文属性。

image-20210629192653559

AutowiredAnnotationBeanPostProcessor

实现@Autowired自动注入功能。在对象创建完后,获取被@Autowired注解修饰的属性,从而实现属性注入。

BeanValidationPostProcessor

BeanValidationPostProcessor常用在JavaWeb中用于数据校验

其类实现的postProcessBeforeInitialization()方法如下:

image-20210629194216136

在其doValidate()方法中提供了数据校验的功能。因此可以在bean初始化前后完成校验工作。

image-20210629194247408

InitDestroyAnnotationBeanPostProcessor

InitDestroyAnnotationBeanPostProcessor用于获取bean对象的@PostConstruct@PreDestroy注解信息,并据此执行相应的初始化和销毁方法。

image-20210629194708129

BeanPostProcessor 总结

每个bean对象在被注册到容器的过程中,会在执行invokeInitMethods()方法初始化bean之前,执行用户自定义注册的BeanPostProcessor实现类和容器默认注册的BeanPostProcessor实现类的postProcessBeforeInitialization()方法。不同实现类的postProcessBeforeInitialization()方法功能不同,可以自定义地为其设置不同的功能,包括输出日志、添加上下文容器、添加环境属性、添加资源解析器等。

若想让某些bean获得上述功能,只需要该类实现BeanPostProcessor接口的postProcessBeforeInitialization()方法即可。

使用BeanPostProcessor接口的实现类,可以实现:bean赋值、注入其他组件、@Autowired属性注入、生命周期注解功能等。

完整的生命周期

image-20220313162036597

组件属性赋值

@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 {

//不需要添加set方法
@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);
}

// 或直接省略@Autowired
@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 {

//不需要添加 set 方法
@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 {

//@Qualifier(value = "userDao1") 不设置时默认注入@Primary()修饰的userDao1
@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源码分析

img

  • 面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得 业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率;
  • 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。
  • 使用登录例子说明 AOP:

img

AOP 底层原理

Spring AOP的底层原理分析见【Spring】Spring5 AOP源码分析

AOP 底层使用动态代理 ,动态代理有两种情况:

第一种 有接口情况,使用JDK 动态代理 ;创建接口实现类代理对象,增强类的方法
在这里插入图片描述

第二种 没有接口情况,使用CGLIB 动态代理创建子类的代理对象,增强类的方法(该方法不需要实现接口,由CGLIB创建代理对象)
在这里插入图片描述

AOP 术语

  • 连接点(JointPoint):类里面哪些方法可以被增强,这些方法称为连接点,每一个方法的每一个位置(开始位置,返回位置,异常位置等)都是一个连接点。
  • 切入点(PointCut):切面通知执行的“地点”的定义,实际被真正增强的方法称为切入点。
  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ….
  • 切面(Aspect):横切关注点被模块化的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。包含前置通知,后置通知,环绕通知 ,异常通知和最终通知。
  • 目标(Target):被通知的对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。

image-20210713170252235

img

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有所不同)

  • 环绕通知(@AroundjoinPoint.procced()方法之前的代码
  • 前置通知(@Before
  • 业务代码
  • 返回通知(@AfterReturning)/ 若有异常,此时执行异常通知(@AfterThrowing
  • 后置通知(@After
  • 环绕通知(@AroundjoinPoint.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 {

// 抽取公共的切入点表达式
// 1. 本类可以引用
// 2. 其他的切面类也可以引用(需要全类名)
@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){
// 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("【环绕前置通知】.... ");
//执行目标方法proceed
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("容器创建完成....");

// 必须从容器中获得bean才能启动AOP
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

声明式事务

使用案例

  1. 导入相关依赖:数据源、数据库驱动、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>
  1. 配置数据源、JdbcTemplate操作数据库(Spring提供的简化数据库操作的工具)

  2. 添加 @EnableTransactionManagement 注解开启基于注解的事务管理功能

  3. 配置事务管理器来控制事务(事务管理器操作数据源,进行事务管理)

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());
}

}
  1. 在类或方法上添加 @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代表插入失败:

image-20210817144253822

  • 场景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()获取该类的对象时,将获取到该类的动态代理对象,而非原始对象。此时在调用该代理对象accountProxyaddAccount()时,将有事务包裹。

而若不调用该代理对象的addAccount(),而是将该方法直接写在本类中,直接调用本类里的该方法,则不会交由Spring事务管理器拦截,此时的方法和普通方法一样。

结论:只有Spring事务代理对象的方法才能被事务拦截器所拦截。直接调用方法无法被拦截(即使该方法被@Transactional注解修饰)。

声明式事务原理

@EnableTransactionManagement 注解向容器中添加AutoProxyRegistrarProxyTransactionManagementConfiguration组件,二者作用分别为:

  • AutoProxyRegistrar:类似于AOP中的AspectJAutoProxyRegistrar,用于向容器中注册InfrastructureAdvisorAutoProxyCreator组件(类似于AOP里的自动代理器,一种后置处理器)来为普通组件进行代理包装,创建代理对象
  • ProxyTransactionManagementConfiguration:用于注册事务增强器,该增强器内设置有事务拦截器,将在代理对象执行目标方法时进行拦截,并调用其invoke()方法,由事务管理器控制事务的提交与回滚

Spring事务原理与AOP原理十分相似,都包含有后置处理器拦截器思想,在组件创建后包装出代理对象、在代理对象执行目标方法时进行拦截,使用事务管理器控制事务的提交与回滚。

详细的源码分析见文章【Spring】Spring5 事务源码分析