@Configuration和@Bean

  • @Configuration 注解表示吧一个类声明为一个配置类
  • @Bean 注解相当于在容器中注册一个 bean

新建一个Person类

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
package com.szx.bean;

/**
* @author songzx
* @create 2022-07-05 9:58
*/
public class Person {
String name;
Integer age;

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public String getName() {
return name;
}

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

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public Person(String name, Integer age) {
this.name = name;
this.age = age;
}

public Person() {
}
}

通过配置类注册一个Person bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.szx.config;

import com.szx.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author songzx
* @create 2022-07-05 9:58
*/
// 告诉Spring这是一个配置类
@Configuration
public class BeanConfig {

// 给容器注册一个bean;类型为返回值的类型,bean名称默认为方法名
// 可以给 @Bean 注解声明value属性值,指明这个bean名称
// @Bean
@Bean("person01")
public Person person(){
return new Person("张三",16);
}
}

实例化 AnnotationConfigApplicationContext 得到 ioc 容器

1
2
3
4
5
6
7
8
9
10
11
public class BeanTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(BeanConfig.class);
// 通过类型获取
Person person = ioc.getBean(Person.class);
System.out.println(person); //=> Person{name='张三', age=16}
// 通过名称获取
Person person01 = ioc.getBean("person01", Person.class);
System.out.println(person01); //=> Person{name='张三', age=16}
}
}

@ComponentScan 自动扫描

基本使用方法

通过 xml 方式配置自动扫描时需要用到 component-scan

1
2
<!--设置自动扫描-->
<context:component-scan base-package="com.szx"></context:component-scan>

同样也可以使用注解的方式来配置自动扫描指定包下面的所有类

  • value 指定扫描那些包下面的类
  • includeFilters 设置只扫描声明了那个注解的类,但是要同时声明 useDefaultFilters = false
    • @ComponentScan.Filter 设置过滤方式
      • type = FilterType.ANNOTATION 根据注解扫描
      • classes = Controller.class 扫描Controller注解
  • useDefaultFilters 默认的扫描
  • excludeFilters 设置不扫描声明了那个注解的类,它的参数和 includeFilters 相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 声明这是一个配置类
@Configuration
// 定义扫描方式
@ComponentScan(
// 设置自动扫描那些包下面的类
value = "com.szx",
// 设置只扫描声明了Controller注解的类
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)
},
// 取消默认的扫描方式
useDefaultFilters = false
)
public class MainConfig {
}

编写测试方法,查询当前ioc容器中的bean

1
2
3
4
5
6
7
8
9
@Test
public void test(){
AnnotationConfigApplicationContext mainioc = new AnnotationConfigApplicationContext(MainConfig.class);
// getBeanDefinitionNames 获取当前容器中的所有bean
String[] beanNames = mainioc.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
}
}

因为设置了 includeFilters 只扫描 Controller 注解,所以结果如下

Snipaste_2022-07-05_11-24-05.png

自定义@ComponentScan.Filter过滤规则

type 属性的几个值分别对应的含义

  • ANNOTATION 根据注解过滤
  • ASSIGNABLE_TYPE 根据指定的类型过滤
  • ASPECTJ 根据ASPECTJ表达式
  • REGEX 根据正则表达式过滤
  • CUSTOM 自定义过滤

根据指定的类型过滤 ASSIGNABLE_TYPE ,示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
@ComponentScan(
// 设置自动扫描那些包下面的类
value = "com.szx",
// 设置只扫描声明了Controller注解的类
includeFilters = {
// 设置ASSIGNABLE_TYPE指定Book类型扫描
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = Book.class)
},
// 取消默认的扫描方式
useDefaultFilters = false
)
public class MainConfig {
}

扫描结果

Snipaste_2022-07-05_11-27-46.png

自定义扫描规则 CUSTOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 声明这是一个配置类
@Configuration
// 定义扫描方式
@ComponentScan(
// 设置自动扫描那些包下面的类
value = "com.szx",
// 设置只扫描声明了Controller注解的类
includeFilters = {
// 自定义扫描规则 CUSTOM
@ComponentScan.Filter(type = FilterType.CUSTOM ,classes = MyTypeFilter.class)
},
// 取消默认的扫描方式
useDefaultFilters = false
)
public class MainConfig {
}

需要新建一个 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
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.szx.config;

import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

/**
* @author songzx
* @create 2022-07-05 11:28
*/
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
/*
* 参数含义:
* metadataReader 获取当前正在扫描的类信息
* metadataReaderFactory 可以获取到其他任何类的信息
* */

// 获取当前类的注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前类的资源信息(类路径)
Resource resource = metadataReader.getResource();
// 获取当前类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();

// 通过类信息获取类名称
String className = classMetadata.getClassName();
// 判断如果类名称中包含 er 关键词则返回true,其他的返回 false
if(className.contains("er")){
return true;
}

return false;
}
}

运行测试方法,查看当前ioc容器中的bena

Snipaste_2022-07-05_11-43-38.png

@Scope 调整作用域

@Scope 作用域有两个值

  • singleton 单例的(默认值) 初始化ioc容器时会调用bean的初始化方法
  • prototype 多例的,初始化ioc容器时不会调用bean中的初始化方法

首先新建一个配置类

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class MainConfig2 {
// @Scope 设置作用域
// prototype 多例的,初始化ioc容器时不会调用bean中的初始化方法
// singleton 单例的(默认) 初始化ioc容器时会调用bean的初始化方法
@Scope
@Bean
public Person person(){
System.out.println("初始化person类bean");
return new Person("李四",18);
}
}

然后添加测试方法,从ioc容器中重复的取两边person,此时判断两个引用地址为用一个,判断返回 true

1
2
3
4
5
6
7
@Test
public void test2(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig2.class);
Person person = ioc.getBean("person", Person.class);
Person person2 = ioc.getBean("person", Person.class);
System.out.println(person == person2); //=> true
}

然后将配置类中的 @Scope 设置 prototype

1
2
3
4
5
@Scope("prototype")
@Bean
public Person person(){
return new Person("李四",18);
}

然后再次执行测试方法会返回 false

Snipaste_2022-07-05_13-50-59.png

其次当我们在初始化容器时两个状态也会有不同的情况,例如将测试方法中的 getBean 注释掉,只初始化一下 ioc 容器

1
2
3
4
5
6
7
@Test
public void test2(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig2.class);
/*Person person = ioc.getBean("person", Person.class);
Person person2 = ioc.getBean("person", Person.class);
System.out.println(person == person2); //=> true*/
}

然后修改配置类,在 new Person 之前打印一句话

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class MainConfig2 {
// @Scope 设置作用域
// prototype 多例的,初始化ioc容器时不会调用bean中的初始化方法
// singleton 单例的(默认) 初始化ioc容器时会调用bean的初始化方法
@Scope("prototype")
@Bean
public Person person(){
System.out.println("实例化 Person");
return new Person("李四",18);
}
}

此时的作用域为 prototype,执行测试方法观察打印。发现控制台并没有任何打印,这说明在初始化ioc容器时并没有初始化bean

Snipaste_2022-07-05_13-54-03.png

然后将测试方法中的 getBean 方法放开

Snipaste_2022-07-05_13-56-06.png

然后将作用域改为 singleton,再来观察控制台输出

Snipaste_2022-07-05_13-57-33.png

调用测试方法

image-20220705135831964

会发现当作用域为singleton时在初始化ioc容器时就会实例化一下Person,当调用 getBean 时不会重复实例化

Snipaste_2022-07-05_14-00-00.png

@Lazy 对象懒加载

设置对象懒加载,在初始化容器阶段不实例化对象,只在第一次获取时实例化,只针对单例有效

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
package com.szx.config;

import com.szx.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;

/**
* @author songzx
* @create 2022-07-05 13:31
*/
@Configuration
public class MainConfig2 {
// @Scope 设置作用域
// prototype 多例的,初始化ioc容器时不会调用bean中的初始化方法
// singleton 单例的(默认) 初始化ioc容器时会调用bean的初始化方法

@Lazy // 设置对象懒加载,在初始化容器阶段不实例化对象,只在第一次获取时实例化,只针对单例有效
@Bean
public Person person(){
System.out.println("实例化 Person");
return new Person("李四",18);
}
}

调用测试方法,重复获取两次,判断结果为 true

1
2
3
4
5
6
7
@Test
public void test2(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig2.class);
Person person = ioc.getBean("person", Person.class);
Person person2 = ioc.getBean("person", Person.class);
System.out.println(person == person2); //=> true
}

@Conditional 自定义条件注册bean

@Conditional 注解可以自定义根据条件注册当前bean

添加配置类,根据操作环境注册不同的bean,@Conditional 接收一个Condition接口实现类

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
package com.szx.config;

import com.szx.bean.Person;
import com.szx.conditional.LinuxConditional;
import com.szx.conditional.WindowConditional;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

/**
* @author songzx
* @create 2022-07-05 15:05
*/
@Configuration
public class MainConfig3 {

@Bean
public Person lisi(){
return new Person("lisi",18);
}

// 使用@Conditional注解,按照条件注册,只在操作系统是 window 时注册
@Conditional(WindowConditional.class)
@Bean
public Person bill(){
return new Person("bill",60);
}

// 使用@Conditional注解,按照条件注册,只在操作系统是 linux 时注册
@Conditional(LinuxConditional.class)
@Bean
public Person linus(){
return new Person("linus",43);
}
}

添加 WindowConditional

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
package com.szx.conditional;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
* @author songzx
* @create 2022-07-05 15:06
*/
public class WindowConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 获取beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 获取类加载器
ClassLoader classLoader = context.getClassLoader();
// 获取当前环境信息
Environment environment = context.getEnvironment();
// 获取bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();

// 获取当前的操作系统信息
String osName = environment.getProperty("os.name");
System.out.println(osName); //=> Windows 10

// 如果当前操作系统是Window注册当前bean
if(osName.contains("Window")){
return true;
}
return false;
}
}

添加 LinuxConditional

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
package com.szx.conditional;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
* @author songzx
* @create 2022-07-05 15:08
*/
public class LinuxConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 获取beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 获取类加载器
ClassLoader classLoader = context.getClassLoader();
// 获取当前环境信息
Environment environment = context.getEnvironment();
// 获取bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();

// 获取当前的操作系统信息
String osName = environment.getProperty("os.name");
System.out.println(osName); //=> Windows 10

// 如果当前操作系统是linux注册当前bean
if(osName.contains("linux")){
return true;
}

return false;
}
}

编写测试方法查看当前ioc容器中的bean

1
2
3
4
5
6
7
8
@Test
public void test3(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] names = ioc.getBeanNamesForType(Person.class);
for (String name : names) {
System.out.println(name);
}
}

运行测试方法,返回如下,因为操作系统是 Windows,所以 linus 不会被注册

Snipaste_2022-07-05_15-25-04.png

@Import 快速注册bean

基本用法

除了使用 @Bean 的方式注册 bean 之外,也可以在类上使用 @Import 快速注册 bean

使用方法:

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
package com.szx.config;

import com.szx.bean.Color;
import com.szx.bean.Order;
import com.szx.bean.Person;
import com.szx.conditional.LinuxConditional;
import com.szx.conditional.WindowConditional;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* @author songzx
* @create 2022-07-05 15:05
*/
// @Import 注解接收一个类数组,id 默认是全类名
@Import({Color.class, Order.class})
@Configuration
public class MainConfig3 {

@Bean
public Person lisi(){
return new Person("lisi",18);
}

// 使用@Conditional注解,按照条件注册,只在操作系统是 window 时注册
@Conditional(WindowConditional.class)
@Bean
public Person bill(){
return new Person("bill",60);
}

// 使用@Conditional注解,按照条件注册,只在操作系统是 linux 时注册
@Conditional(LinuxConditional.class)
@Bean
public Person linus(){
return new Person("linus",43);
}
}

调用测试方法获取当前ioc容器中的所有bean

1
2
3
4
5
6
7
8
@Test
public void test3(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] names = ioc.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}

Snipaste_2022-07-05_16-03-09.png

使用 ImportSelector 导入类

ImportSelector 是一个接口,返回需要导入类的全类名组成的字符串数组。可以在 @Import 注解上传入 ImportSelector 实现类来完成导入

添加 ImportSelector 实现类 MyImportSelector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.szx.config;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;


/**
* @author songzx
* @create 2022-07-05 16:12
*/
public class MyImportSelect implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 返回一个字符串数组,里面声明要导入类的全类名
return new String[]{"com.szx.bean.Yellor","com.szx.bean.Blue"};
}
}

然后在配置类上添加 @Import 注解

Snipaste_2022-07-05_16-24-40.png

执行测试方法,查看当前ioc容器中的bean

1
2
3
4
5
6
7
8
9
10
@Test
public void test3(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] names = ioc.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
Blue blue = ioc.getBean("com.szx.bean.Blue", Blue.class);
System.out.println(blue);
}

Snipaste_2022-07-05_16-25-41.png

ImportBeanDefinitionRegistrar 手动注册bean

ImportBeanDefinitionRegistrar 是一个接口,重写其 registerBeanDefinitions 方法,方法有两个参数

  • AnnotationMetadata 当前类的注解信息
  • BeanDefinitionRegistry BeanDefinition的注册类

首先编写 ImportBeanDefinitionRegistrar 实现类

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
package com.szx.config;

import com.szx.bean.Rainbow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
* @author songzx
* @create 2022-07-05 16:38
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
// 判断当前容器中是否有com.szx.bean.Blue对象
boolean b = registry.containsBeanDefinition("com.szx.bean.Blue");
boolean b1 = registry.containsBeanDefinition("com.szx.bean.Yellor");
if(b && b1){
// 手动注册一个bean
RootBeanDefinition beanDefinition = new RootBeanDefinition(Rainbow.class);
registry.registerBeanDefinition("rainbow",beanDefinition);
}
}
}

然后再配置类中的 @Import 注解上添加 MyImportBeanDefinitionRegistrar.class

Snipaste_2022-07-05_16-55-17.png

执行测试方法查看当前ioc容器中的bean

1
2
3
4
5
6
7
8
9
10
@Test
public void test3(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] names = ioc.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
Blue blue = ioc.getBean("com.szx.bean.Blue", Blue.class);
System.out.println(blue);
}

Snipaste_2022-07-05_16-56-12.png

通过 FactoryBean 工厂bean创建对象

FactoryBean 是一个接口,首先创建这个接口的实现类

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
package com.szx.bean;

import org.springframework.beans.factory.FactoryBean;

/**
* @author songzx
* @create 2022-07-05 17:18
*/
public class ColorFactoryBean implements FactoryBean<Color> {
@Override
public Color getObject() throws Exception {
System.out.println("创建Color实例");
return new Color();
}

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

@Override
public boolean isSingleton() {
return true;
}
}

在配置类中注册

1
2
3
4
5
6
// MainConfig3

@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}

首先调用测试方法查看当前ioc容器中的所有bean,可以看到存在 colorFactoryBean 对象

Snipaste_2022-07-05_17-28-08.png

获取 colorFactoryBean 并打印这个对象的类型,获取工厂bean本身时需要在bean id前面添加 &

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test4() throws Exception {
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig3.class);
// 通过工厂Bean获取对象
Object colorFactoryBean = ioc.getBean("colorFactoryBean");
Class<?> aClass = colorFactoryBean.getClass(); // 调用了创建 Color 实例的方法
// 获取到的类型是 Color 类型
System.out.println("aClass = " + aClass); //=> class com.szx.bean.Color

// 获取工厂bean本身
Object bean = ioc.getBean("&colorFactoryBean");
System.out.println(bean.getClass()); //=> class com.szx.bean.ColorFactoryBean
}

执行运行结果

Snipaste_2022-07-05_17-29-45.png

@Bean 指定初始化和销毁方法

首先新建两个普通类,并分别定义 init 和 destroy 方法

新建 Car

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.szx.bean;

/**
* @author songzx
* @create 2022-07-05 18:49
*/
public class Car {
public Car() {
System.out.println("car constructor...");
}

// 自定义初始化方法
public void init(){
System.out.println("car init...");
}

// 自定义销毁方法
private void destroy() {
System.out.println("car destroy...");
}
}

新建 Emp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.szx.bean;

/**
* @author songzx
* @create 2022-07-05 18:55
*/
public class Emp {
public Emp() {
System.out.println("emp constructor ...");
}

public void init(){
System.out.println("emp init...");
}

public void destroy(){
System.out.println("emp destroy ...");
}
}

新建一个配置类,在这个配置类中注册 car 和 emp 两个 bean,其中emp为多例 bean

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
package com.szx.config;

import com.szx.bean.Car;
import com.szx.bean.Emp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
* @author songzx
* @create 2022-07-05 18:51
*/
@Configuration
public class MainConfig4 {
@Bean(initMethod = "init",destroyMethod = "destroy")
public Car car(){
return new Car();
}

@Scope("prototype")
@Bean(initMethod = "init",destroyMethod = "destroy")
public Emp emp(){
return new Emp();
}
}

编写测试方法,查看两个对象的初始化方法和销毁方法调用时机

1
2
3
4
5
6
7
8
@Test
public void test1(){
// 初始化容器时会调用单例bean的初始化方法
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig4.class);

// 容器关闭时会调用单例bean的销毁方法
ioc.close();
}

首先只初始化容器和关闭容器,查看运行结果,可以看到多例bean emp的构造方法和init以及destroy方法都没有执行

Snipaste_2022-07-05_19-02-25.png

然后现在加上获取 emp bean的方法

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test1(){
// 初始化容器时会调用单例bean的初始化方法
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig4.class);

// 多例bean只会在获取时调用构造方法和初始化方法
ioc.getBean("emp");

// 容器关闭时会调用单例bean的销毁方法
// 容器关闭不会调用多例bean的销毁方法
ioc.close();
}

此时只调用了 emp 对象的构造方法和 init 方法,destroy 方法并没有被调用

Snipaste_2022-07-05_19-03-52.png

总结:

  • 单例bean
    • ioc容器创建时就调用对象的构造方法和init方法
    • ioc容器关闭时会调用对象的destroy方法
  • 多例bean
    • ioc容器创建时不会调用对象的构造方法和init方法
    • 获取这个bean对象时才调用构造方法和init方法
    • ioc容器关闭不会调用多例bean对象的destroy方法

nitializingBean, DisposableBean 接口的使用

让一个类实现这两个接口即可实现初始化方法和销毁方法

创建接口实现类

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
package com.szx.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
* @author songzx
* @create 2022-07-05 19:15
*/
@Component
public class Dep implements InitializingBean, DisposableBean {
public Dep() {
System.out.println("dep constructor ...");
}

// 初始化方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("dep init ...");
}

// 销毁方法
@Override
public void destroy() throws Exception {
System.out.println("dep destroy ...");
}
}

在配置类中添加 @ComponentScan("com.szx.bean") 注解自动扫描

执行测试方法查看初始化方法的打印

1
2
3
4
5
6
7
@Test
public void test1(){
// 初始化容器时会调用单例bean的初始化方法
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig4.class);

ioc.close();
}

运行效果

Snipaste_2022-07-05_19-22-37.png

通过@PostConstruct和@PreDestroy指定初始化和销毁方法

  • @PostConstruct 注解指定一个方法为属性赋值后调用的初始化方法
  • @PreDestroy 注解指定一个方法为对象销毁调用的销毁方法

新建一个 Dog 类

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
package com.szx.bean;

import org.springframework.stereotype.Component;

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

/**
* @author songzx
* @create 2022-07-09 10:03
*/
@Component
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 destroy...");
}
}

运行测试方法查询执行效果

1
2
3
4
5
6
7
@Test
public void test1(){
// 初始化容器时会调用单例bean的初始化方法
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig4.class);

ioc.close();
}

Snipaste_2022-07-09_10-09-56.png

BeanPostProcessor后置处理器

BeanPostProcessor 是一个接口,要重写这个接口的两个方法,分别表示对象初始化之前调用,对象初始化之后调用

  • postProcessBeforeInitialization 对象初始化之前调用
  • postProcessAfterInitialization 对象初始化之后调用

创建实现类

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
package com.szx.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

/**
* @author songzx
* @create 2022-07-09 10:18
*/
@Component
public class MyBeanPostProcess implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + "初始化之前..");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + "初始化之后..");
return bean;
}
}

执行测试方法,查看运行效果

1
2
3
4
5
6
7
@Test
public void test1(){
// 初始化容器时会调用单例bean的初始化方法
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig4.class);

ioc.close();
}

Snipaste_2022-07-09_10-23-42.png

@Value注解给属性赋值

@Value 添加对象的属性上

  • 可以是基本数据类型
  • 可以是SpEL表达式,例如 #{15-6}
  • 可以是 ${} ,读取外部配置文件

新建一个配置类,里面注册一个 Person Bean

1
2
3
4
5
6
7
@Configuration
public class MainConfig5 {
@Bean
public Person person(){
return new Person();
}
}

然后打印这个ioc容器中的bean

1
2
3
4
5
6
7
8
@Test
public void test1(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig5.class);
String[] beanNames = ioc.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
}
}

Snipaste_2022-07-09_10-53-50.png

现在来获取 person bean 查看属性值,可以看到现在都是 null

Snipaste_2022-07-09_10-55-11.png

接着通过 @Value 注解对属性进行赋值

1
2
3
4
5
6
7
8
9
public class Person {

@Value("张三")
String name;

@Value("#{15-6}")
Integer age;

}

再来执行获取 person 方法,查看打印的对象值

Snipaste_2022-07-09_10-56-45.png

@PropertySource 读取配置文件为属性赋值

首先新建一个配置文件 person.properties

1
person.nickName=小张三

然后在配置文件中使用 @PropertySource 配置数据源

  • value 是一个字符串数组,可以设置多个数据源
  • encoding 设置读取配置文件的编码方式,使用 utf-8 解决读取中文乱码问题
1
2
3
4
5
6
7
8
@PropertySource(value = {"classpath:person.properties"},encoding = "utf-8")
@Configuration
public class MainConfig5 {
@Bean
public Person person(){
return new Person();
}
}

然后再 Person 类中使用 ${person.nickName} 的方式为 nickName 属性赋值

1
2
@Value("${person.nickName}")
String nickName;

执行测试方法,查看打印结果

1
2
3
4
5
6
@Test
public void test1(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig5.class);
Person person = ioc.getBean("person", Person.class);
System.out.println(person);
}

Snipaste_2022-07-09_11-22-06.png

自动装配

@AutoWrite

新建一个 Bookdao

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class BookDao {
String bookName = "book1";

public String getBookName() {
return bookName;
}

public void setBookName(String bookName) {
this.bookName = bookName;
}
}

然后新建 BookServer,使用 @Autowired 自动装配 bookDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class BookServer {
![Snipaste_2022-07-09_12-14-22.png](https://szx-bucket1.oss-cn-hangzhou.aliyuncs.com/images/Snipaste_2022-07-09_12-14-22.png)
BookDao bookDao;

public BookServer() {
}

public BookDao getBookDao() {
return bookDao;
}

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

新建配置类 MainConfig6

1
2
3
4
5
@ComponentScan(value = {"com.szx.dao","com.szx.server"})
@Configuration
public class MainConfig6 {

}

添加测试方法,通过 BookServer 获取 bookDao 中的 bookName

1
2
3
4
5
6
7
8
9
public class AutowiredTest {
@Test
public void test1(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig6.class);
BookServer bookServer = ioc.getBean("bookServer", BookServer.class);
BookDao bookDao = bookServer.getBookDao();
System.out.println(bookDao.getBookName());
}
}

打印结果为book1

Snipaste_2022-07-09_12-14-22.png

@Qualifier

使用 @AutoWrite 注解默认按照组件类型去容器中获取,如果容器中有两个相同类型的组件,再将属性的名称作为 id 去容器中查找

修改配置类,在配置类注册另外一个 BookDao 类型的组件,名称为 bookDao2

1
2
3
4
5
6
7
8
9
10
11
@ComponentScan(value = {"com.szx.dao","com.szx.server"})
@Configuration
public class MainConfig6 {

@Bean
public BookDao bookDao2(){
BookDao bookDao = new BookDao();
bookDao.setBookName("book2");
return bookDao;
}
}

运行测试方法,此时仍然打印 book1

如果才能打印 book2 呢?可以使用 @Qualifier 注解

在 BookServer 中添加 @Qualifier(“bookDao2”) 表示自动装配容器中名称为 bookDao2 的对象

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
package com.szx.server;

import com.szx.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
* @author songzx
* @create 2022-07-05 10:49
*/
@Service
public class BookServer {

@Qualifier("bookDao2")
@Autowired
BookDao bookDao;

public BookServer() {
}

public BookDao getBookDao() {
return bookDao;
}

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

执行测试方法

1
2
3
4
5
6
7
@Test
public void test1(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig6.class);
BookServer bookServer = ioc.getBean("bookServer", BookServer.class);
BookDao bookDao = bookServer.getBookDao();
System.out.println(bookDao.getBookName());
}

Snipaste_2022-07-09_18-38-17.png

@Primary

在bean对象上添加 @Primary 注解表示Spring自动装配时默认使用的首选 bean,此时就和 bean 名称无关

将 BookServer 中的 @Qualifier(“bookDao2”) 去掉,只留一个 @AutoWrite,此时正常情况下会装配 bookDao 对象

1
2
@Autowired
BookDao bookDao;

但是我们在配置类中注册的 bookDao2 上添加 @Primary 注解。此时就会装配 bookDao2

1
2
3
4
5
6
7
@Primary
@Bean
public BookDao bookDao2(){
BookDao bookDao = new BookDao();
bookDao.setBookName("book2");
return bookDao;
}

运行测试方法,打印的是 book2

Snipaste_2022-07-09_18-45-02.png

@AutoWrite自动装配的位置

  • 可以放在属性上
  • 可以放在有参构造器上
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
package com.szx.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
* @author songzx
* @create 2022-07-09 19:51
*/
@Component
public class Boos {


Yellor yellor;

public Boos() {
}

@Autowired
public Boos(Yellor yellor) {
this.yellor = yellor;
}

@Override
public String toString() {
return "Boos{" +
"yellor=" + yellor +
'}';
}
}

自动装配Aware注入Spring底层组件

我们可以在自定义组件中注入Spring底层的一些组件,例如在组件中注入 applicationContext

实现 ApplicationContextAware 接口,然后重写 setApplicationContext 方法,这个方法会以回调的方式自动执行

1
2
3
4
5
6
7
8
9
10
@Component
public class MyAware implements ApplicationContextAware {
ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("applicationContext = " + applicationContext);
}
}

运行测试代码注册ioc容器,查看打印结果

1
2
3
4
5
@Test
public void test1(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig6.class);
System.out.println(ioc);
}

Snipaste_2022-07-09_20-37-45.png

@Profile

@Profile 注解可以根据环境自动切换装配不同的bean,例如配置的数据库引用地址。在开发环境我们连接的是 dev 库,生产环境下我们连接 produce 库。

环境搭建

首先新建数据库,分别新建 annotation_devannotation_product

Snipaste_2022-07-09_21-24-38.png

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

然后新建 jdbc.properties 配置文件

1
2
3
4
5
jdbc.name=root
jdbc.pwd=abc123
jdbc.driver=com.mysql.jdbc.Driver
jdbc.devUrl=jdbc:mysql://127.0.0.1:3306/annotation_dev
jdbc.proUrl=jdbc:mysql://127.0.0.1:3306/annotation_product

然后新建配置类

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
53
54
55
56
57
58
59
60
package com.szx.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
* @author songzx
* @create 2022-07-09 21:11
*/
@PropertySource("classpath:jdbc.properties")
@Configuration
public class MainConfig7 {
@Value("${jdbc.name}")
String name;
@Value("${jdbc.pwd}")
String pwd;
@Value("${jdbc.devUrl}")
String devUrl;
@Value("${jdbc.proUrl}")
String proUrl;
@Value("${jdbc.driver}")
String driver;


/**
* 开发环境引用的数据源
* @author Songzx
* @date 2022/7/9
*/
@Bean
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(name);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl(devUrl);
dataSource.setDriverClass(driver);
return dataSource;
}

/**
* 生产环境引用的数据源
* @author Songzx
* @date 2022/7/9
*/
@Bean
public DataSource dataSourceProduct() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(name);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl(devUrl);
dataSource.setDriverClass(proUrl);
return dataSource;
}
}

根据环境配置bean

首先在bean对象上添加 @Profile 注解,@Profile 注解可以自定义一个环境名称,和然后会自动根据当前环境自动装配相对应的bena。如果bean没有指定环境,则默认所有环境都进行装配

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
@Bean
public Yellor yellor(){
return new Yellor();
}


/**
* 开发环境引用的数据源
* @author Songzx
* @date 2022/7/9
*/
@Profile("dev")
@Bean
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(name);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl(devUrl);
dataSource.setDriverClass(driver);
return dataSource;
}

/**
* 生产环境引用的数据源
* @author Songzx
* @date 2022/7/9
*/
@Profile("product")
@Bean
public DataSource dataSourceProduct() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(name);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl(devUrl);
dataSource.setDriverClass(proUrl);
return dataSource;
}

有两种方法:

  • 设置启动参数
  • 通过代码方式设置启动环境

方式一:设置启动参数

设置启动参数

Snipaste_2022-07-09_22-08-16.png

添加启动配置,固定语句:-Dspring.profiles.active=product,product 就是要设置的环境名称

Snipaste_2022-07-09_22-09-44.png

执行测试方法

1
2
3
4
5
6
7
8
@Test
public void test1() {
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig7.class);
String[] beanNames = ioc.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
}
}

通过图中可以看到 yellow 没有设置 @Profile,所以会被打印,但是 DataSource 类型的 bean 只打印了 dataSoruceProduct

Snipaste_2022-07-09_22-11-05.png

修改启动参数 -Dspring.profiles.active=dev

Snipaste_2022-07-09_22-13-02.png

方式二:通过代码方式设置启动环境

操作步骤:

  1. 使用空参构造器创建一个ioc容器
  2. 设置需要激活的环境,可以设置多个
  3. 注册配置类
  4. 刷新容器

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test2(){
// 使用空参构造器创建一个ioc容器
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext();
// 设置需要激活的环境,可以设置多个
ioc.getEnvironment().setActiveProfiles("dev");
// 注册配置类
ioc.register(MainConfig7.class);
// 刷新容器
ioc.refresh();

String[] beanNames = ioc.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
}
}

运行效果

Snipaste_2022-07-09_22-19-31.png

@Profile设置位置

@Profile 注解除了可以设置 bean 上,也可以直接设置在配置类上,设置在配置类上表示当前配置类中的所有 bean 都会根据环境自动注册

例如:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.szx.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.szx.bean.Yellor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
* @author songzx
* @create 2022-07-09 21:11
*/

@Profile("dev")
@PropertySource("classpath:jdbc.properties")
@Configuration
public class MainConfig7 {
@Value("${jdbc.name}")
String name;
@Value("${jdbc.pwd}")
String pwd;
@Value("${jdbc.devUrl}")
String devUrl;
@Value("${jdbc.proUrl}")
String proUrl;
@Value("${jdbc.driver}")
String driver;

@Bean
public Yellor yellor(){
return new Yellor();
}


/**
* 开发环境引用的数据源
* @author Songzx
* @date 2022/7/9
*/
@Bean
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(name);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl(devUrl);
dataSource.setDriverClass(driver);
return dataSource;
}

/**
* 生产环境引用的数据源
* @author Songzx
* @date 2022/7/9
*/
@Bean
public DataSource dataSourceProduct() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(name);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl(devUrl);
dataSource.setDriverClass(proUrl);
return dataSource;
}
}

然后修改测试方法,将环境设置为 product,但是上面的配置类添加了 @Profile(“dev”) 注解,表示只有在 dev 环境下才会注册,所以运行结果中就不会有任何 bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test2(){
// 使用空参构造器创建一个ioc容器
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext();
// 设置需要激活的环境,可以设置多个
ioc.getEnvironment().setActiveProfiles("product");
// 注册配置类
ioc.register(MainConfig7.class);
// 刷新容器
ioc.refresh();

String[] beanNames = ioc.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println(beanName);
}
}

运行结果

Snipaste_2022-07-09_22-24-08.png

AOP功能测试

aop 指的是在程序运行期间动态的将某段代码切入到指定方法指定位置运行的编程方式。

设置步骤

  1. 导入AOP依赖 spring-aspects
  2. 定义一个业务逻辑类(MathFuns),在业务逻辑类中方法运行时将日志进行打印
  3. 定义一个日志切面类(LogAspects),切面类里面的方法需要动态感知 MathFuns 类中方法的运行进度,进行通知
    1. 通知方法:
      • 前置通知 @Before,方法开始调用之前执行
      • 后置通知 @After,方法运行结束后执行,无论方法执行成功还是失败都会执行
      • 成功返回通知 @AfterReturning,方法成功运行后执行
      • 异常返回通知 @AfterThrowing,方法运行出错后执行
      • 环绕通知
  4. 给切面类中的目标方法添加不同的注解
  5. 将切面类和逻辑类都添加到ioc容器中
  6. 告诉Spring那个类才是切面类,添加 @Aspects 注解来表示该类是一个切面类
  7. 在配置类上添加 @EnableAspectJAutoProxy 注解开启基于注解的aop模式

代码演示

第一步:导入AOP依赖 spring-aspects

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>

第二步:定义一个业务逻辑类(MathFuns),在业务逻辑类中方法运行时将日志进行打印

1
2
3
4
5
6
7
public class MathFuns {

public int div(int i,int j){
System.out.println("MathFuns.div 自己运行中..");
return i / j;
}
}

第三步:定义一个日志切面类(LogAspects),切面类里面的方法需要动态感知 MathFuns 类中方法的运行进度,进行通知

第四步:给切面类中的目标方法添加不同的注解

第六步:告诉Spring那个类才是切面类,添加 @Aspects 注解来表示该类是一个切面类

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
53
54
55
56
57
58
59
60
61
62
63
64
package com.szx.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;

/**
* @author songzx
* @create 2022-07-10 10:14
*
* @Aspect 注解表示这个类是一个切面类
*/
@Aspect
public class LogAspects {
@Pointcut(value = "execution(* com.szx.aop.MathFuns.*(..))")
// 切入点抽取
public void pointcut(){}

@Before(value = "pointcut()")
public void logStart(JoinPoint joinPoint){
// 获取类名
String className = joinPoint.getSignature().getDeclaringTypeName();
// 获取方法名称
String methodName = joinPoint.getSignature().getName();
// 获取参数
Object[] args = joinPoint.getArgs();
List<Object> argList = Arrays.asList(args);

System.out.println(className + "." + methodName + "方法开始调用,参数为:" + argList);
}

@AfterReturning(value = "pointcut()",returning = "res")
public void logSucReturn(JoinPoint joinPoint,Object res){
// 获取类名
String className = joinPoint.getSignature().getDeclaringTypeName();
// 获取方法名称
String methodName = joinPoint.getSignature().getName();

System.out.println(className + "." + methodName + "方法正常返回,返回结果:" + res);
}

@AfterThrowing(value = "pointcut()",throwing = "error")
public void logErrReturn(JoinPoint joinPoint,Exception error){
// 获取类名
String className = joinPoint.getSignature().getDeclaringTypeName();
// 获取方法名称
String methodName = joinPoint.getSignature().getName();

System.out.println(className + "." + methodName + "方法发生错误,错误信息:" + error);
}

@After(value = "pointcut()")
public void logEnd(JoinPoint joinPoint){
// 获取类名
String className = joinPoint.getSignature().getDeclaringTypeName();
// 获取方法名称
String methodName = joinPoint.getSignature().getName();

System.out.println(className + "." + methodName + "方法结束");
}
}

第五步:将切面类和逻辑类都添加到ioc容器中

第七步:在配置类上添加 @EnableAspectJAutoProxy 注解开启基于注解的aop模式

1
2
3
4
5
6
7
8
9
10
11
12
13
@EnableAspectJAutoProxy
@Configuration
public class MainConfig8_AOP {
@Bean
public MathFuns mathFuns(){
return new MathFuns();
}

@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}

测试

添加测试方法,从ioc容器中获取 mathFuns 类

1
2
3
4
5
6
@Test
public void test(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig8_AOP.class);
MathFuns mathFuns = ioc.getBean(MathFuns.class);
mathFuns.div(1, 1);
}

运行结果:

Snipaste_2022-07-10_10-49-53.png

如果我们手动的让方法发生错误,将除数设置为0

1
2
3
4
5
6
@Test
public void test(){
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MainConfig8_AOP.class);
MathFuns mathFuns = ioc.getBean(MathFuns.class);
mathFuns.div(1, 0);
}

运行结果:

Snipaste_2022-07-10_10-50-56.png

声明式事务

环境搭建

首先导入相关依赖

  • spring 核心包
  • jdbc
  • c3p0数据库连接池
  • mysql驱动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>

<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

新建 TxConfig 配置类

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
package com.szx.tx;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
* @author songzhengxiang
* @create 2022-07-12 22:47
*/

@ComponentScan("com.szx.tx")
@Configuration
public class TxConfig {
@Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("abc123");
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/txdata");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
return dataSource;
}

@Bean
public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
}

新建 TxDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.szx.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

/**
* @author songzhengxiang
* @create 2022-07-12 22:54
*/
@Component
public class TxDao {
@Autowired
JdbcTemplate jdbcTemplate;

public void addUser(){
String sql = "INSERT INTO users VALUES (null,?,?);";
jdbcTemplate.update(sql,"lisi",21);
}
}

新建 TxServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.szx.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author songzhengxiang
* @create 2022-07-12 22:54
*/
@Service
public class TxServlet {
@Autowired
TxDao txDao;

public void addUser(){
txDao.addUser();
}
}

此时基本环境搭建配置完成,但是还没有引入事务相关注解

添加事务注解

第一步,在配置类上添加 @EnableTransactionManagement 注解开启声明式注解

第二步:在配置类中注册 PlatformTransactionManager 类型的bean

1
2
3
4
@Bean
public PlatformTransactionManager transactionManager() throws PropertyVetoException {
return new DataSourceTransactionManager(dataSource());
}

第三步:在要实现添加事务的方法上添加 @Transactional 注解

1
2
3
4
5
@Transactional
public void addUser(){
txDao.addUser();
Integer i = 10 /0;
}