【Spring注解】04-自动装配

Spring 注解驱动开发【自动装配】

1.定义

​ Spring利用依赖注入(DI),完成对ioc容器中各组件的依赖关系进行赋值

2.bean的属性自动装配

1.@Autowired【Spring定义的】

分别创建BookMapper、BookService和BookController

1
2
3
4
@Reposity
public class BookMapper{
...
}
1
2
3
4
5
6
@Service
// 注册进容器的组件默认bean名字为类名小写
public class BookService {
@Autowired
private BookMapper bookMapper;
}
1
2
3
4
5
6
@Controller
public class BookController {
@Autowired
private BookService bookService;
}

  • 创建配置类并添加组件扫描注解
1
2
3
4
5
@ComponentScan({"indi.zhihuali.service",
"indi.zhihuali.mapper",
"indi.zhihuali.controller"})
public class ConfigOfAutowired {
}
  • @Autowired

1.1.默认优先按照类型去容器中寻找对应组件(以BookService为例):

1
applicationContext.getBean(BookMapper.class);

​ 如果找到就给bookService中的bookMapper赋值

1.1.2.若找到多个相同类型的组件 再根据组件名作为组件的id去容器中查找

注意:默认情况下 添加了@Component的组件 id即为类名首字母小写

1
applicationContext.getBean("bookMapper");

​ 为了区分ioc容器中的组件 给mapper设置一个label从而分辨不同的mapper对象

1
2
3
4
5
6
7
8
9
@Data
@AllArgsConstructor
@NoArgsConstructor
// 默认以类名小写首字母作为ioc容器中bean的名字
public class BookMapper {
// 给BookMapper添加一个标签以分辨在ioc容器中是哪个mapper
// 将BookMapper设置默认的label为 1
private String label = "1";
}

​ 在配置类中再次注册一个组件 命名为bookMapper2并设置label为2

1
2
3
4
5
6
7
//    Config类
@Bean(value = "bookMapper2")
public BookMapper bookMapper(){
BookMapper mapper = new BookMapper();
mapper.setLabel("2");
return mapper;
}

​ 在Service中添加toString方法以便查看Service中的Mapper对象label

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class BookService {
@Autowired
@Qualifier("bookMapper")
private BookMapper bookMapper;


@Override
public String toString() {
return "BookService{" +
"bookMapper=" + bookMapper +
'}';
}
}

编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestAutowired {
// 获取ioc容器
private ApplicationContext context = new AnnotationConfigApplicationContext(ConfigOfAutowired.class);
@Test
public void test(){
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
BookService service = context.getBean(BookService.class);
System.out.println(service);

//可以看到容器中的mapper和service中装配的mapper打印出来的地址相同
// BookMapper mapper = context.getBean("bookMapper",BookMapper.class);
// System.out.println(mapper);
}

}

​ 输出:

1
BookService{bookMapper=BookMapper(label=1)}

​ 到此可以总结出:

@Autowired默认先对类型进行匹配 若同时匹配到多个类型 则按照名称进行匹配
即先byType再byName

1.1.3.同样可以通过@Qualifier(“bookMapper”) 指定需要装配的组件的id 而不是用默认的属性名**

  • 在配置类中注册mapper2

    1
    2
    3
    4
    5
    6
    @Bean(value = "bookMapper2")
    public BookMapper bookMapper(){
    BookMapper mapper = new BookMapper();
    mapper.setLabel("2");
    return mapper;
    }
  • 通过@Qualifier对bean指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class BookService {
@Autowired
@Qualifier("bookMapper2")
private BookMapper bookMapper;


@Override
public String toString() {
return "BookService{" +
"bookMapper=" + bookMapper +
'}';
}
}

测试运行结果为:

​ BookService{bookMapper=BookMapper(label=2)}

1.1.4.当容器中没有匹配bean时

​ 自动装配 默认一定要将属性赋值好 没有就会报错

​ 可以通过

1
@Autowired(required = false)

​ 当没有指定bean时 为空也可以

1
BookService{bookMapper=null}

1.1.5.@Primary

在配置类中注册bean时 可以在其上添加该注解从而使其变成首选bean

当spring自动装配时,默认使用首选的bean

添加该注解后 若指定的是首选bean时 就不需要重复写@Qualifier了 如果是其他bean则仍可以通过@Qualifier进行指定

1
2
3
4
5
6
7
@Primary
@Bean(value = "bookMapper2")
public BookMapper bookMapper(){
BookMapper mapper = new BookMapper();
mapper.setLabel("2");
return mapper;
}

2.@Resource(JSR250)和@Inject(JSR330)(不常用)【Java规范】

  • @Resource

    可以和@Autowired一样实现自动装配功能 默认是按照组件名称进行装配的ByName

1
2
3
4
5
@Service
public class BookService {
@Resource
private BookMapper bookMapper;
}

​ 没有能支持@Primary和require = false的功能

  • @Inject

    需要导入javax.inject的包 和Autowired的功能一样 但没有require = false的功能

@Autowired、@Resource、@Inject都是通过AutowiredAnnotationBeanPostProcessor 解析完成自动配置功能的

3.bean的方法、构造器位置的自动装配

3.1.在set方法上加@Autowired

创建实体类Boss、Car并注册入容器中

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
@Component
public class Boss {
// @Autowored
private Car car;

public Car getCar() {
return car;
}

@Autowired
// Autowired标注在方法上 Spring容器在创建当前对象时就会调用这个方法,完成赋值
// 方法使用的参数 自定义类型的值从ioc容器中获取

public void setCar(Car car) {
this.car = car;
}

@Override
public String toString() {
return "Boss{" +
"car=" + car +
'}';
}
}

1
2
3
4
5
@Component
public class Car {
public Car() {
}
}
  • 测试通过方法@Autowired装配的car是否来自ioc容器中
1
2
3
4
5
6
7
@Test
public void test02(){
Boss boss = context.getBean(Boss.class);
System.out.println(boss);
Car car = context.getBean(Car.class);
System.out.println(car);
}

​ 测试结果:

1
2
Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90

结果显示 Boss的Car属性确实来自于ioc容器中

注:@Autowired注解也可以标在形参处

     public void setCar(Car car) {
    this.car = car;
}

3.2.在有参构造器上加@Autowired

**默认情况下:加载ioc容器中的组件,容器启动是会调用无参构造器创建对象,之后再进行初始化赋值等操作 **

  • 给Boss添加有参构造器
1
2
3
4
@Autowired
public Boss(Car car) {
this.car = car;
}
  • 测试通过方法@Autowired装配的car是否来自ioc容器中
1
2
3
4
5
6
7
@Test
public void test02(){
Boss boss = context.getBean(Boss.class);
System.out.println(boss);
Car car = context.getBean(Car.class);
System.out.println(car);
}

​ 测试结果:

1
2
Boss{car=indi.zhihuali.pojo.Car@120ba90}
indi.zhihuali.pojo.Car@120ba90

​ 因此可得:构造器要用的组件 也是来自ioc容器中

​ 与set方法中一样,注解也可以标在形参前

1
2
3
public Boss(@Autowired Car car) {
this.car = car;
}

标在有参构造器上时 如果组件只有一个有参构造器时,这个有参构造器的@Autowired可以省略 参数位置的组件还是可以自动从容器中获取

3.3.在参数前加@Autowired

1
2
3
public Boss(@Autowired Car car) {
this.car = car;
}

3.4.配置类中@Bean处添加@Autowired(默认不写)

参数从容器中获取 默认不写@Autowired 效果一样 都能自动装配

  • 创建实体类Manager

    1
    2
    3
    4
    5
    6
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Manager {
    private Car car;
    }
  • 在配置类中@Bean注册bean

    1
    2
    3
    4
    5
    6
    @Bean
    public Manager manager(@Autowired(默认不写) Car car){
    Manager manager = new Manager();
    manager.setCar(car);
    return manager;
    }
  • 测试通过Bean上添加@Autowired装配的car是否来自ioc容器中

    1
    2
    3
    4
    5
    6
    7
    @Test
    public void test02(){
    Car car = context.getBean(Car.class);
    System.out.println(car);
    Manager manager = context.getBean(Manager.class);
    System.out.println(manager);
    }

    ​ 测试结果:

    1
    2
    Manager{car=indi.zhihuali.pojo.Car@120ba90}
    indi.zhihuali.pojo.Car@120ba90

4.Aware注入Spring底层组件

当组件需要用到Spring定义的底层组件如:ApplicationContext、BeanFactory…

自定义组件实现xxxAware接口 在创建对象时会调用接口规定的方法注入相关组件

Aware接口的定义:

1
2
3
4
A marker superinterface indicating that a bean is eligible to be notified by the
Spring container of a particular framework object through a callback-style method.
标记超接口,指示bean有资格被
Spring容器通过回调方式调用特定框架对象。

将Spring底层的一些组件注入到自定义的bean中

实例:

  • 自定义类并实现xxxAware接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class Red implements ApplicationContextAware, EmbeddedValueResolverAware, BeanNameAware {
private ApplicationContext applicationContext;

public void setBeanName(String name) {
System.out.println("当前bean的名字"+name);
}

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("传入的Ioc"+applicationContext);
}

// String值解析器 可以解析占位符
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String s = resolver.resolveStringValue("你好!${os.name},我是#{18*20}!");
System.out.println(s);
}
}
  • 测试
1
2
3
4
5
6
7
8
9
@Test
public void test(){
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
BookService service = context.getBean(BookService.class);
System.out.println(service);
}

输出结果为:

1
2
3
4
5
6
当前bean的名字red
你好!Windows 10,我是360!
传入的Iocorg.springframework.context.annotation.AnnotationConfigApplicationContext@117365b, started on Wed Sep 23 17:19:55 CST 2020
postProcessBeforeInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
postProcessAfterInitialization...indi.zhihuali.pojo.Red@15d6a01 name:red
...

xxxAware往往都会有对应的Processor后置处理器通过debug实现的xxxAware接口中重写的方法 可以看到后置处理器也在工作 也会调用BeforeInitialization()…

5.@Profile

Spring提供的可以根据当前环境 动态激活和切换一系列组件的功能

在实际工作环境中 需要切换不同的环境来适应不同的业务、进行不同的操作

例如:在测试环境与开发环境中,连接数据库中不同的表

​ 在不同的环境下连接不同的Tomcat端口号等等

1.在@Bean上添加@Profile

  • 配置不同生产环境中的数据源信息
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
@Configuration
public class ConfigOfProfile {
// 测试环境(假设) :
public DataSource dataSourceTest() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}
// 开发环境
public DataSource dataSourceDev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}

// 生产环境
public DataSource dataSourceProd() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("1234");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");
dataSource.setDriverClass("com.jdbc.mysql.Driver");
return dataSource;
}
}

  • 通常情况下配置信息不会暴露在代码中而会写在配置文件中

    db.properties

1
2
3
db.user=root
db.password=1234
db.driverClass=com.mysql.jdbc.Driver
  • 获取.properties文件中数据的三种方法:

配置类首先要添加注解配置源文件

1
2
3
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile{
  1. 直接通过@Value获取配置文件中的数据
1
2
@Value("${db.user}")
private String user;
  1. 通过向@Bean下的方法用@ Value添加形参并获取配置文件中的数据
1
2
@Bean("test")
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
  1. 实现EmbeddedValueResolverAware接口并重写setEmbeddedValueResolver方法对占位符进行解析获取
1
2
3
4
5
6
7
8
9
public class xxx implements EmbeddedValueResolverAware{
private StringValueResolver valueResolver;
private String driverClass;

public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}

修改后的配置类为:

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
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile implements EmbeddedValueResolverAware {

@Value("${db.user}")
private String user;

private StringValueResolver valueResolver;

private String driverClass;

@Bean("test")
// test环境(假设) :
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}

@Bean("dev")
public DataSource dataSourceDev(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass(driverClass);
return dataSource;
}

@Bean("prod")
public DataSource dataSourceProd(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");

dataSource.setDriverClass(driverClass);
return dataSource;
}

public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}

  • 指定组件在哪个环境的情况下才能被注册到容器中

    只有当@Profile的环境被激活时才能将这个bean注册到容器中 默认环境为default

    1
      
  • 通过不同方式来激活不同的环境

    1. VM options处输入-Dspring.profiles.active=test
    2. 在Test类中注册ioc容器时通过setActiveProfiles激活环境
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class TestProfile {
    @Test
    public void test(){

    // 1.通过无参构造器创建一个ioc容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    // 2.设置需要激活的环境
    context.getEnvironment().setActiveProfiles("test","dev");
    // 3.设置主配置类
    context.register(ConfigOfProfile.class);
    // 4.启动刷新容器
    context.refresh();

    String[] beanDefinitionNames = context.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
    }
    }
    }

    注意:

    ​ 原来直接将配置类作为参数传入构造器初始化ioc容器

    1
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(xxxConfiguration.class);

    ​ 可以看一下ioc容器的有参构造器

    1
    2
    3
    4
    5
    public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
    }

    ​ 对比上面的代码:

1
2
3
4
5
6
7
8
//        1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("test","dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();

可以发现

​ 原来有参构造器直接将配置类进行注入而没有配置任何profile 这里只是插入一个设置活跃profile的过程

  • 测试结果为:
1
2
3
4
5
6
7
8
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
test
dev

可见 只有test和dev被注册入ioc 而prod并没有 因此可以看出只有当当前环境的profile与bean的@Profile内部参数的一致时,该bean才会被注册入ioc容器

2.在类上添加@Profile

1
2
3
4
5
@Profile("test")
@Configuration
public class MyConfig{
....
}

只有当ioc容器中设置的环境与@Profile(“test”)一致时才会注册入整个配置类

  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestProfile {
@Test
public void test(){
// 1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();

String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}

输出结果为:

1
2
3
4
5
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

自定义的配置bean都没有被注册进去

此时无论类中是否有@Profile(“dev”) 由于整个配置类都不会被注册进容器 因此也不会有bean被注册

3.没有标注@Profile的bean

在配置类可以被加载的情况下 非2中情况时 没有标注环境的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
@Configuration
@PropertySource("classpath:/db.properties")
public class ConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private StringValueResolver valueResolver;
private String driverClass;

public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
driverClass = resolver.resolveStringValue("${db.driverClass}");
}

@Bean("test")
@Profile("test")
// Test环境
public DataSource dataSourceTest(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
}

@Bean("dev")
@Profile("dev")
// 开发环境
public DataSource dataSourceDev(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
dataSource.setDriverClass(driverClass);
return dataSource;
}

@Bean("prod")
@Profile("prod")
// 生产环境
public DataSource dataSourceProd(@Value("{db.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/prod");

dataSource.setDriverClass(driverClass);
return dataSource;
}


// 给容器中添加一个不指定环境的bean



@Bean
public Person person(){
return new Person();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestProfile {
@Test
public void test(){
// 1.通过无参构造器创建一个ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2.设置需要激活的环境
context.getEnvironment().setActiveProfiles("dev");
// 3.设置主配置类
context.register(ConfigOfProfile.class);
// 4.启动刷新容器
context.refresh();

String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}

测试结果:

1
2
3
4
5
6
7
8
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
configOfProfile
dev
person