SpringFramework源码解析——Bean在Spring中是如何完成依赖注入的?
1. 背景
1.1. 什么是依赖注入?
宽泛地讲,依赖注入(Dependency Inject,DI)是控制反转(Inversion of Control,IoC)的一种实现方式,本质上就是将依赖的创建和管理委派给IoC容器,使用者只需要声明要注入的依赖便可完成依赖的获取。在日常开发中,最常使用的Spring注解是@Component
和@Autowired
(或@Resource
),前者将Bean注册到Spring的IoC容器中,后者完成Bean的依赖注入。依赖注入有字段注入,方法注入和构造器注入三种方式。
@Component
public class Client {
// Dependency Inject
@Resource
private Dependency dependency;
}
另外,依赖查找(Dependency Lookup)也是控制反转的一种实现方式,不过相比于依赖注入,使用者需要去IoC容器中主动查找所需的依赖,会耦合容器的API,如BeanFactory#getBean
。依赖查找有按名称查找,按类型查找等方式。
@Component
public class Client {
public Dependency getDependency() {
// Dependency Lookup
return beanFactory.getBean(Dependency.class);
}
}
1.2. 为什么要了解依赖注入的原理?
作为使用者,使用框架是为了简化开发,通常只需要关心框架是如何使用的而并不需要关心框架是如何实现的。那为什么还要去了解依赖注入的原理?主要有以下几个原因,也是阅读源码的原因:
- 学习框架的设计思想和设计模式,提高工程抽象和建模能力。
- 把源码当做文档,通过源码了解框架用法和扩展点。如下文中的自定义依赖注入方式。
- 为排查问题提供原理支持。如依赖注入时抛出
NoUniqueBeanDefinitionException
异常如何解决。
作为一个专业的赛车手,有必要了解赛车的发动机是怎么工作的。
1.3. 什么时候会发生依赖注入?
作为使用者,通过在Bean的字段,方法或构造器上标注特定注解(如@Autowired
,@Resource
等)的方式来告知IoC容器此处需要依赖注入,并希望在使用Bean的时候其依赖注入已然完成。
作为实现者,Bean的字段注入或方法注入发生在设置Bean的属性的过程中,也就是Bean的实例化之后,Bean的初始化之前;Bean的构造器注入则发生在实例化Bean的过程中;具体流程如下:
- 实例化Bean;
- 获取候选构造器
- 构造器注入
- 设置Bean的属性;
- 字段注入和方法注入
- 初始化Bean。
当调用BeanFactory#getBean
方法获取Bean的时候,会执行一系列Bean后置处理器的方法,其中就包括完成依赖注入的Bean后置处理器,对于@Autowired
,@Value
,@Inject
等自动装配注解而言,对应的Bean后置处理器是AutowiredAnnotationBeanPostProcessor
,具体方法如下:
- 字段注入和方法注入:
AutowiredAnnotationBeanPostProcessor#postProcessProperties
。 - 构造器注入前获取候选构造器:
AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
。
对于@Resource
,@EJB
等通用注解而言,对应的Bean后置处理器是CommonAnnotationBeanPostProcessor
,具体方法如下。
- 字段注入和方法注入:
CommonAnnotationBeanPostProcessor#postProcessProperties
。
总的来看,依赖注入发生在实例化Bean和设置Bean的属性两大步骤中,依赖注入的具体实现则在相应的Bean后置处理器中,依赖注入发生的时机如下流程图:
1.4. 依赖注入在Spring中是如何描述的?
上文我们说到可以通过在Bean的字段,方法或构造器上标注特定注解(如@Autowired
,@Resource
等)的方式来告知IoC容器此处需要依赖注入,那在Spring中是如何描述这些需要依赖注入的地方的呢?Spring使用注入元素InjectElement
来描述Bean中每个需要依赖注入的地方,并使用注入元数据InjectMetadata
来组合Bean中所有的注入元素,类图如下:
从类图中我们可以看到,注入元素InjectElement
中有一个InjectElement#inject
方法,其提供了字段注入和方法注入的默认实现。
- 如果想要实现字段注入和方法注入,则需要重写
InjectElement#getResourceToInject
方法。 - 如果想要完全自定义依赖注入,则需要重写
InjectElement#inject
方法。
从类图中我们可以看到,注入元素InjectElement
有以下子类:
AutowiredElement
:维护了依赖是否必选required
属性,使用自动装配的方式实现依赖注入。AutowiredFieldElement
:重写了InjectElement#inject
方法,实现了@Autowired
,@Inject
等自动装配注解的字段注入。AutowiredMethodElement
:重写了InjectElement#inject
方法,实现了@Autowired
,@Inject
等自动装配注解的方法注入。
LookupElement
:维护了依赖名称name
等属性,使用依赖查找的方式实现依赖注入。EjbRefElement
:重写了InjectElement#getResourceToInject
方法,实现了@Ejb
注解的字段注入和方法注入。ResourceElement
:重写了InjectElement#getResourceToInject
方法,实现了@Resource
注解的字段注入和方法注入。
InjectElement
使用了模板方法模式,子类可通过重写InjectElement#getResourceToInject
方法来实现不同的依赖注入方式。InjectMetadata
使用了组合模式,其维护了Bean进行依赖注入所需的InjectElement
列表,当对Bean进行依赖注入时,InjectMetadata#inject
方法会依次调用每个InjectElement
的InjectElement#inject
方法来进行依赖注入。
2. 自动装配注解依赖注入详解
使用@Autowired
,@Value
,@Inject
等自动装配注解完成依赖注入的流程图如下:
2.1. 如何获取Bean的注入元数据?
当调用自动装配Bean后置处理器的AutowiredAnnotationBeanPostProcessor#postProcessProperties
方法时,会获取该Bean的注入元数据,为后续的依赖注入做准备。获取Bean的注入元数据的流程如下:
- 首先,会调用
ReflectionUtils#doWithLocalFields
方法通过反射的方式执行依次遍历该Bean的各个字段Field
,来构建自动装配字段注入元素AutowiredFieldElement
; - 而后,会调用
ReflectionUtils#doWithLocalMethods
方法通过反射的方式依次遍历该Bean的各个方法Method
,来构建自动装配方法注入元素AutowiredMethodElement
; - 最后,组合所有的注入元素构建注入元数据
InjectMetadata
并返回。
对应源码所在位置:
AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata
。
2.2. 自动装配注解的字段注入是如何实现的?
在获取到Bean的注入元数据InjectMetadata
后,会依次调用注入元素InjectElement
的InjectElement#inject
方法来完成依赖注入。其中自动装配字段注入元素AutowiredFieldElement
的AutowiredFieldElement#inject
方法的流程如下:
- 首先,根据字段
Field
构建依赖描述符DependencyDescriptor
; - 而后,通过
AutowireCapableBeanFactory#resolveDependency
解析依赖描述符DependencyDescriptor
所对应的依赖; - 最后,将解析到的依赖通过反射的方式
Field#set
设置到Bean中,实现字段注入。对应源码所在位置:
AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
。
2.3. 自动装配注解的方法注入是如何实现的?
在获取到Bean的注入元数据InjectMetadata
后,会依次调用注入元素InjectElement
的InjectElement#inject
方法来完成依赖注入。其中自动装配方法注入元素AutowiredMethodElement
的AutowiredMethodElement#inject
方法的流程如下:
- 首先,根据方法
Method
中的参数依次构建依赖描述符DependencyDescriptor
; - 而后,通过
AutowireCapableBeanFactory#resolveDependency
解析每个依赖描述符DependencyDescriptor
所对应的依赖; - 最后,当所有的方法参数所对应的依赖解析完成后,通过反射的方式
Method#invoke
调用注入方法,实现方法注入。
对应源码所在位置:
AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject
。
2.4. 自动装配注解的构造器注入是如何实现的?
- 在创建Bean实例的时候,也就是使用
AbstractAutowireCapableBeanFactory#createBeanInstance
方法的时候,会调用SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors
来获取自动装配构造器候选者列表; - 如果自动装配构造器候选者列表不为空,则会通过
ConstructorResolver#autowireConstructor
方法从列表中选举最终的自动装配构造器候选者并实例化Bean。
对应源码所在位置:
AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors
。ConstructorResolver#autowireConstructor
。
2.5. 推荐使用哪种依赖注入方式?
- 对于字段注入而言,编码最简单,也是开发中最常用,可以解决循环依赖的问题;
- 对于方法注入而言,可以解决循环依赖的问题,常用于注入可选依赖;但编码会显得臃肿;
- 对于构造器注入而言(Spring官方推荐使用),常用于注入必选依赖,能够保证依赖注入的顺序;但不能解决循环依赖的问题,且当要注入的依赖过多时,构造器方法会有过多参数,应该考虑类的设计是否合理。
如果需要注入的依赖较少或强调是必选依赖,则使用构造器注入;如果需要强调是可选依赖,则使用方法注入;否者,使用字段注入。在同一个Bean中也可同时使用以上三种依赖注入的方式。
3. 通用注解依赖注入详解
使用@Resource
,@Ejb
等通用注解完成依赖注入的流程图如下:
3.1. 如何获取Bean的注入元数据
当调用自动装配Bean后置处理器的CommonAnnotationBeanPostProcessor#postProcessProperties
方法时,会获取该Bean的注入元数据,为后续的依赖注入做准备。获取Bean的注入元数据的流程如下:
- 首先,会调用
ReflectionUtils#doWithLocalFields
方法通过反射的方式执行依次遍历该Bean的各个字段Field
,来构建注入元素EjbRefElement
或ResourceElement
; - 而后,会调用
ReflectionUtils#doWithLocalMethods
方法通过反射的方式依次遍历该Bean的各个方法Method
,来构建注入元素EjbRefElement
或ResourceElement
; - 最后,组合所有的注入元素构建注入元数据
InjectMetadata
并返回。
对应源码所在位置:
CommonAnnotationBeanPostProcessor#findResourceMetadata
。
3.2. @Resource注解的字段注入和方法注入是如何实现的?
在获取到Bean的注入元数据InjectMetadata
后,会依次调用注入元素InjectElement
的InjectElement#inject
方法来完成依赖注入。以通用注解中的@Resource
注解为例,其对应的资源注入元素ResourceElement
的ResourceElement#inject
方法的流程如下:
- 首先,会判断当前注入元素来源于字段还是方法;
- 如果是字段
Field
的话,会通过ResourceElement#getResourceToInject
方法获取资源,并通过反射的方式Field#set
设置到Bean中,实现字段注入; - 如果是方法
Method
的话,会通过ResourceElement#getResourceToInject
方法获取资源,并通过反射的方式Method#invoke
调用注入方法,实现方法注入。
对应源码所在位置:
ResourceElement#inject
。
资源注入元素获取资源的ResourceElement#getResourceToInject
方法的实现逻辑如下图:
对应源码所在位置:
CommonAnnotationBeanPostProcessor#getResource
。
3.3. @Autowired和@Resouce有什么区别?
- 从实现上看,
@Autowired
是Spring提供的注解,由AutowiredAnnotationBeanPostProcessor
实现依赖注入逻辑;@Resource
是JSR提供的注解,由CommonAnnotationBeanPostProcessor
实现依赖注入逻辑; - 从使用上看,
@Autowired
支持字段注入,方法注入和构造器注入;@Resource
支持字段注入和方法注入,不支持构造器注入; - 从依赖的获取方式上看,
@Autowired
根据类型进行依赖的获取;而@Resource
会先尝试通过名称进行依赖的获取,未获取到则通过类型进行依赖的获取。
4. 如何解析依赖描述符对应的依赖?
依赖描述符DependencyDescriptor
用来描述被注入的依赖的信息,包括字段,方法参数或构造器参数,是否必须,依赖的类型等信息。根据依赖描述符解析依赖的方法是AutowireCapableBeanFactory#resolveDependency
,由DefaultListableBeanFactory#resolveDependency
实现。类图如下:
4.1. 解析特殊类型的依赖
- 对于类型为
Optional
的依赖,会构建一个required
属性为false
的NestedDependencyDescriptor
依赖描述符,然后解析依赖后构建Optional
对象并返回; - 对于类型为
ObjectFactory
或ObjectProvider
的依赖,会构建DependencyObjectProvider
对象并返回;在调用DependencyObjectProvider#getObject
等方法时,才会解析依赖,是延迟依赖注入的一种实现方式; - 对于类型为
Provider
的依赖,会构建Jsr330Provider
对象并返回,其是DependencyObjectProvider
的子类;在调用Jsr330Provider#get
方法时,会调用父类的DependencyObjectProvider#getObject
方法,才会解析依赖,是延迟依赖注入的一种实现方式; - 对于标注了
@Lazy
注解的依赖,会构建延迟依赖注入代理对象并返回;在使用代理对象时,才会解析依赖,是延迟依赖注入的一种实现方式。
对应源码所在位置:
DefaultListableBeanFactory#resolveDependency
。
4.2. 解析建议值的依赖
对于标注了@Value
注解的依赖,处理逻辑如下:
- 通过
QualifierAnnotationAutowireCandidateResolver#extractValue
方法获取@Value
注解的value
属性; - 通过
StringValueResolver#resolveStringValue
方法替换占位符; - 通过
BeanExpressionResolver#evaluate
方法解析SpringEL
表达式; - 通过
TypeConverter#convertIfNecessary
方法进行类型转换并返回。
对应源码所在位置:
DefaultListableBeanFactory#resolveDependency
。
4.3. 解析多值类型的依赖
- 对于依赖描述符类型为
StreamDependencyDescriptor
的依赖,会先获取自动装配候选者,然后构建排序后的Stream
对象并返回; - 对于类型为
Array
的依赖,会先获取自动装配候选者,然后构建排序后的Array
对象并返回; - 对于类型为
Collection
的依赖,会先获取自动装配候选者,然后构建排序后的Collection
对象并返回; - 对于类型为
Map
的依赖,会先获取自动装配候选者,然后构建Map
对象并返回。
对应源码所在位置:
DefaultListableBeanFactory#resolveMultipleBeans
。
4.4. 获取满足自动装配条件的自动装配候选者列表
- 从
DefaultListableBeanFactory#resolvableDependencies
内建依赖中获取满足自动装配条件的候选者,并添加到自动装配候选者列表中; - 从
BeanFactoryUtils#beanNamesForTypeIncludingAncestors
类型匹配的Bean名称中获取满足自动装配条件的候选者,并添加到自动装配候选者列表中; - 如果自动装配候选者列表为空,则获取满足自身注入自动装配条件的候选者,并添加到自动装配候选者列表中。
对应源码所在位置:
DefaultListableBeanFactory#findAutowireCandidates
。DefaultListableBeanFactory#isAutowireCandidate
。
4.5. 从自动装配候选者列表中选举最终的自动装配候选者
- 选举标注了
@Primary
的自动装配候选者作为最终的自动装配候选者; - 如果还未选举出来,则选举
AnnotationAwareOrderComparator#getPriority
优先级最高的自动装配候选者作为最终的自动装配候选者; - 如果还未选举出来,则选举内建依赖名称或依赖的名称同自动装配候选者的Bean名称相同的自动装配候选者作为最终的自动装配候选者。
- 如果还未选举出来,返回
null
作为最终的自动装配候选者。
对应源码所在位置:
DefaultListableBeanFactory#determineAutowireCandidate
。
5. 实战
5.1. 如何实现自定义依赖注入方式标注注入?
我们定义一种注入方式为标注注入,标注注入是指将标注特定注解的依赖通过标注注入的方式注入到Bean中。标注注入使用的注解我们定义为@Marked
,标注在依赖上的测试注解我们定义为@Print
,首先来看预期效果:
import com.remeio.upsnippet.spring.di.markinject.Marked;
import com.remeio.upsnippet.spring.di.markinject.MarkedAnnotationBeanPostProcessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.Set;
@Configuration
public class MarkInjectTest {
@Marked(Print.class)
private Set<Printable> printables;
@PostConstruct
public void init() {
final String message = "Hello, world";
printables.forEach(e -> e.print(message));
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan(MarkInjectTest.class.getPackage().getName());
context.register(MarkedAnnotationBeanPostProcessor.class);
context.refresh();
}
}
import org.springframework.stereotype.Component;
@Print
@Component
public class ConsolePrint implements Printable {
@Override
public void print(String message) {
System.out.println(message);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Print
@Component
@Slf4j
public class LogPrint implements Printable {
@Override
public void print(String message) {
log.info("{}", message);
}
}
我们将所有标注了@Print
注解且类型是Printable
的Bean通过标注注入的方式注入到了MarkInjectTest
的字段printables
中,运行结果如下。
[com.remeio.upsnippet.spring.di.markinject.test.ConsolePrint] : Hello, world
Hello, world
实现逻辑在MarkedAnnotationBeanPostProcessor#postProcessProperties
方法中,实现逻辑如下:
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Marked {
Class<? extends Annotation> value();
}
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
@Component
public class MarkedAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {
protected final Log logger = LogFactory.getLog(getClass());
private final Set<Class<? extends Annotation>> annotationTypes = new LinkedHashSet<>();
private ConfigurableListableBeanFactory beanFactory;
public MarkedAnnotationBeanPostProcessor() {
this.annotationTypes.add(Marked.class);
}
public MarkedAnnotationBeanPostProcessor(Set<Class<? extends Annotation>> annotationTypes) {
this.annotationTypes.addAll(annotationTypes);
}
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
InjectionMetadata metadata = findAnnotationInjectMetadata(bean.getClass());
try {
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of mark dependencies failed", ex);
}
return pvs;
}
private InjectionMetadata findAnnotationInjectMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.annotationTypes)) {
return InjectionMetadata.EMPTY;
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Mark annotation is not supported on static fields: " + field);
}
return;
}
currElements.add(new MarkFieldInjectElement(field, ann));
}
});
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return InjectionMetadata.forElements(elements, clazz);
}
private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
MergedAnnotations annotations = MergedAnnotations.from(ao);
for (Class<? extends Annotation> type : this.annotationTypes) {
MergedAnnotation<?> annotation = annotations.get(type);
if (annotation.isPresent()) {
return annotation;
}
}
return null;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
private class MarkFieldInjectElement extends InjectionMetadata.InjectedElement {
private final MergedAnnotation<? extends Annotation> mergedAnnotation;
protected MarkFieldInjectElement(Field field, MergedAnnotation<? extends Annotation> mergedAnnotation) {
super(field, null);
this.mergedAnnotation = mergedAnnotation;
}
@Override
protected void inject(Object bean, String requestingBeanName, PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value = resolveFieldValue();
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
@Nullable
private Object resolveFieldValue() {
DependencyDescriptor descriptor = new DependencyDescriptor((Field) this.member, false);
Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
final Class<? extends Annotation> annotationType = (Class<? extends Annotation>) this.mergedAnnotation.getClass("value");
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(beanFactory, annotationType);
if (beanNames.length == 0) {
return null;
}
List<?> result = Arrays.stream(beanNames)
.filter(e -> beanFactory.isTypeMatch(e, elementType))
.map(e -> beanFactory.getBean(e, elementType))
.collect(Collectors.toList());
return beanFactory.getTypeConverter().convertIfNecessary(result, descriptor.getDependencyType());
}
}
}