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

一、自动装配

当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依赖注入

使用@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注解来确定。

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

(0)

相关推荐

  • Java注解机制之Spring自动装配实现原理详解

    Java中使用注解的情况主要在SpringMVC(Spring Boot等),注解实际上相当于一种标记语言,它允许你在运行时动态地对拥有该标记的成员进行操作.注意:spring框架默认不支持自动装配的,要想使用自动装配需要修改spring配置文件中<bean>标签的autowire属性. 自动装配属性有6个值可选,分别代表不同的含义: byName ->从Spring环境中获取目标对象时,目标对象中的属性会根据名称在整个Spring环境中查找<bean>标签的id属性值.如果

  • 深入浅析SpringBoot中的自动装配

    SpringBoot的自动装配是拆箱即用的基础,也是微服务化的前提.这次主要的议题是,来看看它是怎么样实现的,我们透过源代码来把握自动装配的来龙去脉. 一.自动装配过程分析 1.1.关于@SpringBootApplication 我们在编写SpringBoot项目时,@SpringBootApplication是最常见的注解了,我们可以看一下源代码: /* * Copyright 2012-2017 the original author or authors. * * Licensed un

  • Spring的自动装配Bean的三种方式

    spring的自动装配功能的定义:无须在Spring配置文件中描述javaBean之间的依赖关系(如配置<property>.<constructor-arg>).IOC容器会自动建立javabean之间的关联关系. 如果没有采用自动装配的话,手动装配我们通常在配置文件中进行实现:一下代码就是手动装配: <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="ht

  • 基于XML配置Spring的自动装配过程解析

    一.了解Spring自动装配的方式 采用传统的XML方式配置Bean组件的关键代码如下所示 <bean id="userMapper" class="edu.cn.dao.UserMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> <bean id="userSer

  • 详解Spring Boot自动装配的方法步骤

    在<Spring Boot Hello World>中介绍了一个简单的spring boot例子,体验了spring boot中的诸多特性,其中的自动配置特性极大的简化了程序开发中的工作(不用写一行XML).本文我们就来看一下spring boot是如何做到自动配置的. 首先阐明,spring boot的自动配置是基于spring framework提供的特性实现的,所以在本文中,我们先介绍spring framework的相关特性,在了解了这些基础知识后,我们再来看spring boot的自

  • 基于Spring@Autowired注解与自动装配详谈

    1 配置文件的方法 我们编写spring 框架的代码时候.一直遵循是这样一个规则:所有在spring中注入的bean 都建议定义成私有的域变量.并且要配套写上 get 和 set方法. Boss 拥有 Office 和 Car 类型的两个属性: 清单 3. Boss.java package com.baobaotao; public class Boss { private Car car; private Office office; // 省略 get/setter @Override p

  • Spring 自动装配的二义性实例解析

    这篇文章主要介绍了Spring 自动装配的二义性实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.我们知道可以用Spring的自动装配(@Autowired)将Bean应用注入到构造参数和属性中,但是,注意了,仅有一个bean匹配需要的结果时,自动装配才可以生效.如果有多个bean匹配同一个结果,这种歧义性会阻碍Spring自动装配属性,构造参数或方法参数. 大白话说一下,就如我们有一个甜片接口(Dessert)里面有一个好吃的方法(

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

    一.自动装配 当Spring装配Bean属性时,有时候非常明确,就是需要将某个Bean的引用装配给指定属性.比如,如果我们的应用上下文中只有一个org.mybatis.spring.SqlSessionFactoryBean类型的Bean,那么任意一个依赖SqlSessionFactoryBean的其他Bean就是需要这个Bean.毕竟这里只有一个SqlSessionFactoryBean的Bean. 为了应对这种明确的装配场景,Spring提供了自动装配(autowiring).与其显式的装配

  • Spring自动装配与扫描注解代码详解

    1 javabean的自动装配 自动注入,减少xml文件的配置信息. <?xml version="1.0" encoding="UTF-8"?> <!-- 到入xml文件的约束 --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p&quo

  • 使用Spring由构造方法自动装配

    Spring由构造方法自动装配 在Spring中,可以使用"通过构造自动装配",实际上是按构造函数的参数 类型自动装配. 这意味着,如果一个bean的数据类型与其他bean的构造器参数的数据类型是相同的,那么将自动装配. 下面看看Spring构造函数自动装配的一个完整例子. 1. Beans 这里有两个 beans, 分别是:developer 和 language public class Developer { private Language language; public L

  • 一文搞懂Spring中的Bean作用域

    目录 概述 Singleton prototype request session application 概述 scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其 相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象. Spring容器bean的作用域类型: singleton:Spring IoC 容器的单个对象实例作用域都默认为singleton prototype:针对声明为拥有prototyp

  • 一文搞懂Spring中的注解与反射

    目录 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody 1.4@GetMapping 1.5@PathVariable 1.6@RequestParam 1.7@ComponentScan 1.8@Component 1.9@Service 1.10@Repository 二.元注解 @Target @Retention @Documented @Inherited 三.自定义注解 四.反射机制概述 4.1动态语言与静态语

  • 一文搞懂Spring中Bean的生命周期

    目录 一.使用配置生命周期的方法 二.生命周期控制——接口控制(了解) 小结 生命周期:从创建到消亡的完整过程 bean声明周期:bean从创建到销毁的整体过程 bean声明周期控制:在bean创建后到销毁前做一些事情 一.使用配置生命周期的方法 在BookDaoImpl中实现类中创建相应的方法: //表示bean初始化对应的操作 public void init(){ System.out.println("init..."); } //表示bean销毁前对应的操作 public v

  • 一文搞懂Spring中@Autowired和@Resource的区别

    目录 1.来源不同 2.依赖查找顺序不同 2.1 @Autowired 查找顺序 2.2 @Resource 查找顺序 2.3 查找顺序小结 3.支持的参数不同 4.依赖注入的支持不同 5.编译器提示不同 总结 @Autowired 和 @Resource 都是 Spring/Spring Boot 项目中,用来进行依赖注入的注解.它们都提供了将依赖对象注入到当前对象的功能,但二者却有众多不同,并且这也是常见的面试题之一,所以我们今天就来盘它. @Autowired 和 @Resource 的区

  • 详解在Spring中如何自动创建代理

    Spring 提供了自动代理机制,可以让容器自动生成代理,从而把开发人员从繁琐的配置中解脱出来 . 具体是使用 BeanPostProcessor 来实现这项功能. 1 BeanPostProcessor BeanPostProcessor 代理创建器的实现类可以分为 3 类: 类型 实现类 基于 Bean 配置名规则 BeanNameAutoProxyCreator 基于 Advisor 匹配规则 DefaultAdvisorAutoProxyCreator 基于 Bean 中的 Aspect

  • 一文搞懂Spring中的JavaConfig

    目录 配置类 注册组件 扫描包配置 事务注解驱动 单元测试加载配置类 properties配置文件加载(了解) aspectj注解开关 传统spring一般都是基于xml配置的,不过后来新增了许多JavaConfig的注解.特别是springboot,基本都是清一色的java config,不了解一下,还真是不适应.这里给大家普及下Spring中的JavaConfig知识. 什么是JavaConfig.通过注解和配置类完成Spring的相关配置 Spring配置都做了什么? 注册组件.其他配置(

随机推荐