前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring的自动装配

Spring的自动装配

作者头像
SerMs
发布2022-04-11 15:10:17
5910
发布2022-04-11 15:10:17
举报
文章被收录于专栏:SerMsBlog

好处:大幅度减少Spring配置

坏处:依赖不能明确管理,可能会有多个bean同时符合注入规则,没有清晰的依赖关系。

在装配的时候会有两种方式,byNamebyType两种。

byName:根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配。

byType:如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配;如果存在多个该类型bean,那么抛出异常,并指出不能使用byType方式进行自动装配;如果没有找到相匹配的bean,则什么事都不发生,也可以通过设置

什么是自动装配

自动装配:也就是 Spring 会在容器中自动的查找,并自动的给 bean 装配及其关联的属性

涉及到自动装配 bean依赖关系时,Spring 有多种处理方式。Spring 提供了 4 种自动装配策略

代码语言:javascript
复制
public interface AutowireCapableBeanFactory extends BeanFactory {

    // 无需自动装配
    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;
}

什么是依赖注入

依赖注入:当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。然而采用依赖注入的方式,创建被调用者的工作不再由调用者来完成,创建被调用者的实例的工作由 IOC 容器来完成。然后注入调用者,称为依赖注入

控制反转:当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。然而采用控制反转的方式,创建被调用者的工作不再由调用者来完成,创建被调用者的实例的工作由 IOC 容器来完成。也就是把对象的创建,初始化,销毁的工作交给 spring ioc 容器来做。由 spring ioc 容器来管理对象的生命周期

依赖注入的方式有两种:构造器注入和 setter 方法注入

依赖注入与自动装配的关系

依赖注入的本质就是装配,装配是依赖注入的具体行为

在传统的使用 xml 文件装配 bean 是一件很繁琐的事情,而且还需要找到对应类型的 bean 才能装配,一旦 bean 很多,就不好维护了。为了解决这种问题,spring 使用注解来进行自动装配。自动装配就是开发人员不必知道具体要装配哪个 bean 的引用,这个识别的工作会由 spring 来完成。与自动装配配合的还有“自动检测”,这个动作会自动识别哪些类需要被配置成 bean,进而来进行装配

因此也可以这样理解:自动装配是为了将依赖注入“自动化”的一个简化配置的操作

Spring 中自动装配的策略

byName

它的意思是:把与 bean 的属性具有相同名字的其他 bean 自动装配到 bean 的对应属性中

例:在 User 的 bean 中有个属性 Role myRole,再创建一个 Role 的 bean,它的名字如果叫 myRole,那么在 User 中就可以使用 byName 来自动装配

代码语言:javascript
复制
public class User{
    private Role myRole;
}

public class Role {
    private String id;  
    private String name;
}

上面是 bean 的定义,再看配置文件

代码语言:javascript
复制
<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 来自动装配。那么,如果属性名称对应不上呢?

byType

它的意思是:把与 bean 的属性具有相同类型的其他 bean 自动装配到 bean 的对应属性中

代码语言:javascript
复制
<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 beanid 都可以省去

constructor

它是说:把与 bean 的构造器入参具有相同类型的其他 bean 自动装配到 bean 构造器的对应入参中。值的注意的是,具有相同类型的其他 bean 这句话说明它在查找入参的时候,还是通过 bean 的类型来确定

代码语言:javascript
复制
public class User{
    private Role role;

    public User(Role role) {
        this.role = role;
    }
}
代码语言:javascript
复制
<bean id="user" class="com.viewscenes.netsupervisor.entity.User" autowire="constructor"></bean>

autodetect(不推荐使用了)

它首先会尝试使用 constructor 进行自动装配,如果失败再尝试使用 byType 。不过,它在 Spring 3.0 之后已经被标记为 @Deprecated

默认的自动装配策略

默认情况下,default-autowire 属性被设置为 none,标示所有的 bean 都不使用自动装配,除非 bean 上配置了 autowire 属性

如果你需要为所有的 bean 配置相同的 autowire 属性,有个办法可以简化这一操作,在根元素 beans 上增加属性

代码语言:javascript
复制
<beans default-autowire="byType">

@Autowired 注解

Autowired 注解使用

https://hgm.vercel.app/post/7047b0e7/

具体的大家可以看这篇文章,这里补充一些文章没写全的内容

Spring 2.5 开始,开始支持使用注解来自动装配 bean 的属性。它允许更细粒度的自动装配,我们可以选择性的标注某一个属性来对其应用自动装配。Spring 支持几种不同的应用于自动装配的注解

  • Spring 自带的 @Autowired 注解
  • JSR-330@Inject 注解
  • JSR-250@Resource 注解

使用 @Autowired 它有几个点需要注意

强制性

默认情况下,它具有强制契约特性,其所标注的属性必须是可装配的。如果没有 bean 可以装配到 @Autowired 所标注的属性或参数中,那么你会看到 NoSuchBeanDefinitionException 的异常信息

代码语言:javascript
复制
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 为 true,就抛出异常
    if (matchingBeans.isEmpty()) {
        if (descriptor.isRequired()) {
            raiseNoSuchBeanDefinitionException(type, "", descriptor);
        }
        return null;
    }
}

看到上面的源码,我们可以得到这一信息,bean 集合为空不要紧,关键 isRequired 条件不能成立,如果成立就会抛异常。那么,如果我们不确定属性是否可以装配,可以这样来使用 Autowired

代码语言:javascript
复制
@Autowired(required = false)
UserService userService;

装配策略

前几天看到大佬群里有个面试题是这样问的:Autowired 是按照什么策略来自动装配的呢?关于这个问题,不能一概而论,你不能简单的说按照类型或者按照名称。但可以确定的一点的是,它默认是按照类型来自动装配的,即 byType

默认按照类型装配

关键点 findAutowireCandidates 这个方法

代码语言:javascript
复制
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 实现类的名称

代码语言:javascript
复制
@Autowired
UserService UserServiceImpl2;

这样的话,spring 会按照 byName 来进行装配。首先,如果查到类型的多个实例,spring 已经做了判断

代码语言:javascript
复制
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 的名称来匹配

代码语言:javascript
复制
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

主和优先级

上面我们已经看到了,通过 byType 可能会找到多个实例的 bean。然后再通过 byName 来确定一个合适的 bean,如果通过名称也确定不了呢?

还是 determineAutowireCandidate 这个方法,它还有两种方式来确定

代码语言:javascript
复制
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 这个异常

代码语言:javascript
复制
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异常照样出来找你

代码语言:javascript
复制
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,如果想使用它还要引入一个坐标

代码语言:javascript
复制
<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注解来确定。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是自动装配
  • 什么是依赖注入
    • 依赖注入与自动装配的关系
    • Spring 中自动装配的策略
      • byName
        • byType
          • constructor
            • autodetect(不推荐使用了)
            • 默认的自动装配策略
            • @Autowired 注解
              • 强制性
                • 装配策略
                  • 默认按照类型装配
                • 主和优先级
                  • @Primary 注解
                  • @Priority 注解
              • 总结
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档