Spring注解驱动开发(三)-属性赋值和自动装配

1.属性赋值

1.1 @Value

使用@Value注解赋值;

  1. 基本数值
  2. 可以写Spring的EL表达式; #{}
  3. 可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值)

1.2 @PropertySource

  • 使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;

  • 加载完外部的配置文件以后使用${}取出配置文件的值

  • @PropertySource(value={“classpath:/person.properties”}) 数组类型,可以支持一次性引入多个文件。

  • 另外@PropertySource 是一个可重复的注解,可以使用@PropertySources注解引入多个@PropertySource标注的properties配置文件
1
2
3
4
5
6
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources {
PropertySource[] value();
}

测试代码如下:

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
70
//实体类信息
package com.rocklei123.bean;

import org.springframework.beans.factory.annotation.Value;

/**
* @ClassName: Person
* @Author: rocklei123
* @Date: 2018/12/1 21:27
* @Description: TODO
* @Version 1.0
*/
public class Person {
@Value(value = "张三")
private String name;
@Value(value = "#{20-2}")
private Integer age;

@Value(value = "${person.nickName}")
private String nickName;

public Person() {
System.out.println("Person默认构造方法");
}

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

//配置类信息
package com.rocklei123.config;

import com.rocklei123.bean.Person;
import org.springframework.context.annotation.*;

@PropertySource(value = {"classpath:/person.properties"})
@Configuration
public class MainConfigOfPropertyValues {

@Bean
public Person person() {
return new Person();
}
}
//测试类信息
public class IocTestOfPropertyValues {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class);

public void printBean(ApplicationContext applicationContext) {
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object o = applicationContext.getBean(beanDefinitionName);
System.out.println(o);
}
}

@Test
public void testValue() {
printBean(applicationContext);
Environment environment = applicationContext.getEnvironment();
String nickName = environment.getProperty("person.nickName");
System.out.println("环境信息中property=" + nickName);
}
}

测试结果:

1
2
Person{name='张三', age=18, nickName='小李四'}
环境信息中property=小李四

2.自动装配

自动装配;
Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值;

2.1 @Autowired、@Qualifier、@Primary

2.1.1 @Autowired

2.1.1.1 概念性总结

  • 概念:Spring 2.5 引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。

  • 原理:其实在启动spring IOC容器时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。

  • 注意事项:

    • 1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值
    • 2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查 applicationContext.getBean(“bookDao”)所以容器中有多个相同组件时,名称不应该乱起。
    • 3)、自动装配默认一定要将属性赋值好,没有就会报错;可以使用@Autowired(required=false);默认required=true

2.1.1.2 默认注入情况测试

默认注入情况:名字默认是类名首字母小写

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
//service 层代码,自动注入dao层
@Service
public class BookService {
@Autowired
BookDao bookDao;

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

//dao层代码,加入一个默认名称区别
@Repository
public class BookDao {
private String nameFlag = "1";

public BookDao() {
}

public BookDao(String nameFlag) {
this.nameFlag = nameFlag;
}

public String getNameFlag() {
return nameFlag;
}

public void setNameFlag(String nameFlag) {
this.nameFlag = nameFlag;
}

@Override
public String toString() {
return "BookDao{" +
"nameFlag='" + nameFlag + '\'' +
'}';
}
}

//配置类,向容器中注入另一个bookDao,名称为BookDao2
@ComponentScan(value = {"com.rocklei123.service", "com.rocklei123.controller", "com.rocklei123.repository"})
@Configuration
public class MainConfigOfAutowired {

@Bean(value = "bookDao2")
public BookDao bookDao2(BookDao bookDao) {
BookDao bookDao2 = new BookDao();
bookDao2.setNameFlag("2");
return bookDao2;
}
}
//测试类
public class IocTestOfAutowired {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);

@Test
public void testValue() {
BookService bean = applicationContext.getBean(BookService.class);
System.out.println(bean);
}
}

测试结果:通过上文注意事项可知,Spring容器注入时会先通过类型找到两个BookDao类型的对象,第二次会通过 默认是类名首字母小写在查找一次。故Service层注入的BookDao对象应该为nameFlag = “1”; 的对象

1
2
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4aa8f0b4: startup date [Sat Dec 15 18:56:29 CST 2018]; root of context hierarchy
BookService{bookDao=BookDao{nameFlag='1'}}

如果此时修改测试类使用IOC容器按类型获取BookDao此时,因为容器中有两个BookDao类型的对象,当打印时就会报错。

1
2
3
4
5
6
7
8
@Test
public void testValue() {
BookDao bean = applicationContext.getBean(BookDao.class);
System.out.println(bean);
}

//异常信息
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.rocklei123.repository.BookDao' available: expected single matching bean but found 2: bookDao,bookDao2

2.1.1.3 @Autowired(required=false)

自动装配默认一定要将属性赋值好,没有就会报错;可以使用@Autowired(required=false);

1
2
3
4
5
6
7
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
默认required 为true

测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//业务层代码,自动注入Dao层对象,required = false
public class BookService {
@Qualifier(value = "bookDao2")
@Autowired(required = false)
BookDao bookDao;
}
//将BookDao不注入到容器中

//虽然容器中不含有BookDao,通过测试类打印业务层代码时,也不会报错。
@Test
public void testValue() {
BookService bean = applicationContext.getBean(BookService.class);
System.out.println(bean);
}

//测试结果
BookService{bookDao=null}

2.1.1.4 @Autowired:构造器,参数,方法,属性

@Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值

默认情况说明:

  • @Bean标注的方法创建对象的时候,方法参数的值从容器中获取
  • 默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作

@Autowired标注说明:

  • 1)、[标注在方法位置]:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配
  • 2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取.
  • 3)、放在参数位置:

2.1.2 @Qualifier

​ @Qualifier(“XXX”) Spring的Bean注入配置注解,该注解指定注入的Bean的名称,Spring框架使用byName方式寻找合格的bean,这样就消除了byType方式产生的歧义。

​ @Qualifier(“bookDao”):使用@Qualifier指定需要装配的组件的id,而不是使用属性名。@Qualifier需要和 @Autowired一起使用。

1
2
3
4
5
6
7
public class BookService {
@Qualifier(value = "bookDao2") //指定装配组件的Id为bookDao2
@Autowired
BookDao bookDao;
}
//测试结果
BookService{bookDao=BookDao{nameFlag='2'}}

2.1.3 @Primary

​ @Primary:让Spring进行自动装配的时候,默认使用首选的bean;也可以继续使用@Qualifier指定需要装配的bean的名字。

​ 通过下面的示例也可以看出@Primary和@Qualifier 在一定程度上有所冲突,如果我们service层代码使用@Qualifier指定注入名称,即使在相同类型的其他Bean 上标注@Primary,Spring注入Service层的也是根据@Qualifier标注注入的。

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
@Service
public class BookService {

@Qualifier(value = "bookDao")
@Autowired(required = false)
BookDao bookDao;

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

@ComponentScan(value = {"com.rocklei123.service", "com.rocklei123.controller", "com.rocklei123.repository"})
@Configuration
public class MainConfigOfAutowired {

@Primary
@Bean(value = "bookDao2")
public BookDao bookDao2(BookDao bookDao) {
BookDao bookDao2 = new BookDao();
bookDao2.setNameFlag("2");
return bookDao2;
}
}

//测试代码
public class IocTestOfAutowired {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAutowired.class);

public void printBean(ApplicationContext applicationContext) {
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object o = applicationContext.getBean(beanDefinitionName);
System.out.println(o);
}
}

@Test
public void testValue() {
printBean(applicationContext);
System.out.println("-----------------------------------");
BookService bean = applicationContext.getBean(BookService.class);
System.out.println(bean);
}
}

测试结果:可以看出Spring容器加载了两个BookDao类型的bean,向Serveice层注入的是 @Qualifier(value = “bookDao”)标注的。

1
2
3
4
5
6
7
com.rocklei123.config.MainConfigOfAutowired$$EnhancerBySpringCGLIB$$7d34398f@37e547da
BookService{bookDao=BookDao{nameFlag='1'}}
com.rocklei123.controller.BookController@5db45159
BookDao{nameFlag='1'}
BookDao{nameFlag='2'}
-----------------------------------
BookService{bookDao=BookDao{nameFlag='1'}}

2.2 @Resource(JSR250)和@Inject(JSR330)

Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]

2.2.1 @Resource:

  • 可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的;
  • 没有能支持@Primary功能
  • 没有支持@Autowired(reqiured=false);
1
2
3
4
5
@Service
public class BookService {
@Resource(name = "bookDao2")
BookDao bookDao;
}

2.2.2 @Inject:

​ @Inject支持构造函数、方法和字段注解,也可能使用于静态实例成员。可注解成员可以是任意修饰符(private,package-private,protected,public)。注入顺序:构造函数、字段,然后是方法。父类的字段和方法注入优先于子类的字段和方法,同一类中的字段和方法是没有顺序的。

  • 需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;但是支持@Primary
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

2.3 @Autowired 和 @Resource、@Inject 的区别

相同点

  1. @Autowired和@Inject基本是一样的,因为两者都是使用AutowiredAnnotationBeanPostProcessor来处理依赖注入。但是@Resource是个例外,它使用的是CommonAnnotationBeanPostProcessor来处理依赖注入。当然,两者都是BeanPostProcessor。
  2. 三个注解都能实现自动装配功能

不同点

  1. 注解定义不同

    • @Autowired:Spring定义的,脱离Spring框架就没有了;

    • @Resource、@Inject都是java规范,其他IOC框架也会支持。

  2. 默认注入方式不同

    • @Autowired和@Inject: 默认 autowired by type,可以通过@Qualifier 显式指定;
    • @Resource:默认 autowired by field name,如果 autowired by field name失败,会退化为 autowired by type;可以 通过@Qualifier 显式指定 autowired by qualifier name;如果 autowired by qualifier name失败,会退化为 autowired by field name。
  3. 功能性差异

    • @Resource 没有能支持@Primary功能;没有支持@Autowired(reqiured=false)功能;
    • @Inject 虽然支持@Primary功能功能,但是不支持@Autowired(reqiured=false)功能;
    • @Autowired支持@Primary功能,@Autowired(reqiured=false)功能;

2.4 自定义组件注入Spring容器底层的一些组件

2.4.1 xxxAware介绍

自定义组件实现xxxAware接口,在创建对象的时候,会调用接口规定的方法注入相关组件;xxxxAware接口把Spring底层一些组件注入到自定义的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
package com.rocklei123.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringValueResolver;

/**
* @ClassName: Apple
* @Author: rocklei123
* @Date: 2018/12/15 20:46
* @Description: 自定义组件注入Spring容器底层的一些组件
* @Version 1.0
*/
@Component
public class Apple implements ApplicationContextAware, BeanNameAware, EnvironmentAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;

@Override
public void setBeanName(String s) {
System.out.println("当前Bean的名称:" + s);
}

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

@Override
public void setEnvironment(Environment environment) {
System.out.println("当前person.properties文件中的昵称名:" + environment.getProperty("person.nickName"));
}

@Override
public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
String s = stringValueResolver.resolveStringValue("我的操作系统类型${os.name},我的年龄#{26+1}");
System.out.println(s);
}
}

//配置类
@PropertySource(value = {"classpath:/person.properties"})
@ComponentScan(value = {"com.rocklei123.bean", "com.rocklei123.service", "com.rocklei123.controller", "com.rocklei123.repository"})
@Configuration
public class MainConfigOfAutowired {

@Primary
@Bean(value = "bookDao2")
public BookDao bookDao2(BookDao bookDao) {
BookDao bookDao2 = new BookDao();
bookDao2.setNameFlag("2");
return bookDao2;
}
}
//测试结果
当前Bean的名称:apple
当前person.properties文件中的昵称名:小李四
我的操作系统类型Windows 10,我的年龄27
传入的IOC:org.springframework.context.annotation.AnnotationConfigApplicationContext@1e81f4dc: startup date [Sat Dec 15 20:57:18 CST 2018]; root of context hierarchy

2.4.2 xxxAware底层原理

xxxAware原理:功能使用xxxProcessor,通过后置处理器功能实现;
如: ApplicationContextAware==》ApplicationContextAwareProcessor;

以ApplicationContextAwareProcessor为例源码:

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
package org.springframework.context.support;

class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ConfigurableApplicationContext applicationContext;
private final StringValueResolver embeddedValueResolver;

public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null && (bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware || bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}

if (acc != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
ApplicationContextAwareProcessor.this.invokeAwareInterfaces(bean);
return null;
}
}, acc);
} else {
this.invokeAwareInterfaces(bean);
}

return bean;
}

private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware)bean).setEnvironment(this.applicationContext.getEnvironment());
}

if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware)bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}

if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware)bean).setResourceLoader(this.applicationContext);
}

if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware)bean).setApplicationEventPublisher(this.applicationContext);
}

if (bean instanceof MessageSourceAware) {
((MessageSourceAware)bean).setMessageSource(this.applicationContext);
}

if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware)bean).setApplicationContext(this.applicationContext);
}
}

}

public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}

3 希望大家手动敲一遍代码,会收获颇丰!

4.欢迎关注米宝窝,持续更新中,谢谢!

米宝窝 https://rocklei123.github.io/

-------------本文结束感谢您的阅读-------------
欢迎持续关注米宝窝,定期更新谢谢! https://rocklei123.github.io/
欢迎持续关注我的CSDN https://blog.csdn.net/rocklei123
rocklei123的技术点滴
熬夜写博客挺辛苦的,生怕猝死,所以每当写博客都带着听诊器,心脏一有异响,随时按Ctrl+S。
rocklei123 微信支付

微信支付

rocklei123 支付宝

支付宝