前言
本文是 如何實現一個簡易版的 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 ,感興趣的朋友可以檢視完整程式碼。