完全搞明白Spring中的自动装配和Autowired

1、自动装配

当Spring装配Bean属性时,有时候很是明确,就是须要将某个Bean的引用装配给指定属性。好比,若是咱们的应用上下文中只有一个org.mybatis.spring.SqlSessionFactoryBean类型的Bean,那么任意一个依赖SqlSessionFactoryBean的其余Bean就是须要这个Bean。毕竟这里只有一个SqlSessionFactoryBean的Bean。java

为了应对这种明确的装配场景,Spring提供了自动装配(autowiring)。与其显式的装配Bean属性,为什么不让Spring识别出能够自动装配的场景。面试

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

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以后已经再也不支持。api

一、byName

它的意思是,把与Bean的属性具备相同名字的其余Bean自动装配到Bean的对应属性中。听起来可能比较拗口,咱们来看个例子。bash

首先,在User的Bean中有个属性Role myRole,再建立一个Role的Bean,它的名字若是叫myRole,那么在User中就可使用byName来自动装配。mybatis

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来自动装配。那么,若是属性名称对应不上呢?ui

二、byType

是的,若是不使用属性名称来对应,你也能够选择使用类型来自动装配。它的意思是,把与Bean的属性具备相同类型的其余Bean自动装配到Bean的对应属性中。this

<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均可以省去。spa

三、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>
复制代码

四、autodetect

它首先会尝试使用constructor进行自动装配,若是失败再尝试使用byType。不过,它在Spring3.0以后已经被标记为@Deprecated

五、默认自动装配

默认状况下,default-autowire属性被设置为none,标示全部的Bean都不使用自动装配,除非Bean上配置了autowire属性。 若是你须要为全部的Bean配置相同的autowire属性,有个办法能够简化这一操做。 在根元素Beans上增长属性default-autowire="byType"

<beans default-autowire="byType">
复制代码

Spring自动装配的优势不言而喻。可是事实上,在Spring XML配置文件里的自动装配并不推荐使用,其中笔者认为最大的缺点在于不肯定性。或者除非你对整个Spring应用中的全部Bean的状况了如指掌,否则随着Bean的增多和关系复杂度的上升,状况可能会很糟糕。

2、Autowired

从Spring2.5开始,开始支持使用注解来自动装配Bean的属性。它容许更细粒度的自动装配,咱们能够选择性的标注某一个属性来对其应用自动装配。

Spring支持几种不一样的应用于自动装配的注解。

  • Spring自带的@Autowired注解。

  • JSR-330的@Inject注解。

  • JSR-250的@Resource注解。

咱们今天只重点关注Autowired注解,关于它的解析和注入过程,请参考笔者Spring源码系列的文章。Spring源码分析(二)bean的实例化和IOC依赖注入

使用@Autowired很简单,在须要注入的属性加入注解便可。

@Autowired
UserService userService;
复制代码

不过,使用它有几个点须要注意。

一、强制性

默认状况下,它具备强制契约特性,其所标注的属性必须是可装配的。若是没有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;
复制代码

二、装配策略

我记得曾经有个面试题是这样问的: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。

三、主和优先级

上面咱们已经看到了,经过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>
复制代码

3、总结

本章节重点阐述了Spring中的自动装配的几种策略,又经过源码分析了Autowired注解的使用方式。 在Spring3.0以后,有效的自动装配策略分为byType、byName、constructor三种方式。注解Autowired默认使用byType来自动装配,若是存在类型的多个实例就尝试使用byName匹配,若是经过byName也肯定不了,能够经过Primary和Priority注解来肯定。

相关文章
相关标签/搜索