前言
本文是 如何實現一個簡易版的 Spring 系列第四篇,在 上篇 介紹了 @Component 注解的實現,這篇再來看看在使用 Spring 框架開發中常用的 @Autowired 注入要如何實現,大家用過 Spring 都知道,該注解可以用在字段、構造函數以及setter 方法上,限於篇幅原因我們主要討論用在字段的方式實現,其它的使用方式大體思路是相同的,不同的只是解析和注入方式有所區別,話不多說,下面進入我們今天的正題—如何實現一個簡易版的 Spring - 如何實現 @Autowired 注解。
實現步驟拆分
實現步驟總的來說分為三大步:
- 分析總結要做的事情,抽象出數據結構
- 利用這些數據結構來做一些事情
- 在某個時機注入到 Spring 容器中
細心的朋友可以發現,其實前面幾篇文章的實現也是套路,其中最為關鍵也是比較困難的點就是如何抽象出數據結構。這里我們要做的是當某個 Bean 上的字段有 @Autowired 注解時,從容器中獲取該類型的 Bean 然后調用該字段對應的 setter 方法設置到對象的屬性中。下面就跟着這個思路去實現 @Autowired 注解。
數據結構抽象
要想根據字段的類型注入在容器中對應的實例,首先需要提供這個從一個類型獲取對應 Bean 實例的能力,這需要 BeanFactory 接口提供一個這樣的能力,等等,像這樣容器內部使用的接口直接定義在 BeanFactory 好嗎?像這種內部的操作應該盡量做到對使用者透明,所以這里新加一個接口 AutowireCapableBeanFactory 繼承自 BeanFactory,這樣在內部就可以直接使用新接口接口。需要注意的是新接口的方法參數並不能直接使用 Class 類型去容器中查找對應的 Bean,為了后期的靈活擴展(比如:是否必須依賴等),需要使用一個類來描述這種依賴,命名為 DependencyDescriptor,其部分源碼如下所示:
/**
* @author mghio
* @since 2021-03-07
*/
public class DependencyDescriptor {
private Field field;
private boolean required;
public DependencyDescriptor(Field field, boolean required) {
Assert.notNull(field, "Field must not be null");
this.field = field;
this.required = required;
}
public Class<?> getDependencyType() {
if (this.field != null) {
return field.getType();
}
throw new RuntimeException("only support field dependency");
}
public boolean isRequired() {
return this.required;
}
}
接口 AutowireCapableBeanFactory 聲明如下:
/**
* @author mghio
* @since 2021-03-07
*/
public interface AutowireCapableBeanFactory extends BeanFactory {
Object resolveDependency(DependencyDescriptor descriptor);
}
查找解析依賴的功能我們抽象完成了,下面來看看核心步驟如何抽象封裝注入的過程,抽象總結后不難發現,注入可以分為兩大部分:注入的目標對象 和 需要被注入的元素列表,這些對於注入來說是一些元數據,命名為 InjectionMetadata,其包含兩個字段,一個是注入的目標對象,另一個是被注入的元素列表,還有一個重要的方法將元素列表注入到方法參數傳入的目標對象中去。
每個注入元素都要提供一個注入到指定目標對象的能力,所以抽取出公共抽象父類 InjectionElement,使用上文的 AutowireCapableBeanFactory 接口解析出當前字段類型對應 Bean,然后注入到指定的目標對象中。抽象父類 InjectinElement 的主要代碼如下:
/**
* @author mghio
* @since 2021-03-07
*/
public abstract class InjectionElement {
protected Member member;
protected AutowireCapableBeanFactory factory;
public InjectionElement(Member member, AutowireCapableBeanFactory factory) {
this.member = member;
this.factory = factory;
}
abstract void inject(Object target);
}
注入元數據類 InjectionMetadata 的主要代碼如下:
/**
* @author mghio
* @since 2021-03-07
*/
public class InjectionMetadata {
private final Class<?> targetClass;
private List<InjectionElement> injectionElements;
public InjectionMetadata(Class<?> targetClass, List<InjectedElement> injectionElements) {
this.targetClass = targetClass;
this.injectionElements = injectionElements;
}
public void inject(Object target) {
if (injectionElements == null || injectionElements.isEmpty()) {
return;
}
for (InjectionElement element : injectionElements) {
element.inject(target);
}
}
...
}
把一個 Class 轉換為 InjectionMetadata 的部分實現我們留到下文實現部分介紹,抽象后總的流程就是把一個 Class 轉換為 InjectionMedata ,然后調用 InjectionMedata 提供的 inject(Object) 方法來完成注入(依賴 AutowireCapableBeanFactory 接口提供的 resolveDependency(DependencyDescriptor) 能力),下面是抽象后的字段注入部分的相關類圖關系如下:
解析構造出定義的數據結構
在上文我們還沒實現將一個類轉換為 InjectionMetadata 的操作,也就是需要實現這樣的一個方法 InjectionMetadata buildAutowiringMetadata(Class<?> clz),實現過程也比較簡單,掃描類中聲明的屬性找到有 @Autowried 注解解析構造出 InjectinMetadata 實例,核心實現代碼如下:
/**
* @author mghio
* @since 2021-03-07
*/
public class AutowiredAnnotationProcessor {
private final String requiredParameterName = "required";
private boolean requiredParameterValue = true;
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>();
public AutowiredAnnotationProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
}
public InjectionMetadata buildAutowiringMetadata(Class<?> clz) {
LinkedList<InjectionElement> elements = new LinkedList<>();
Class<?> targetClass = clz;
do {
LinkedList<InjectionElement> currElements = new LinkedList<>();
for (Field field : targetClass.getDeclaredFields()) {
Annotation ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
boolean required = determineRequiredStatus(ann);
elements.add(new AutowiredFieldElement(field, required, beanFactory));
}
}
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);
return new InjectionMetadata(clz, elements);
}
protected boolean determineRequiredStatus(Annotation ann) {
try {
Method method = ReflectionUtils.findMethod(ann.annotationType(), this.requiredParameterName);
if (method == null) {
return true;
}
return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, ann));
} catch (Exception e) {
return true;
}
}
private Annotation findAutowiredAnnotation(AccessibleObject ao) {
for (Class<? extends Annotation> annotationType : this.autowiredAnnotationTypes) {
Annotation ann = AnnotationUtils.getAnnotation(ao, annotationType);
if (ann != null) {
return ann;
}
}
return null;
}
...
}
上面在做數據結構抽象時定義好了注入元素的抽象父類 InjectionElement,這里需要定義一個子類表示字段注入類型,命名為 AutowiredFieldElement,依賴 AutowireCapableBeanFactory 接口的能力解析出字段所屬類型的 Bean,然后調用屬性的 setter 方法完成注入,在基於我們上面定義好的數據結構后實現比較簡單,主要代碼如下:
/**
* @author mghio
* @since 2021-03-07
*/
public class AutowiredFieldElement extends InjectionElement {
private final boolean required;
public AutowiredFieldElement(Field field, boolean required, AutowireCapableBeanFactory factory) {
super(field, factory);
this.required = required;
}
public Field getField() {
return (Field) this.member;
}
@Override
void inject(Object target) {
Field field = this.getField();
try {
DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required);
Object value = factory.resolveDependency(descriptor);
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(target, value);
}
} catch (Throwable e) {
throw new BeanCreationException("Could not autowire field:" + field, e);
}
}
}
注入到 Spring 中
接下來面臨的問題是:要在什么時候調用上面這些類和方法呢?在這里我們回顧一下 Spring 中 Bean 的生命周期,其中幾個鈎子入口如下圖所示:
通過生命周期開放的鈎子方法可以看出我們需要在 InstantiationAwareBeanPostProcessor 接口的 postProcessPropertyValues 方法中實現 Autowired 注入,將前面的 AutowiredAnnotationProcessor 類實現該接口然后在 postProcessPropertyValues 方法處理注入即可。這部分的整體類圖如下所示:
AutowiredAnnotationProcessor 處理器實現的 postProcessPropertyValues() 方法如下:
/**
* @author mghio
* @since 2021-03-07
*/
public class AutowiredAnnotationProcessor implements InstantiationAwareBeanProcessor {
...
@Override
public void postProcessPropertyValues(Object bean, String beanName) throws BeansException {
InjectionMetadata metadata = this.buildAutowiringMetadata(bean.getClass());
try {
metadata.inject(bean);
} catch (Throwable e) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed");
}
}
}
然后只需要在抽象父類 AbstractApplicationContext 構造函數注冊那些我們定義的 processor,然后在 Bean 注入的時候(DefaultBeanFactory.populateBean())調用 processor 的 postProcessPropertyValues 方法完成屬性注入,抽象類 AbstractApplicationContext 改動部分的代碼如下:
/**
* @author mghio
* @since 2021-03-07
*/
public abstract class AbstractApplicationContext implements ApplicationContext {
...
public AbstractApplicationContext(String configFilePath) {
...
registerBeanPostProcessor(beanFactory);
}
protected void registerBeanPostProcessor(ConfigurableBeanFactory beanFactory) {
AutowiredAnnotationProcessor postProcessor = new AutowiredAnnotationProcessor();
postProcessor.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(postProcessor);
}
...
}
BeanFactory 接口的默認實現類 DefaultBeanFactory 注入 Bean 屬性的方法 populateBean(BeanDefinition, Object) 改動如下:
/**
* @author mghio
* @since 2021-03-07
*/
public class DefaultBeanFactory extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory,
BeanDefinitionRegistry {
...
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
private void populateBean(BeanDefinition bd, Object bean) {
for (BeanPostProcessor postProcessor : this.getBeanPostProcessors()) {
if (postProcessor instanceof InstantiationAwareBeanProcessor) {
((InstantiationAwareBeanProcessor) postProcessor).postProcessPropertyValues(bean, bd.getId());
}
}
...
}
...
}
總的來說整個使用 processor 的過程分為兩步,首先在 AbstractApplicationContext 構造方法中注冊我們自定義的 processor,然后再 DefaultBeanFactory 中調用其 postProcessPropertyValues 方法進行注入,至此使用在類字段上的 @Autowired 注解實現完成。
總結
本文簡要介紹了實現 Spring 的 @Autowired 注解(使用在類字段上的方式),其中比較麻煩的步驟是數據結構抽象部分,需要考慮到后期的擴展性和內部操作對使用者盡量透明,限於篇幅,只列出了部分核心實現代碼,完整代碼已上傳至 GitHub ,感興趣的朋友可以查看完整代碼。