1. 彻底搞明白Spring中的自动装配和Autowired注解的使用

     更新时间:2019年03月13日 14:13:19   作者:清幽之地   我要评论

    这篇文章主要介绍了彻底搞明白Spring中的自动装配和Autowired注解的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    一、自动装配

    当Spring装配Bean属性时,有时候非常明确,就是需要将某个Bean的引用装配给指定属性。比如,如果我们的应用上下文中只有一个org.mybatis.spring.SqlSessionFactoryBean类型的Bean,那么?#25105;?#19968;个依赖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依?#24213;?#20837;

    使用@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名称,里面?#23548;?#24490;环所有的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;
    }
    

    按照名称装配

    可以看到它返回的是一个列表,那么就表明,按照类型匹配可能会查询到多个实例。到底应该装配哪个实例呢?我看有的文章里说,可以加注解?#28304;?#35268;避。比如@qulifier、@Primary等,?#23548;?#36824;有个简单的办法。

    比如,按照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来自动装配,如果存在类型的多个实例?#32479;?#35797;使用byName匹配,如果通过byName也确定不了,可以通过Primary和Priority注解来确定。

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    相关文章

    最新评论

    山东群英会开奖查询
      1. 中国福彩网双色球开奖结果 黑龙江快乐十分尾走势图表 球探体育网球比分 中彩网3d走势图 新疆时时彩今天开奖 江苏快三软件 中国福利彩票走势图 七星彩017预测 欧洲快乐赛车 捷克酷喜乐彩铅怎么样 福建31选7今天中奖结果 p3开机号试机号及金胆 吉林快3三同号遗漏 福建36选7晚上几点几分开奖 秒速飞艇直播