文章目录
这是我Spring Frame 专栏的第十篇文章,在 Spring注解驱动开发(九):利用@Value与@PropertySource实现外部化配置注入 这篇文章中,我向你详细介绍了如何利用@Value与@PropertySource将配置文件的属性注入到容器的 Bean 中,如果你未读过那篇文章,但是对内容感兴趣的话,我建议你去阅读一下
1. 背景介绍
在我们进行 Web 应用开发的时候,大概率会使用 MVC 的三层架构,我们在表现层会调用服务层的服务,通常需要在控制器内组合对应的服务类(XXXService),而这些 Service 通常是全局唯一的,那我们在使用 Spring Framework 的时候,有没有什么方法可以通过 IoC 容器控制这些 Service 的声明周期,不再像传统的方式(在控制器内部 new 出 Service 的实例),而是从 IoC 容器中获取对应的单例 Service 对象呢 ?
💡 Spring Framework 为我们提供了 @Autowired(依赖注入注解)、@Qualifier、@Primary
三个注解来配合实现 依赖注入(将我们想要的对象自动注入到对应的调用类实例中)
2. @Autowired 详解
@Autowired
注解在 Spring 注解开发中发挥着及其重要的作用,这里我就从原码,用法,原理三个角度和你分享一下这个注解的强大之处
2.1 源码解析
按照惯例,我们还是从 @Autowired 的源码入手,读一读注释和核心参数:
// Marks a constructor, field, setter method, or config method as to be autowired by Spring's dependency injection facilities
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
}
它的注释很长,这里我只是贴出了它的作用,从源码中我们可以得到以下内容
- 作用: 将构造函数、字段、设置方法或配置方法标记为由 Spring 的依赖注入工具自动装配
- 标注位置: 构造器,方法,参数,注解,属性
required
属性: 标记该注解的字段是否必须进行依赖注入
💡 这里我强烈的建议你看一下它的类注释,里面详细的介绍了 @Autowired 标注在各个位置的意义和注意事项
这里还有一个使用 @Auworeid 的核心点注意: 其默认是
byType
注入的,但当存在多个同类型的待注入对象时,它才会byName
注入
2.2 案例实战
上一小结我带你浏览了以下 @Autowired 的源码,这节我带你做以下案例,并且看一下 @Autowired 的注入模式是怎样的.
为了演示其作用,这里我定义了一个几个类:
PersonDao
类,内部并没有任何 pojo 的处理逻辑
public class PersonDao {
}
PersonService
类,内部利用 @Autowired 注入了 一个 PersonDao 的实例
public class PersonService {
@Autowired
public PersonDao personDao1;
}
- 定义一个配置类,向容器中注入 PersonService 和 PersonDao 的对象
@Configuration
public class AutowiredConfig {
@Bean
public PersonDao personDao() {
return new PersonDao();
}
@Bean
public PersonService personservice() {
return new PersonService();
}
}
- 测试类,查看是否personDao 是否成功注入到了 PersonService
public class AutowiredMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
String[] definitionNames = context.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
PersonService personService = context.getBean(PersonService.class);
PersonDao personDao = context.getBean(PersonDao.class);
System.out.println("personService.personDao1 == personDao: "+(personDao == personService.personDao1));
}
}
我们运行一下测试类,查看一下执行结果:
通过结果可以知道,personDao 被成功的注入到了 personService 中
你是否思考过: 如果容器中有多个 PersonDao 的实例,会出现什么状况?
这里我修改一下配置类,向容器中注入两个 PersonDao 的实例 ,分别叫做 personDao1,personDao2:
@Configuration
public class AutowiredConfig {
@Bean
public PersonDao personDao1() {
return new PersonDao();
}
@Bean
public PersonDao personDao2() {
return new PersonDao();
}
@Bean
public PersonService personservice() {
return new PersonService();
}
}
接着我们修改以下测试类,看一下 PersonService 中注入的到底是哪个 PersonDao
public class AutowiredMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
String[] definitionNames = context.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
PersonService personService = context.getBean(PersonService.class);
PersonDao personDao1 = context.getBean("personDao1",PersonDao.class);
PersonDao personDao2 = context.getBean("personDao2",PersonDao.class);
System.out.println("personService.personDao1 == personDao1: "+(personDao1 == personService.personDao1));
System.out.println("personService.personDao1 == personDao2: "+(personDao2 == personService.personDao1));
}
}
我们运行一下测试类,查看一下结果
我们发现依赖注入成功了,但是为什么注入的是名称为 personDao1 的 Bean 呢?
⭐️ 其实如果你仔细观察会发现 personService 里面的 PersonDao 的属性名称就是 personDao1
,这里就验证了我再上一节所说的: @Autowired 默认注入是 byType
,如果存在多个同类型后,是 byName
的
这里我把 PersonService 里面的属性名换成 personDao2 ,查看一下测试结果:
public class PersonService {
@Autowired
public PersonDao personDao2;
}
这是发现注入的就是名称为 personDao2
的对象了
这里你是否又会好奇,如果 personService 里面的属性名称既不是 personDao1 也不是 personDao2 呢?
这里我再次修改 PersonService 的 PersonDao属性名称为personDao3
,这时候运行测试代码:
public class PersonService {
@Autowired
public PersonDao personDao3;
}
我们发现会报出 NoUniqueBeanDefinitionException
的异常,它会说找到两个满足类型的 Bean : personDao1,personDao2,不是唯一存在的
那这个问题该如何解决呢?这时候可以借助 @Qualifier
和 @Primary
,这个问题我们在下一小节再聊
到现在我觉得你应该能了解 @Autowired 的作用了.
2.3 扩展阅读
其实 @Autowired 注解不仅仅向上面介绍的标注在属性上,它还可以标注在其它位置:
- 构造器
@Service
public class UserService {
private IUser user;
@Autowired
public UserService(IUser user) {
this.user = user;
}
}
- 方法
@Service
public class UserService {
@Autowired
public void test(IUser user) {
user.say();
}
}
- 参数上
@Service
public class UserService {
private IUser user;
public UserService(@Autowired IUser user) {
this.user = user;
}
}
其实综上所述的利用 @Autowired 进行依赖注入时主要有三种方式 : 属性注入, 构造方法注入, set 方法注入
,其对比如下图所示:
⭐️ 其实 IDEA 和官网都不建议利用 @Autowired 进行属性注入而建议使用 构造器注入,其原因主要有以下几点
- 基于属性注入的方式, 违背单一职责原则(其实很多时候应该是以组合的形式获取依赖的对象)
- 基于属性注入的方式, 容易导致Spring 初始化失败(这是由对象初始化顺序决定的 : 静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired)
- 通过@Autowired 注入, 又因为是 ByType 注入, 因此有可能会出现两个相同的类型Bean(出现注入异常)
🐳 那么怎么解决这些问题呢?
- 如果一定要使用属性注入, 可以使用
@Resource
代替@Autowired
注解- 如果可能的话, 尽量使用构造注入
2.4 处理流程解析
讲了 @Autowired 注解的使用方式,你是否对它背后的注入原理有所好奇呢?
这里我带你再浏览一下 Spring Framework 对于 @Autowired 注解的处理流程:
- 采用构造器注入:
因为是构造器注入,所以肯定是在创建对应的 Bean 实例的时候将属性注入的,其核心方法为AbstractAutowireCapableBeanFactory#createBeanInstance
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// Make sure bean class is actually resolved at this point.
Class<?> beanClass = resolveBeanClass(mbd, beanName);
if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName);
}
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// Shortcut when re-creating the same bean...
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized (mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
if (autowireNecessary) {
return autowireConstructor(beanName, mbd, null, null);
}
else {
return instantiateBean(beanName, mbd);
}
}
// core --> Candidate constructors for autowiring? ---> 选择最佳的构造器
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
return autowireConstructor(beanName, mbd, ctors, null);
}
// No special handling: simply use no-arg constructor.
return instantiateBean(beanName, mbd);
}
从源码可以看到它会调用 determineConstructorsFromBeanPostProcessors
选择合适的构造器(带 @Autowired 的)
protected Constructor<?>[] determineConstructorsFromBeanPostProcessors(@Nullable Class<?> beanClass, String beanName)
throws BeansException {
if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
Constructor<?>[] ctors = ibp.determineCandidateConstructors(beanClass, beanName);
if (ctors != null) {
return ctors;
}
}
}
}
return null;
}
其本质就是调用 SmartInstantiationAwareBeanPostProcessor #determineCandidateConstructors
来选择构造起的
说了这些源码,貌似也没有看到处理标注 @Autowired 注解的构造器:
这里由于篇幅原因,我并不展开说了,给你留个引子: 你可以自己去阅读AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
这个方法,它就是用来选择标注了 @Autowired 注解的构造起的,之后根据指定内容将属性进行赋值
- 采用属性/方法注入
💡 其实我在我专栏的上一篇文章介绍 @Value 的时候介绍了其注解的处理流程,@Autowired 也是一样的,我建议你看一下那块内容,就能理解 @Autowired 属性注入的本质了,这里我就不重复介绍了
3. @Qualifier 详解
在上一小结的 2.2 案例实战
的末尾我提出了一个问题是和 @Qualifier
相关的,这里我就来介绍一下这个注解的作用以及解决上一小节的方案
3.1 @Qualifier 源码解析
按照惯例,这里我还是先带你阅读一下源码,通过注释了解这个注解的作用
/**
* This annotation may be used on a field or parameter as a qualifier for
* candidate beans when autowiring. It may also be used to annotate other
* custom annotations that can then in turn be used as qualifiers.
*
*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
从注释可以看到 @Qualifier 注解有两个作用:
- 标注在字段或者参数上面,配合 @Autowired 在
byName
依赖注入时使用 - 标注在自定义注解上进行 “限定”(逻辑装配)
它只有一个属性,它的作用是在 byName
依赖注入时想要注入的 Bean 的名字
⭐️ 听到这里,你应该知道我上面那个问题的答案了,利用这个注解就可以解决
NoUniqueBeanDefinitionException
问题,但是你可能对第二个用途不太理解,在下面两小节我会分别讲述这两种用法
3.2 @Qualifier 配合 @Autowired
这是上一小节报错的那张图,它会说找到两个满足类型的 Bean : personDao1,personDao2,不是唯一存在的(此时我们PersonService 里的 PersonDao对应的对应的对象名称叫做personDao3)
我们尝试修改PersonService,为这个字段添加一个 @Qualifier
:
public class PersonService {
@Autowired
@Qualifier("personDao1") // 此处为指定依赖注入的 Bean 名称
public PersonDao personDao3;
}
运行测试类:
public class AutowiredMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
String[] definitionNames = context.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
PersonService personService = context.getBean(PersonService.class);
PersonDao personDao1 = context.getBean("personDao1",PersonDao.class);
PersonDao personDao2 = context.getBean("personDao2",PersonDao.class);
System.out.println("personService.personDao3 == personDao1: "+(personDao1 == personService.personDao3));
System.out.println("personService.personDao3 == personDao2: "+(personDao2 == personService.personDao3));
}
}
我们发现 Bean 名称为 perrsonDao1
的对象被成功注入了。
到这里,我相信你就能理解 @Qualifier 的第一个作用是配合 @Autowired 在 byName 注入时指定注入的Bean名称了
3.3 @Quailifier 扩展介绍
我在介绍注解的时候还介绍了 @Quailifier 还有一个 逻辑分组 的功能,这是什么意思呢?
💡 这里我给你举一个例子,加入我们的 Ioc 容器中注入了很多个 User 以及子类对象,如果我们想把它们中的指定一部分注入到集合中,这时候我们就可以把这一部分对象称为一个 逻辑分组,而 @Quailifier 就有这样的功能
这里我通过案例的方式向你展示以下做法:
- 想定义一个 @Quailifier 的派生注解
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier
public @interface UserGroup {
}
- 接着我们定义一个 User 类
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 省略 getter,setter,toString 方法
}
- 声明一个配置类
@Configuration
public class QualifierConfig {
/**
* 处于@Qualifier逻辑组
* @return
*/
@Bean
@Qualifier
public static User user1() {
return createUser("user1");
}
/**
* 处于@Qualifier逻辑组
* @return
*/
@Bean
@Qualifier
public static User user2() {
return createUser("user2");
}
/**
* 处于@UserGroup逻辑组
* @return
*/
@Bean
@UserGroup
public static User user3() {
return createUser("user3");
}
/**
* 处于@UserGroup逻辑组
* @return
*/
@Bean
@UserGroup
public static User user4() {
return createUser("user4");
}
private static User createUser(String name) {
User user = new User();
user.setName(name);
return user;
}
/**
* Qualifier 分组
*/
@Autowired
@Qualifier
public Collection<User> qualifiedUsers;
/**
* UserGroup 逻辑分组
*/
@Autowired
@UserGroup
public Collection<User> groupedUsers;
}
这个类中分别注入了四个 User:
- user1,user2 标注了 @Qualifier
- user3,user4 标注了 @UserGroup
并且有两个集合类 - qualifiedUsers: 收集所有标注了 @Qualifier 的 User 对象
- groupedUsers:收集所有标注了 @UserGroup的 User 对象
- 编写测试类:
public class QualifierMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(QualifierConfig.class);
QualifierConfig bean = context.getBean(QualifierConfig.class);
System.out.println(bean.qualifiedUsers);
System.out.println(bean.groupedUsers);
}
}
我们按照逻辑分组的概念,来看看两个集合分别注入了几个对象:
- qualifiedUsers : 由于标注了@Qualifier , 那么它应该收集user1,user2,user3,user4 四个对象(@UserGroup 也是 @Qualifier 派生而来的,所以会收集 user3,user4)
- groupedUsers: 由于标注了 @UserGroup ,那么它应该收集 user3,user4 两个对象
接下来,我们运行测试类,看看和我们的预期是否相同:
可以看到注入的对象和我们的预期是相同的,到这里我相信你应该明白了如何利用 @Qualifier 配合自定义注解实现逻辑分组了
4. @Primary 详解
4.1 源码解析
按照惯例,我还是先带你看一眼源码注释,并总结以下它的作用:
// Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}
可以看到这个注解很干净,并没有属性信息,它的作用也很明确: 在多个候选 Bean 中偏向一个 Bean
4.2 优先级注入
这一小节我带你使用以下 @Primary 注解
在Spring Framework 中,对同一个接口可能会有几种不同的实现类,我们可以利用 @Primary 实现优先级注入,即注入被次注解标注的实现类
- 这里我还是向配置类中声明两个 PersonDao 对象实例,一个 PersonService
@Configuration
public class AutowiredConfig {
@Primary
@Bean
public PersonDao personDao1() {
return new PersonDao();
}
@Bean
public PersonDao personDao2() {
return new PersonDao();
}
@Bean
public PersonService personservice() {
return new PersonService();
}
}
可以看到我在 personDao1 上标注了 @Primary ,这个对象在依赖注入时就会被优先选择
2. 修改 PersonService 的 PersonDao 属性名为 personDao2
public class PersonService {
@Autowired
public PersonDao personDao2;
}
- 编写测试类:
public class AutowiredMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
String[] definitionNames = context.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
PersonService personService = context.getBean(PersonService.class);
PersonDao personDao1 = context.getBean("personDao1",PersonDao.class);
PersonDao personDao2 = context.getBean("personDao2",PersonDao.class);
System.out.println("personService.personDao2 == personDao1: "+(personDao1 == personService.personDao2));
System.out.println("personService.personDao2 == personDao2: "+(personDao2 == personService.personDao2));
}
}
先思考一下: 如果没有 @Primary 时,PersonService 中注入的应该是谁呢?
根据前面的讲解,先 byType 后 byName ,应该是 personDao2 实例
运行查看一下结果:
结果和没有 @Primary 时是不同的,这就意味着在进行依赖注入时,有多个同一个类的对象实例时,优先会注入被 @Primary 标注的实例
❓ 那么问题来了,@Qualifire 和 @Primary 谁的优先级高呢?
这里我们修改一下 PersonService
public class PersonService {
@Autowired
@Qualifier("personDao2")
public PersonDao personDao2;
}
这时候注入的是 personDao1 还是 personDao2 呢?
运行测试类,查看结果:
可以看到注入的是 personDao2,这说明,存在多个同类型的实例时: @Qualifire 优先级高于 @Primary
那么它俩真的只是优先级高低关系吗?
我们继续修改 PersonService :
public class PersonService {
@Autowired
@Qualifier("personDao4")
public PersonDao personDao2;
}
这时候由于 IoC 容器中并没有 @Qualifier的属性值为
personDao4
的 Bean ,这是如果只是优先级关系,会找到标注了 @Primary 的 personDao1,但事实真的如此吗?
运行测试类,查看结果:
但是运行报错了:NoSuchBeanDefinitionException
异常,在 IoC 容器中没有找到名称为 personDao4
的对象,这就意味着 @Qualifire 会使对应的 @Primary 失效!!!
到这里我相信你应该了解 @Primary 的用法了
5. 总结
这里我先总结以下三个注解在依赖注入时候的流程:
- 当依赖注入的类型对象只有一个实例时: 注入的就是这个实例
- 当依赖注入的类型对象有多个实例时
- 如果标注了 @Qualifire , 以 @Qualifire 中的 Bean 名称寻找注入,同时它会使 @Primary 失效
- 如果为标注 @Qualifire,并且在每个类的实例上标注了 @Primary , 以此对象为主
这篇文章,我主要向你介绍了:
- @Autowired 注解的源码,原理及其用法
- @Qualifire 的源码及其用法
- @Primary 的源码及其用法
最后,我希望你看完本篇文章后,我希望你掌握如何使用利用@Autowired 实现依赖注入,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!