一, 自动装配
当 Spring 装配 Bean 属性时, 有时候非常明确, 就是需要将某个 Bean 的引用装配给指定属性. 比如, 如果我们的应用上下文中只有一个 org.mybatis.spring.SqlSessionFactoryBean 类型的 Bean, 那么任意一个依赖 SqlSessionFactoryBean 的其他 Bean 就是需要这个 Bean. 毕竟这里只有一个 SqlSessionFactoryBean 的 Bean.
为了应对这种明确的装配场景, Spring 提供了自动装配 (autowiring). 与其显式的装配 Bean 属性, 为何不让 Spring 识别出可以自动装配的场景.
当涉及到自动装配 Bean 的依赖关系时, Spring 有多种处理方式. 因此, Spring 提供了 4 种自动装配策略.
- public interface AutowireCapableBeanFactory{
- // 无需自动装配
- int AUTOWIRE_NO = 0;
- // 按名称自动装配 bean 属性
- int AUTOWIRE_BY_NAME = 1;
- // 按类型自动装配 bean 属性
- int AUTOWIRE_BY_TYPE = 2;
- // 按构造器自动装配
- int AUTOWIRE_CONSTRUCTOR = 3;
- // 过时方法, Spring3.0 之后不再支持
- @Deprecated
- int AUTOWIRE_AUTODETECT = 4;
- }
Spring 在 AutowireCapableBeanFactory 接口中定义了这几种策略. 其中, AUTOWIRE_AUTODETECT 被标记为过时方法, 在 Spring3.0 之后已经不再支持.
1,byName
它的意思是, 把与 Bean 的属性具有相同名字的其他 Bean 自动装配到 Bean 的对应属性中. 听起来可能比较拗口, 我们来看个例子.
首先, 在 User 的 Bean 中有个属性 Role myRole, 再创建一个 Role 的 Bean, 它的名字如果叫 myRole, 那么在 User 中就可以使用 byName 来自动装配.
- public class User{
- private Role myRole;
- }
- public class Role {
- private String id;
- private String name;
- }
上面是 Bean 的定义, 再看配置文件.
- <bean id="myRole" class="com.viewscenes.netsupervisor.entity.Role">
- <property name="id" value="1001"></property>
- <property name="name" value="管理员"></property>
- </bean>
- <bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="byName"></bean>
如上所述, 只要属性名称和 Bean 的名称可以对应, 那么在 user 的 Bean 中就可以使用 byName 来自动装配. 那么, 如果属性名称对应不上呢?
2,byType
是的, 如果不使用属性名称来对应, 你也可以选择使用类型来自动装配. 它的意思是, 把与 Bean 的属性具有相同类型的其他 Bean 自动装配到 Bean 的对应属性中.
- <bean class="com.viewscenes.netsupervisor.entity.Role">
- <property name="id" value="1001"></property>
- <property name="name" value="管理员"></property>
- </bean>
- <bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="byType"></bean>
还是上面的例子, 如果使用 byType,Role Bean 的 ID 都可以省去.
3,constructor
它是说, 把与 Bean 的构造器入参具有相同类型的其他 Bean 自动装配到 Bean 构造器的对应入参中. 值的注意的是, 具有相同类型的其他 Bean 这句话说明它在查找入参的时候, 还是通过 Bean 的类型来确定.
构造器中入参的类型为 Role
- public class User{
- private Role role;
- public User(Role role) {
- this.role = role;
- }
- }
- <bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="constructor"></bean>
4,autodetect
它首先会尝试使用 constructor 进行自动装配, 如果失败再尝试使用 byType. 不过, 它在 Spring3.0 之后已经被标记为 @Deprecated.
5, 默认自动装配
默认情况下, default-autowire 属性被设置为 none, 标示所有的 Bean 都不使用自动装配, 除非 Bean 上配置了 autowire 属性. 如果你需要为所有的 Bean 配置相同的 autowire 属性, 有个办法可以简化这一操作. 在根元素 Beans 上增加属性 default-autowire="byType".
<beans default-autowire="byType">
Spring 自动装配的优点不言而喻. 但是事实上, 在 Spring xml 配置文件里的自动装配并不推荐使用, 其中笔者认为最大的缺点在于不确定性. 或者除非你对整个 Spring 应用中的所有 Bean 的情况了如指掌, 不然随着 Bean 的增多和关系复杂度的上升, 情况可能会很糟糕.
二, Autowired
从 Spring2.5 开始, 开始支持使用注解来自动装配 Bean 的属性. 它允许更细粒度的自动装配, 我们可以选择性的标注某一个属性来对其应用自动装配.
Spring 支持几种不同的应用于自动装配的注解.
Spring 自带的 @Autowired 注解.
JSR-330 的 @Inject 注解.
JSR-250 的 @Resource 注解.
我们今天只重点关注 Autowired 注解, 关于它的解析和注入过程, 请参考笔者 Spring 源码系列的文章. Spring 源码分析 (二)bean 的实例化和 IoC 依赖注入 https://www.jianshu.com/p/00f1a6739a9e
使用 @Autowired 很简单, 在需要注入的属性加入注解即可.
- @Autowired
- UserService userService;
不过, 使用它有几个点需要注意.
1, 强制性
默认情况下, 它具有强制契约特性, 其所标注的属性必须是可装配的. 如果没有 Bean 可以装配到 Autowired 所标注的属性或参数中, 那么你会看到 NoSuchBeanDefinitionException 的异常信息.
- public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
- Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
- // 查找 Bean
- Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
- // 如果拿到的 Bean 集合为空, 且 isRequired, 就抛出异常.
- if (matchingBeans.isEmpty()) {
- if (descriptor.isRequired()) {
- raiseNoSuchBeanDefinitionException(type, "", descriptor);
- }
- return null;
- }
- }
看到上面的源码, 我们可以得到这一信息, Bean 集合为空不要紧, 关键 isRequired 条件不能成立, 那么, 如果我们不确定属性是否可以装配, 可以这样来使用 Autowired.
- @Autowired(required=false)
- UserService userService;
2, 装配策略
我记得曾经有个面试题是这样问的: Autowired 是按照什么策略来自动装配的呢?
关于这个问题, 不能一概而论, 你不能简单的说按照类型或者按照名称. 但可以确定的一点的是, 它默认是按照类型来自动装配的, 即 byType.
默认按照类型装配
关键点 findAutowireCandidates 这个方法.
- protected Map<String, Object> findAutowireCandidates(
- String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
- // 获取给定类型的所有 bean 名称, 里面实际循环所有的 beanName, 获取它的实例
- // 再通过 isTypeMatch 方法来确定
- String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
- this, requiredType, true, descriptor.isEager());
- Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length);
- // 根据返回的 beanName, 获取其实例返回
- for (String candidateName : candidateNames) {
- if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
- result.put(candidateName, getBean(candidateName));
- }
- }
- return result;
- }
按照名称装配
可以看到它返回的是一个列表, 那么就表明, 按照类型匹配可能会查询到多个实例. 到底应该装配哪个实例呢? 我看有的文章里说, 可以加注解以此规避. 比如 @qulifier,@Primary 等, 实际还有个简单的办法.
比如, 按照 UserService 接口类型来装配它的实现类. UserService 接口有多个实现类, 分为 UserServiceImpl,UserServiceImpl2. 那么我们在注入的时候, 就可以把属性名称定义为 Bean 实现类的名称.
- @Autowired
- UserService UserServiceImpl2;
这样的话, Spring 会按照 byName 来进行装配. 首先, 如果查到类型的多个实例, Spring 已经做了判断.
- public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
- Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
- // 按照类型查找 Bean 实例
- Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
- // 如果 Bean 集合为空, 且 isRequired 成立就抛出异常
- if (matchingBeans.isEmpty()) {
- if (descriptor.isRequired()) {
- raiseNoSuchBeanDefinitionException(type, "", descriptor);
- }
- return null;
- }
- // 如果查找的 Bean 实例大于 1 个
- if (matchingBeans.size()> 1) {
- // 找到最合适的那个, 如果没有合适的.. 也抛出异常
- String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
- if (primaryBeanName == null) {
- throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
- }
- if (autowiredBeanNames != null) {
- autowiredBeanNames.add(primaryBeanName);
- }
- return matchingBeans.get(primaryBeanName);
- }
- }
可以看出, 如果查到多个实例, determineAutowireCandidate 方法就是关键. 它来确定一个合适的 Bean 返回. 其中一部分就是按照 Bean 的名称来匹配.
- protected String determineAutowireCandidate(Map<String, Object> candidateBeans,
- DependencyDescriptor descriptor) {
- // 循环拿到的 Bean 集合
- for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
- String candidateBeanName = entry.getKey();
- Object beanInstance = entry.getValue();
- // 通过 matchesBeanName 方法来确定 bean 集合中的名称是否与属性的名称相同
- if (matchesBeanName(candidateBeanName, descriptor.getDependencyName())) {
- return candidateBeanName;
- }
- }
- return null;
- }
最后我们回到问题上, 得到的答案就是:@Autowired 默认使用 byType 来装配属性, 如果匹配到类型的多个实例, 再通过 byName 来确定 Bean.
3, 主和优先级
上面我们已经看到了, 通过 byType 可能会找到多个实例的 Bean. 然后再通过 byName 来确定一个合适的 Bean, 如果通过名称也确定不了呢? 还是 determineAutowireCandidate 这个方法, 它还有两种方式来确定.
- protected String determineAutowireCandidate(Map<String, Object> candidateBeans,
- DependencyDescriptor descriptor) {
- Class<?> requiredType = descriptor.getDependencyType();
- // 通过 @Primary 注解来标识 Bean
- String primaryCandidate = determinePrimaryCandidate(candidateBeans, requiredType);
- if (primaryCandidate != null) {
- return primaryCandidate;
- }
- // 通过 @Priority(value = 0) 注解来标识 Bean value 为优先级大小
- String priorityCandidate = determineHighestPriorityCandidate(candidateBeans, requiredType);
- if (priorityCandidate != null) {
- return priorityCandidate;
- }
- return null;
- }
Primary
它的作用是看 Bean 上是否包含 @Primary 注解, 如果包含就返回. 当然了, 你不能把多个 Bean 都设置为 @Primary, 不然你会得到 NoUniqueBeanDefinitionException 这个异常.
- protected String determinePrimaryCandidate(Map<String, Object> candidateBeans, Class<?> requiredType) {
- String primaryBeanName = null;
- for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
- String candidateBeanName = entry.getKey();
- Object beanInstance = entry.getValue();
- if (isPrimary(candidateBeanName, beanInstance)) {
- if (primaryBeanName != null) {
- boolean candidateLocal = containsBeanDefinition(candidateBeanName);
- boolean primaryLocal = containsBeanDefinition(primaryBeanName);
- if (candidateLocal && primaryLocal) {
- throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(),
- "more than one'primary'bean found among candidates:" + candidateBeans.keySet());
- }
- else if (candidateLocal) {
- primaryBeanName = candidateBeanName;
- }
- }
- else {
- primaryBeanName = candidateBeanName;
- }
- }
- }
- return primaryBeanName;
- }
Priority
你也可以在 Bean 上配置 @Priority 注解, 它有个 int 类型的属性 value, 可以配置优先级大小. 数字越小的, 就被优先匹配. 同样的, 你也不能把多个 Bean 的优先级配置成相同大小的数值, 否则 NoUniqueBeanDefinitionException 异常照样出来找你.
- protected String determineHighestPriorityCandidate(Map<String, Object> candidateBeans,
- Class<?> requiredType) {
- String highestPriorityBeanName = null;
- Integer highestPriority = null;
- for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
- String candidateBeanName = entry.getKey();
- Object beanInstance = entry.getValue();
- Integer candidatePriority = getPriority(beanInstance);
- if (candidatePriority != null) {
- if (highestPriorityBeanName != null) {
- // 如果优先级大小相同
- if (candidatePriority.equals(highestPriority)) {
- throw new NoUniqueBeanDefinitionException(requiredType, candidateBeans.size(),
- "Multiple beans found with the same priority ('" + highestPriority + "')" +
- "among candidates:" + candidateBeans.keySet());
- }
- else if (candidatePriority <highestPriority) {
- highestPriorityBeanName = candidateBeanName;
- highestPriority = candidatePriority;
- }
- }
- else {
- highestPriorityBeanName = candidateBeanName;
- highestPriority = candidatePriority;
- }
- }
- }
- return highestPriorityBeanName;
- }
最后, 有一点需要注意. Priority 的包在 javax.annotation.Priority;, 如果想使用它还要引入一个坐标.
- <dependency>
- <groupId>javax.annotation</groupId>
- <artifactId>javax.annotation-API</artifactId>
- <version>1.2</version>
- </dependency>
三, 总结
本章节重点阐述了 Spring 中的自动装配的几种策略, 又通过源码分析了 Autowired 注解的使用方式. 在 Spring3.0 之后, 有效的自动装配策略分为 byType,byName,constructor 三种方式. 注解 Autowired 默认使用 byType 来自动装配, 如果存在类型的多个实例就尝试使用 byName 匹配, 如果通过 byName 也确定不了, 可以通过 Primary 和 Priority 注解来确定.
来源: https://juejin.im/post/5c84b5285188257c5b477177