Spring注解驱动开发(十):利用@Autowired 实现依赖注入


这是我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 的注入模式是怎样的.

为了演示其作用,这里我定义了一个几个类:

  1. PersonDao 类,内部并没有任何 pojo 的处理逻辑
public class PersonDao {
}
  1. PersonService 类,内部利用 @Autowired 注入了 一个 PersonDao 的实例
public class PersonService {
    @Autowired
    public PersonDao personDao1;
}
  1. 定义一个配置类,向容器中注入 PersonService 和 PersonDao 的对象
@Configuration
public class AutowiredConfig {
   
   @Bean
   public PersonDao personDao() {
       return new PersonDao();
   }
   
   @Bean
   public PersonService personservice() {
       return new PersonService();
   }
}
  1. 测试类,查看是否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 注解不仅仅向上面介绍的标注在属性上,它还可以标注在其它位置:

  1. 构造器
@Service
public class UserService {
    private IUser user;
    @Autowired
    public UserService(IUser user) {
        this.user = user;
    }
}
  1. 方法
@Service
public class UserService {

   @Autowired
   public void test(IUser user) {
      user.say();
   }
}
  1. 参数上
@Service
public class UserService {
   private IUser user;
   public UserService(@Autowired IUser user) {
       this.user = user;
   }
}

其实综上所述的利用 @Autowired 进行依赖注入时主要有三种方式 : 属性注入, 构造方法注入, set 方法注入,其对比如下图所示:
在这里插入图片描述

⭐️ 其实 IDEA 和官网都不建议利用 @Autowired 进行属性注入而建议使用 构造器注入,其原因主要有以下几点

  1. 基于属性注入的方式, 违背单一职责原则(其实很多时候应该是以组合的形式获取依赖的对象)
  2. 基于属性注入的方式, 容易导致Spring 初始化失败(这是由对象初始化顺序决定的 : 静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired)
  3. 通过@Autowired 注入, 又因为是 ByType 注入, 因此有可能会出现两个相同的类型Bean(出现注入异常)

🐳 那么怎么解决这些问题呢?

  1. 如果一定要使用属性注入, 可以使用 @Resource 代替 @Autowired 注解
  2. 如果可能的话, 尽量使用构造注入
2.4 处理流程解析

讲了 @Autowired 注解的使用方式,你是否对它背后的注入原理有所好奇呢?

这里我带你再浏览一下 Spring Framework 对于 @Autowired 注解的处理流程:

  1. 采用构造器注入:
    因为是构造器注入,所以肯定是在创建对应的 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 注解的构造起的,之后根据指定内容将属性进行赋值

  1. 采用属性/方法注入

💡 其实我在我专栏的上一篇文章介绍 @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 注解有两个作用:

  1. 标注在字段或者参数上面,配合 @Autowired 在byName依赖注入时使用
  2. 标注在自定义注解上进行 “限定”(逻辑装配)

它只有一个属性,它的作用是在 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 就有这样的功能

这里我通过案例的方式向你展示以下做法:

  1. 想定义一个 @Quailifier 的派生注解
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier
public @interface UserGroup {
}
  1. 接着我们定义一个 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 方法
}
  1. 声明一个配置类
@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 对象
  1. 编写测试类:
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 实现优先级注入,即注入被次注解标注的实现类

  1. 这里我还是向配置类中声明两个 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;
}
  1. 编写测试类:
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. 总结

这里我先总结以下三个注解在依赖注入时候的流程:

  1. 当依赖注入的类型对象只有一个实例时: 注入的就是这个实例
  2. 当依赖注入的类型对象有多个实例时
    1. 如果标注了 @Qualifire , 以 @Qualifire 中的 Bean 名称寻找注入,同时它会使 @Primary 失效
    2. 如果为标注 @Qualifire,并且在每个类的实例上标注了 @Primary , 以此对象为主

这篇文章,我主要向你介绍了:

  • @Autowired 注解的源码,原理及其用法
  • @Qualifire 的源码及其用法
  • @Primary 的源码及其用法
    最后,我希望你看完本篇文章后,我希望你掌握如何使用利用@Autowired 实现依赖注入,也希望你指出我在文章中的错误点,希望我们一起进步,也希望你能给我的文章点个赞,原创不易!

参考文档:
为什么 Spring和IDEA 都不推荐使用 @Autowired 注解

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值