回顾
上篇,介绍了AutowireCapableBeanFactory
的作用:让Spring管理的Bean去装配和填充那些不被Spring托管的Bean,为第三方框架赋能。其中,介绍AutowireCapableBeanFactory
接口时简单提了一下该接口定义了6个属性,其中有5个(AUTOWIRE_*)是与autowireMode
相关的常量值定义,用于描述该用何种方式来进行装配Bean
正文
这里有个容易让人误解的点,就是关于autowireMode
的各个常量的命名。其中有两个常量定义分别是int AUTOWIRE_BY_NAME = 1
、int AUTOWIRE_BY_TYPE = 2
,从命名上看跟我们最常用的@Resouce、@Autowired
这种注解使用的方式很像,但实际上二者八杆子打不着关系,千万不要把他们混为一谈
实际上,注解注入方式在Spring中被称为annotation-driven injection
,而autowireMode
这种装配在Spring里其实被称为traditional autowiring
autowireMode
之所以称为traditional,是因为这种使用姿势已经足够老,老到新一代的Javaer,甚至都没有在项目中用过这种装配模式,上来就是注解也能很好地work,因此,traditional autowiring在Spring 2.5后已式微,大概只有在古董项目上才能窥之身影
先来看一下,Spring 2.5以前项目里如何使用traditional autowiring
定义案例需要用到的类FooService
、BarService
,其中FooService
依赖BarService
public class FooService {
private BarService barService;
public void setBarService(BarService barService) {
this.barService = barService;
}
}
public class BarService {
}
在很久很久以前,那时候Spring还没有大量使用注解,也没有提供各类注解给应用层使用,各类配置项在都是依靠XML进行配置,如下所示
<bean id="fooService" class="com.example.demo.service.FooService">
<property name="barService" ref="barService"/>
</bean>
<bean id="barService" class="com.example.demo.service.BarService"/>
需要用户在XML配置文件中定义Spring需要管理哪些Bean,并通过手动装配方式指定依赖关系。如果只有两个Bean以及它们之间的依赖关系,这种方式也很OK,但如果有大量的Bean呢,都要手动指定那得多麻烦枯燥
byName
懒惰是科技进步的动力,程序员天性懒惰但又很聪明,想出了自动装配的方式,如下所示:
<bean id="fooService" class="com.example.demo.service.FooService" autowire="byName"/>
<bean id="barService" class="com.example.demo.service.BarService"/>
这样,无论FooService
里依赖多少其它Bean,都不再需要手动一个个装配,通过autowire="byName"
的方式,Spring 就会在构建FooService之后,内过Java Bean内省机制拿到Bean里所有的propertyName(案例中的propertyName:barService),然后上Spring IoC容器中找到beanName为propertyName的bean(barService)并将之注入,完成装配。这种方式要求propertyName与beanName一致,故命名为byName
byType
如果想要想打破上面的限制,不要求propertyName与beanName一致也能完成注入,可以配置为autowire="byType"
,如下所示:
<bean id="fooService" class="com.example.demo.service.FooService" autowire="byType"/>
<bean id="baz" class="com.example.demo.service.BarService"/>
甚至将FooService对BarService的依赖改的"面目全非"(此处,propertyName:quz)
public class FooService {
private BarService xxx;
public void setQuz(BarService xxx) {
this.xxx = xxx;
}
}
将BarService的bean id设置为baz,尽管与FooService中的propertyName(quz)不一致,但通过指定autowire="byType"
,依然能够将barService注入到fooService实例中。
原理是: Spring 在构建FooService之后,内过Java Bean内省机制拿到Bean里所有的propertyName(案例中的propertyName:quz),最终拿到DependencyDescriptor(依赖描述符,它描述了一个待注入的依赖信息:要么是构造器参数,要么是方法参数,要么是字段,并且提供了非常友好的、一种统一的方式去访问)。在本案例中,DependencyDescriptor描述的是:我的名字叫setQuz,是在FooService类中被声名的(declaringClass),需要一个入参且其类型是BarService(parameterTypes、resolvableType)。Spring在IoC容器中找到type为BarService的bean返回并注入,完成装配。这种方式只要求待注入的参数,其类型能在Spring里找到,故命名为byType
小总结:无论是byName
还是byType
,Spring都是通过Java Bean的内省机制找到property,然后上IoC容器中找到对应的Bean来完成的注入,这也就是古董项目重充斥大量setter方法的缘由
简单看一下源码中,处理byName
、byType
的地方,依然是在populateBean
方法内部
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
//如果是byName或byType
if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// Add property values based on autowire by name if applicable.
if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
// Add property values based on autowire by type if applicable.
if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
pvs = newPvs;
}
// ...(省略)[省略的内容就包括处理@Resouce、@Autowired注解驱动注入的部分]
// 上面是收集属性到pvs,并不执行属性装配的动作,下边才真正执行
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
通过判断autowireMode是byName
还是byType
,分别进入autowireByName
、autowireByType
方法,将待装配的对象收集到pvs,最后再通过applyPropertyValues
真正执行属性装配。此处再次提醒:该段逻辑与处理@Resouce、@Autowired的注解驱动注入无关,省略部分是上篇内容,即上篇内容提到ibp.postProcessProperties
才是处理annotation-driven injection的逻辑的地方,且真正执行了注入的逻辑
pvs是MutablePropertyValues
类型实例,该类主要职责是维护内部的PropertyValue
集合,实现了PropertyValues
接口,提供对集合的增删查操作
public class MutablePropertyValues implements PropertyValues, Serializable {
private final List<PropertyValue> propertyValueList;
// ...(省略)
}
public interface PropertyValues extends Iterable<PropertyValue>
而PropertyValue
代表的是某个Bean属性的属性名与属性值,类似于一个键值对
public class PropertyValue extends BeanMetadataAttributeAccessor implements Serializable {
private final String name;
@Nullable
private final Object value;
// ...(省略)
}
因此,pvs也即List<PropertyValue>代表了某个Bean里所有的属性名及属性值
constructor
byName
、byType
都是setter方法注入,接着看看autowireMode = constructor
构造器装配使用姿势
public class FooService {
private BarService barService;
public FooService(BarService barService) {
this.barService = barService;
}
}
<bean id="fooService" class="com.example.demo.service.FooService" autowire="constructor"/>
<bean id="baz" class="com.example.demo.service.BarService"/>
通过在FooService
中定入参为BarService
的构造函数,在定义Spring bean时指定autowire="constructor"
,那么Spring在构造FooService实例时,就会找到该构造函数,并从IoC容器中找到类型为BarService
的bean,传入构造函数中进行构造,如此,生成的FooService实例就自动注入了BarService
原理涉及的核心代码如下:
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 如果找到合适的构造器或者autowireMode = AUTOWIRE_CONSTRUCTOR 则执行autowireConstructor
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
这里有一点特殊的地方值得一提:本案例中不配置autowire="constructor"
,也能够通过构造器进行装配,原因在于determineConstructorsFromBeanPostProcessors
的返回值如果不为空,表明找到了合适的构造器,也会进入autowireConstructor
中执行构造器装配
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
// ...(省略)
if (!candidates.isEmpty()) {
// 与@Autowired、@Value 注入有关,不是本节重点,暂时忽略
// Add default constructor to list of optional constructors, as fallback.
if (requiredConstructor == null) {
if (defaultConstructor != null) {
candidates.add(defaultConstructor);
}
else if (candidates.size() == 1 && logger.isInfoEnabled()) {
// ...(省略,log信息)
}
}
candidateConstructors = candidates.toArray(new Constructor<?>[0]);
}
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; // 有且只有一个非空参构造函数
}
// ...(省略两个else if,它们与kotlin相关,跳过不看)
else {
candidateConstructors = new Constructor<?>[0];
}
return (candidateConstructors.length > 0 ? candidateConstructors : null);
在目前的Spring实现中,AutowiredAnnotationBeanPostProcessor实现了determineCandidateConstructors
方法,如果找到有且只有一个非空参构造器,那么就可以成功返回
,很显然,我们的FooService
符合这样的条件,因此返回public FooService(BarService barService)
构造器,进入autowireConstructor
中执行构造器装配
假如给FooService再添加一个空参构造器,或者别的带参构造器,就不再满足上述条件,需要配置autowire="constructor"
才能实现构造器注入
AUTODETECT
该装配模式是byType
和constructor
的混合体,从Spring 3.0已经标识为Deprecated,Spring认为,如果你不想关注到底是哪种autowireMode,就想闭着眼睛让Spring帮忙注入,那就用注解注入的方式吧,这样同样实现效果的同时,也能让注入语义更清晰一些
// org.springframework.beans.factory.support.AbstractBeanDefinition#getResolvedAutowireMode
public int getResolvedAutowireMode() {
if (this.autowireMode == AUTOWIRE_AUTODETECT) {
// Work out whether to apply setter autowiring or constructor autowiring.
// If it has a no-arg constructor it's deemed to be setter autowiring,
// otherwise we'll try constructor autowiring.
Constructor<?>[] constructors = getBeanClass().getConstructors();
for (Constructor<?> constructor : constructors) {
if (constructor.getParameterCount() == 0) {
return AUTOWIRE_BY_TYPE;
}
}
return AUTOWIRE_CONSTRUCTOR;
}
else {
return this.autowireMode;
}
}
判断的逻辑很简单,如果存在空参构造函数,就认为是想用byType的装配模式,否则就是constructor的构造模式
由于Spring 已经将这种autowireMode标识为Deprecated,且除了稍显简便也没别的其他优势,我们还是能不用则不用
AUTOWIRE_NO
默认的autowireMode就是不装配,无特殊行为,不作过多介绍
到了Spring 3.0,XML配置方式已经开始展现颓势,官方推出了Annotation Config,搭配@Configuration、@Bean、@DependsOn等注解,作为XML的等价配置方式,试图取代XML的配置地位,从今日的结果看来,这一目的已经达成,新项目中基本不再出现旧的配置方式
简单介绍一下Annotation Config如何配置autowireMode,以下两种形式是等价的:
<bean id="fooService" class="com.example.demo.service.FooService" autowire="byName"/>
<bean id="barService" class="com.example.demo.service.BarService"/>
@Bean(autowire = Autowire.BY_NAME)
public FooService fooService() {
return new FooService();
}
@Bean
public BarService BarService() {
return new BarService();
}
@Bean注解有一个autowire属性,取值类似于autowireMode,不过在此处它的取值只有三个:NO、BY_NAME、BY_TYPE,没有CONSTRUCT、AUTODETECT,简单想一下可知道,Annotation Config的配置方式不适合CONSTRUCT,以及可能包含CONSTRUCT方式的AUTODETECT。
无论XML配置还是Annotaion Config配置autowireMode,它们的底层原理都一样,最终都是给AbstractBeanDefinition
的autowireMode
属性赋值,因此在真正执行装配时都是判断BeanDefinition的autowireMode属性值来决定装配模式,默认取值: AUTOWIRE_NO
此外,上篇文章提到的AutowireCapableBeanFactory
有很多细粒度控制Bean生命周期的方法,如autowire、autowireBeanProperties
等带有autowireMode
参数,作用与上面介绍的是一样的,最终都落实到AbstractBeanDefinition
的autowireMode
属性上,也会将待装配对象收集到pvs,最后调用applyPropertyValues
方法执行装配
以上便是传统装配模式的介绍,接下来将介绍现代注解驱动注入方式
Annotation-driven injection的方式,在目前的应用开发中被广泛的运用,打开任意一个三层模型(Controller-Service-Dao)的Java Web项目,在各层总是能看到@Resouce、@Autowired、@Value、@Inject(javax.inject.Inject),甚至是自定义注入注解(AutowiredAnnotationBeanPostProcessor#setAutowiredAnnotationType提供这个能力,不需要太多工作量,很迅速地完成自定义),Spring赋予了这些注解DI的能力 ,总体上可作用于Field,Setter Method,Construct,Parameter,AnnotationType(不同注解作用的范围并不一致),再加上注解自身能够配置的属性、与其它注解的配合(如@Qualifier)使用,能玩出的花样真是不胜枚举。他们之间的功能有重叠有交叉,不必要每一个都掌握,徒增精力,只掌握最小子集的注解又可涵盖注解功能的总和,满足任何场景的开发即可。故此,本节探讨一下大概率使用最多的@Resouce、@Autowired
在字段注入的场景
@Resouce
案例代码如下:
@Service
public class FooService {
@Resource
private BarService barService;
}
@Service
public class BarService {}
Spring 处理@Resouce体现在Spring Bean Lifecycle的两个阶段
- 构建Bean实例之后,填充属性之前,收集Bean里面所有@Resouce相关的信息并缓存起来,注意,此阶段是作信息收集之用,不作具体处理(CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition)
- 在填充属性的过程中,将待装配的对象收集到pvs后,执行applyPropertyValues之前(执行传统的属性装配之前),将第1阶段收集到的信息利用起来,执行Annotation-driven injection(CommonAnnotationBeanPostProcessor#postProcessProperties)
先看第一阶段,收集待注入的元信息
// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
// 收集待注入的元信息(@Resouce相关信息)
InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {
// ...(省略)
// 构建待注入元信息,并放入缓存
metadata = buildResourceMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
return metadata;
}
// 构建待注入元信息
private InjectionMetadata buildResourceMetadata(final Class<?> clazz) {
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 通过反射查看class所有field上是否含有@Resouce注解,找到后就封装成ResourceElement
ReflectionUtils.doWithLocalFields(targetClass, field -> {
// ...(省略)
else if (field.isAnnotationPresent(Resource.class)) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new ResourceElement(field, field, null));
}
}
});
// ...(下边是收集方法注入方式的元信息,省略)
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return new InjectionMetadata(clazz, elements);
}
通过源码可以看到,最后待注入的元信息其实是ResourceElement
,看着看一下ResourceElement的构造函数做了些什么事情
// 从上面的buildResourceMetadata可以看出,member、ae都是field
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
Resource resource = ae.getAnnotation(Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
this.isDefaultName = !StringUtils.hasLength(resourceName);
// 处理resourceName
if (this.isDefaultName) {
// 如果使用@Resource时未指定name,默认取member的name做为resouceName
// 对于member为field的case,取的field name
resourceName = this.member.getName();
//如果member是个方法,代表setter注入,那就把setXxx前的set去掉后将首字段小写作为resouceName,即xxx
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}
else if (embeddedValueResolver != null) {
// 如果手动指定resoureName了,就使用embeddedValueResolver.resolveStringValue去解析
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
// 处理resouceType
if (Object.class != resourceType) {
// 如果手动指了定resouceType,需要检查一下是不是瞎指定,比如field明明是个A,却通过resouceType指定为B type,那明显会有问题
checkResourceType(resourceType);
}
else {
// No resource type specified... check field/method.
// 如果未手动指定resourceType,则取member或者pd的类型
resourceType = getResourceType();
}
this.name = (resourceName != null ? resourceName : "");
this.lookupType = resourceType;
String lookupValue = resource.lookup();
this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}
构造函数主要就是给name、lookupType、lookupValue等属性赋值,其中name为resourceName,lookupType为resourceType,而且一般都不会为空,即便我们没有通过注解手动指定它们的值,也有默认的方式获取resourceName跟resourceType,这也就是我们在多数场景直接打上注解就完事,而不指定具体参数的原因
这种设计原则值得借鉴与学习:如果我们需要给他人提供某项能力,出于优雅设计的原则,首先调研、考虑大多数场景怎样使用,我们将这类应用的场景以代码的形式固化下来,当发生API调用时不需要提供过多参数也可以正常运行,针对另外一小部分需要自定义的场景,通过提供一种optional的能力,允许用户选填,选填项优先级高于固化代码的方式,就能覆盖100%的场景。这也是一种"约定优于配置(convention over configuration)"的思想实践
另外,如果我们通过注解的属性手动指定了name,会走resourceName = embeddedValueResolver.resolveStringValue(resourceName);
这段逻辑,embeddedValueResolver是EmbeddedValueResolver
类的实例,该类特别强大,不但能够解析"占位符",还能够解析"Spring EL"表达式,简言之,@Value里能配置啥值,在这就能配啥值,而且解析的效果是一样的。因此,如下的方式也是与上边的方式等下的
// application.properties
quz = barService
// ----------
public class FooService {
@Resource(name = "${quz}")
private BarService barService;
}
Spring 的设计真是惊为天人,考虑到各种我们开发过程中可能使用到的姿势并且提供了能力,真是让人佩服五体投地
再看第二阶段,执行真正的注入逻辑
// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 该方法上面出现过,第一次出现的时候是构建并且放入缓存,这是第二次出现 ,直接从缓存中获取
// ResourceElement是InjectionMetadata的子类,因此metadata其实是ResourceElement的实例
InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
try {
// 执行注入
metadata.inject(bean, beanName, pvs);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
}
return pvs;
}
// org.springframework.beans.factory.annotation.InjectionMetadata#inject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
// 循环每一个待注入的元素,执行注入
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
// 注入
element.inject(target, beanName, pvs);
}
}
}
通过反射执行注入逻辑field.set(target, getResourceToInject(target, requestingBeanName));
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
throws Throwable {
if (this.isField) {
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
}
// ...(方法注入,省略)
}
接着看getResourceToInject(target, requestingBeanName)
,获取待注入对象实例
// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
// 我们并没有配置@Lazy注解,因此走`getResource(this, requestingBeanName)`逻辑
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
throws NoSuchBeanDefinitionException {
// ...(省略)
// resourceFactory是BeanFactory的实例,在构建CommonAnnotationBeanPostProcessor实例的时候通过BeanFactoryAware#setBeanFactory回调接口注入进来
return autowireResource(this.resourceFactory, element, requestingBeanName);
}
// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResource
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
throws NoSuchBeanDefinitionException {
Object resource;
Set<String> autowiredBeanNames;
String name = element.name;
// factory是DefaultListableBeanFactory的实例对象,因此也就是AutowireCapableBeanFactory的实例对象
if (factory instanceof AutowireCapableBeanFactory) { // (if 1)
AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
DependencyDescriptor descriptor = element.getDependencyDescriptor();
// fallbackToDefaultTypeMatch默认值为true
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) { // (if 2)
autowiredBeanNames = new LinkedHashSet<>();
// resouce为解析出来的待注入实例对象
resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
if (resource == null) {
throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
}
}
else { // (else 1)
// resouce为解析出来的待注入实例对象
resource = beanFactory.resolveBeanByName(name, descriptor);
autowiredBeanNames = Collections.singleton(name);
}
}
else { // (else 2)
// 基本上不会考到这
resource = factory.getBean(name, element.lookupType);
autowiredBeanNames = Collections.singleton(name);
}
// factory是DefaultListableBeanFactory的实例对象,因此也就是ConfigurableBeanFactory的实例对象
if (factory instanceof ConfigurableBeanFactory) { // (if 3)
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
for (String autowiredBeanName : autowiredBeanNames) {
if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
// 将依赖关系注册到Spring中,是个双向的描述
// 即dependentBeanMap、dependenciesForBeanMap
beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
}
}
}
return resource;
}
factory是DefaultListableBeanFactory的实例对象,因此也就是AutowireCapableBeanFactory、ConfigurableBeanFactory的实例对象,所以if 1、if 3都会进去,if 1逻辑用于解析待注入的bean,if 3逻辑是维护依赖Bean与被依赖Bean之间的关系
if 2代码块中,有个名为fallbackToDefaultTypeMatch
的属性,它表示的含义是:当未通过注解手动指定name(意味着使用defaultName,即字段名或属性名),Spring根据defaultName上Ioc容器找不到Bean时,是否需要fallback,根据type到Ioc容器中找,默认值为true
因此进入if 2块需同时满足以下三个条件:
- fallbackToDefaultTypeMatch = true(默认值是true)
- element.isDefaultName = true(不能通过注解指定name)
- factory.containsBean(name) = false(根据name在Ioc容器中找不到Bean)
否则,直接进入else 1的逻辑: resource = beanFactory.resolveBeanByName(name, descriptor);
fallbackToDefaultTypeMatch
默认true,一般也不会改为false,忽略这种情况不考虑,那进入else 1的情况有2个:
- 通过注解手动指定name,如@Resource(name = "quz")
- IoC中包含beanName = name的Bean或者BeanDefinition
在本案例中,由于factory.containsBean("barService") = true,因此进入的是else 1的逻辑
网上有说法:
@Resouce默认根据name进行注入,如果找不到就根据type进行注入
其实这种说法不够严谨,也不太准确,能成立的前提条件是:name是fieldName或者propertyName,即未通过注解进行明确指定。如若通过注解手动指定name,就会走else 1逻辑,压根不会根据type进行注入
由于name、type都可以手动指定或不指定,因此出现4种排列组合的情况:
- 不指定name与type: 通过fileName或propertyName判断Ioc中是否存在,存在进入else 1 ,不存在就通过type去找,找不到或找到多个都抛异常(if 2)
- 仅指定name: 通过name找唯一的Bean,找不到抛异常(else 1)
- 仅指定type: 通过type找唯一的Bean,找不到或找到多个都抛异常(if 2)
- 指定name、指定type: 通过name找唯一的Bean,找不到抛异常(else 1)
注: 不建议把这4个排列组合的结论记下来,而是记住原理,遇到问题能够根据原理分析出来原因并加以解决
if 2、else 1都是将待注入的Bean解析出来,所谓解析就是在Spring容器中完成Bean的构造、属性填充、初始化等,然后返回一个能用的Bean。由于解析Bean是一个比较复杂的过程,涉及的知识点很多,不在本篇讨论的重点,暂且按下不表。只要知道它能返回一个Bean即可,接着,拿着这个返回结果(待注入对象)就可以完成注入
@Autowired
Spring 处理@Autowired同样体现在Spring Bean Lifecycle的两个阶段,与@Resouce处理方式如出一辙,甚至它们之间的代码有大部分都是相似的,因此熟悉@Resouce的处理流程之后,再来看@Autowired的处理代码,简直不能再轻松
第一阶段,收集待注入的元信息(InjectionMetadata)
AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition -> findAutowiringMetadata -> buildAutowiringMetadata
第二阶段,执行真正的注入逻辑
AutowiredAnnotationBeanPostProcessor#postProcessProperties -> findAutowiringMetadata -> InjectionMetadata#inject -> AutowiredFieldElement#inject
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
// value为解析出来的待注入实例对象
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
// ...(省略)
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
还是同样的配方,还是熟悉的味道,不过过程更简单了,因为没有name、type的属性,而是将field包装成DependencyDescriptor,通过beanFactory.resolveDependency根据类型解析出来待注入的实例对象,然后通过反射直接注入
思考
了解到传统装配模式与现代注解驱动注入方式的区别之后,我们在具体开发过程中,该如何抉择呢?很显然,Spring的方式是在引导我们向Annotation-driven Injection靠拢,也就是在正常的业务代码中应该使用现代注解驱动注入的方式,这种方式已经是大家约定俗成的习惯用法,是行业共识,没有太多的勾通成本。实际上,在Annotation Config中使用autowireMode也是不被建议的,从Spring 5.1开始,@Bean注解的autowire属性已经被标识为Deprecated,文档上提到这种方式已经被@Autowired注解所取代
那么是否意味着traditional autowiring就应该被打入冷宫,不应该使用了呢?其实不是的,至少从Spring 5.1看来,AbstractBeanDefinition的autowireMode还没有被标识为过期,AutowireCapableBeanFactory的细粒度生命周期方法带有autowireMode参数也没有被标识为过期,它们还在一些场合——为第三方框架赋能的场景发挥着光和热
试想一下,如果我是一个第三方框架的开发者,开发的框架想与Spring结合,想借助它的一些能力,但是又不想与Spring结合得太过紧密,而是仅仅做为DI的可选项之一,那么设计上就要考虑松耦合,不能嵌入太多Spring相关的注解或者代码,而是独立一个-spring的模块,在该模块中引入AutowireCapableBeanFactory,根据场景配置不同的autowireMode,使之与框架自身需要DI的场景、对象进行结合,而框架的实例对象看起来与Spring毫不相关,只有这样,才能与Spring进行解耦合
举个Dubbo的例子,在Dubbo DI过程,调用的是Object object = objectFactory.getExtension(pt, property);
。objectFactory是一个AdaptiveExtensionFactory
(自适应的DI工厂,是个composite模式),里边维护了一个ExtensionFactory
(真正的工厂类)集合,该集合包含两个工厂,一个依靠Dubbo自身的SPI机制加载元素(SpiExtensionFactory),另一个直接从Spring获取元素(SpringExtensionFactory)
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
// ...(省略)
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
// 任意一个工厂获取到元素,就立即返回
if (extension != null) {
return extension;
}
}
return null;
}
}
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
public class SpringExtensionFactory implements ExtensionFactory {
// ...(省略)
@Override
@SuppressWarnings("unchecked")
public <T> T getExtension(Class<T> type, String name) {
for (ApplicationContext context : contexts) {
// 从Spring的ApplicationContext里获取Bean
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
return null;
}
}
借助Spring能力,从ApplicationContext里获取到需要的Bean之后,装配到Dubbo框架的实例对象中,而完全看不出Spring的痕迹,很好地与Spring解耦
总结
本文介绍了传统装配模式与现代注解驱动注入方式之间的区别,先是明白了传统装配模式中的byName
、byType
与@Resouce
、@Autowired
等注解之间并无任何的联系,在底层处理上它们是两套逻辑,不可"见名知义"而混问一谈。接着介绍了Annotation-driven injection的方式,从中挑选了field injection进行了详细过程的分析,并澄清了@Resouce一个网上不严谨的说法。最后,对于这两种方式的使用进行了一个思考,对在什么场合使用什么方式能更了然于胸