實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 IOC 容器 (一)
這篇文章主要講一下如何使用 Java 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 IOC 容器,這里該系列的第一篇,要實(shí)現(xiàn)的內(nèi)容的也相對(duì)簡(jiǎn)單,主要介紹一下 B 依賴 A 這種簡(jiǎn)單的關(guān)系是怎么實(shí)現(xiàn)的

Java 依賴注入標(biāo)準(zhǔn) JSR-330 實(shí)現(xiàn)
我們常常使用的 Java DI 框架包括 Spring 和 Guice,在 Java 規(guī)范中也定義了對(duì)依賴注入的基本規(guī)范,其就是 JSR-330
標(biāo)準(zhǔn)對(duì)依賴注入的使用進(jìn)行了定義, 但是對(duì)實(shí)現(xiàn)和配置未定義。包javax.inject對(duì)應(yīng)該標(biāo)準(zhǔn)。具體實(shí)現(xiàn)依賴于各個(gè)框架。
javax.injects
該包提供了如下5個(gè)注解(Inject、Qualifier、Named、Scope、Singleton)和1個(gè)接口(Provider)。
@Inject
標(biāo)識(shí)某個(gè)類中,需要由注入器注入的類成員(被標(biāo)識(shí)的成員稱為”可注入的”)。
使用規(guī)則
可用于注解構(gòu)造器、字段、方法這些類成員(對(duì)成員是靜態(tài)與否、最好是public的)
每個(gè)類只能有一個(gè)構(gòu)造器可以被標(biāo)記可注入;空構(gòu)造器可以不用@Inject注解。
可注入的字段不能為 final 的
可注入的方法不能為 abstract 的
注入器的依賴注入順序
構(gòu)造器 > 字段 > 方法
父類 > 子類
一個(gè)類的兩個(gè)可注入字段或其他成員無注入順序
另外的四個(gè)注解對(duì)依賴注入進(jìn)一步進(jìn)行配置。
@Qualifier 和 @Named
其中,@Qualifiery用于創(chuàng)建限定器。限定器是一個(gè)自定義的注解,可注解字段或方法的參數(shù),用于限制可注入的依賴的類型。限定器注解必須被 @Qualifier 和 @Retention(RetentionPolicy.RUNTIME) 注解。
@Scope 和 @Singleton
其中,@Scope 用于創(chuàng)建作用域。作用域是一個(gè)自定義的注解,可注解構(gòu)造器,用于要求注入器對(duì)注入的實(shí)例的創(chuàng)建方式。比如,是每次構(gòu)造器被調(diào)用就創(chuàng)建一個(gè)依賴的實(shí)例,還是就創(chuàng)建一個(gè)依賴的實(shí)例然后重用。作用域注解必須被 @Scope 和 @Retention(RetentionPolicy.RUNTIME) 注解。
@Singleton 就是一個(gè)通過 @Scope 定義的作用域。
Provider<T>
Provider 作為另一種提供依賴的定義(有一種是 @Inject 注解),其實(shí)例提供 T 類型的實(shí)例。與 @Inject 注解相比,其還能:
返回的實(shí)例可以是多個(gè)
返回的實(shí)例可以是延遲返回的
返回的實(shí)例來自指定作用域內(nèi)
實(shí)現(xiàn)思路
在這里在不考慮 AOP 的情況進(jìn)行對(duì) @Inject 和 @Singleton 進(jìn)行實(shí)現(xiàn),也就是只實(shí)現(xiàn)一個(gè)單例類型的依賴注入

在這個(gè)關(guān)系中,Node 作為一個(gè)單例對(duì)象,且不依賴于其他;NodeB 作為一個(gè)單例對(duì)象,并依賴于 Node
在創(chuàng)建 NodeB 對(duì)象時(shí),發(fā)現(xiàn)需要進(jìn)行依賴注入,此時(shí)檢測(cè)依賴對(duì)象 Node 是否創(chuàng)建,如果沒有創(chuàng)建則創(chuàng)建 Node , 如果已經(jīng)創(chuàng)建,則進(jìn)行注入操作
大致流程:
- NodeB 是否原來創(chuàng)建過,如果創(chuàng)建過直接返回
- 獲取 NodeB 的空參構(gòu)造器和帶有 @Inject 的構(gòu)造器,如果無法找到對(duì)應(yīng)的構(gòu)造器則拋出異常
- 在選擇好的構(gòu)造器中優(yōu)先使用帶有 @Inject 的構(gòu)造器,如果沒有使用空參數(shù)構(gòu)造器
- 將當(dāng)前 NodeB 標(biāo)記為生成中
- 根據(jù) NodeB 的構(gòu)造器,獲取構(gòu)造器參數(shù),如果是空參則直接生成,如果不是空參,則判斷當(dāng)前參數(shù)類型是不是被標(biāo)記成生成中,如果被標(biāo)記成生成中則拋出循環(huán)依賴異常,否則從第一步開始創(chuàng)建對(duì)應(yīng)的對(duì)象,直到 NodeB 的所有的構(gòu)造器依賴的參數(shù)都創(chuàng)建完成,進(jìn)行有參構(gòu)造器生成。
- 假設(shè)上面生成的 NodeB 對(duì)應(yīng)的對(duì)象實(shí)例為 baby
- 獲取 body 的所有的字段屬性,并找出帶有 Inject.class 注解的屬性
- 根據(jù)上面篩選出來的 Field, 獲取 Field 對(duì)應(yīng)的類型,如果對(duì)應(yīng)類型已經(jīng)生成,則直接賦值,如果對(duì)應(yīng)的類型未生成,則從第一步開始生成指定的類型的實(shí)例對(duì)象
- 獲取 body 的所有非私有方法,并找出帶有 Inject.class 注解的方法
- 根據(jù)上面篩選出來的方法找到方法的參數(shù),類似于構(gòu)造器有參方法的步驟獲取到所有的方法參數(shù),并反射調(diào)用
- 生成之后將 NodeB 的 生成中 標(biāo)記去除,并加入已經(jīng)生成結(jié)果中
具體實(shí)現(xiàn)
1. 定義個(gè)異常
這東西很簡(jiǎn)單,不做多解釋了
public class InjectException extends RuntimeException {
public InjectException() {
super();
}
public InjectException(String message, Throwable cause) {
super(message, cause);
}
public InjectException(String message) {
super(message);
}
public InjectException(Throwable cause) {
super(cause);
}
}
2. 定義一個(gè)容器 Injector
先確定一下最基本的
- finalSingletonMap 用來保存已經(jīng)生成好的實(shí)例
- processingInstances 用來保存處理中的類型
- getInstance 一個(gè)公共方法,用來獲取對(duì)應(yīng)的類型實(shí)例
public class Injector {
// 已經(jīng)生成的單例
private final Map<Class<?>, Object> finalSingletonMap = Collections.synchronizedMap(new HashMap<>());
// 準(zhǔn)備進(jìn)行構(gòu)造的類
private final Set<Class<?>> processingInstances = Collections.synchronizedSet(new HashSet<>());
public <T> T getInstance(Class<T> clazz) {
return createNew(clazz);
}
}
3. 構(gòu)造器處理邏輯
3.1 獲取構(gòu)造器 createNew
這里 clazz 為我們要生成的實(shí)例的 class 類型
- 判斷類型是否已經(jīng)生成,如果生成則直接返回對(duì)應(yīng)的實(shí)例
- 根據(jù)構(gòu)造器生成對(duì)象實(shí)例
Object o = finalSingletonMap.get(clazz);
if (o != null) {
return (T) o;
}
ArrayList<Constructor<T>> constructors = new ArrayList<>();
T target = null;
// 獲取對(duì)應(yīng)的 class 的所有的構(gòu)造器
for (Constructor<?> con : clazz.getDeclaredConstructors()) {
if (con.isAnnotationPresent(Inject.class) && ReflectUtil.trySetAccessible(con)) {
// 優(yōu)先處理 Inject 構(gòu)造器
constructors.add(0, (Constructor<T>) con);
} else if (con.getParameterCount() == 0 && ReflectUtil.trySetAccessible(con)) {
// 再次獲取無參構(gòu)造器
constructors.add((Constructor<T>) con);
}
}
if (constructors.size() > 2) {
// 如果大于 2 說明存在多個(gè)被 Inject 標(biāo)記的構(gòu)造器,此時(shí)無確定優(yōu)先使用哪個(gè)
throw new InjectException("無法確定使用哪個(gè)構(gòu)造器進(jìn)行構(gòu)造 " + clazz.getCanonicalName());
}
if (constructors.size() == 0) {
// 如果不存在可用的構(gòu)造器,則無法構(gòu)造
throw new InjectException("無可用的構(gòu)造器 " + clazz.getCanonicalName());
}
processingInstances.add(clazz); // 放入表示未完成的容器
target = createFromConstructor(constructors.get(0)); // 構(gòu)造器注入
processingInstances.remove(clazz); // 從未完成的容器取出
// 處理所有的 field 注入
injectField(target);
injectMethod(target);
3.2 通過構(gòu)造器創(chuàng)建對(duì)象
獲取到構(gòu)造器所需的所有參數(shù)的類型,并創(chuàng)建對(duì)應(yīng)的類型,其創(chuàng)建步驟再次從 3.1 步驟開始
private <T> T createFromConstructor(Constructor<T> con) {
Object[] params = new Object[con.getParameterCount()];
int i = 0;
for (Parameter parameter : con.getParameters()) {
if (processingInstances.contains(parameter.getType())) {
throw new InjectException(
String.format("循環(huán)依賴 class is %s", con.getDeclaringClass().getCanonicalName()));
}
Object param = createFromParameter(parameter);
if (param == null) {
throw new InjectException(String.format("無法創(chuàng)建構(gòu)造器中的參數(shù) %s of class %s",
parameter.getName(), con.getDeclaringClass().getCanonicalName()));
}
params[i++] = param;
}
try {
return con.newInstance(params);
} catch (Exception e) {
throw new InjectException("create instance from constructor error", e);
}
}
@SuppressWarnings("unchecked")
private <T> T createFromParameter(Parameter parameter) {
Class<?> clazz = parameter.getType();
return (T) createNew(clazz);
}
4. 屬性 Field 處理邏輯
如果細(xì)看的話,其實(shí)和構(gòu)造器注入的邏輯是類似的,
- 獲取 body 的所有的字段屬性,并找出帶有 Inject.class 注解的屬性
- 根據(jù)上面篩選出來的 Field, 獲取 Field 對(duì)應(yīng)的類型,如果對(duì)應(yīng)類型已經(jīng)生成,則直接賦值,如果對(duì)應(yīng)的類型未生成,則從 3.1 開始生成指定的類型的實(shí)例對(duì)象
private <T> void injectField(T body) {
List<Field> fields = new ArrayList<>();
for (Field field : t.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class) && ReflectUtil.trySetAccessible(field)) {
fields.add(field);
}
}
for (Field field : fields) {
Object f = createFromField(field);
try {
field.set(t, f);
} catch (Exception e) {
throw new InjectException(
String.format("set field for %s@%s error", t.getClass().getCanonicalName(), field.getName()),
e);
}
}
}
private <T> T createFromField(Field field) {
Class<?> clazz = field.getType();
return (T) createNew(clazz);
}
5. 處理注入方法
這個(gè)和構(gòu)造器處理就大同小異了
private <T> void injectMethod(T body) {
List<Method> methods = new ArrayList<>();
Method[] declaredMethods = body.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.getParameterCount() > 0
&& declaredMethod.isAnnotationPresent(Inject.class)
&& ReflectUtil.trySetAccessible(declaredMethod)) {
methods.add(declaredMethod);
}
}
int i;
for (Method method : methods) {
Object[] params = new Object[method.getParameterCount()];
i = 0;
for (Parameter parameter : method.getParameters()) {
if (processingInstances.contains(parameter.getType())) {
throw new InjectException(
String.format("循環(huán)依賴 on method , the root class is %s", body.getClass().getCanonicalName()));
}
Object param = createFromParameter(parameter);
params[i++] = param;
}
try {
method.invoke(body, params);
} catch (Exception e) {
throw new InjectException("injectMethod ", e);
}
}
}
參考
完整代碼
Github:https://github.com/some-big-bugs/wheel-java-di
package com.github.sbb.di;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
public class Injector {
// 已經(jīng)生成的單例
private final Map<Class<?>, Object> finalSingletonMap = Collections.synchronizedMap(new HashMap<>());
// 準(zhǔn)備進(jìn)行構(gòu)造的類
private final Set<Class<?>> processingInstances = Collections.synchronizedSet(new HashSet<>());
/**
* 獲取對(duì)象
*
* @param clazz
* @return
*/
public <T> T getInstance(Class<T> clazz) {
return createNew(clazz);
}
@SuppressWarnings("unchecked")
private <T> T createNew(Class<T> clazz) {
Object o = finalSingletonMap.get(clazz);
if (o != null) {
return (T) o;
}
ArrayList<Constructor<T>> constructors = new ArrayList<>();
T target = null;
for (Constructor<?> con : clazz.getDeclaredConstructors()) {
// 優(yōu)先處理 Inject 構(gòu)造器
if (con.isAnnotationPresent(Inject.class) && ReflectUtil.trySetAccessible(con)) {
constructors.add(0, (Constructor<T>) con);
} else if (con.getParameterCount() == 0 && ReflectUtil.trySetAccessible(con)) {
constructors.add((Constructor<T>) con);
}
}
if (constructors.size() > 2) {
throw new InjectException("dupcated constructor for injection class " + clazz.getCanonicalName());
}
if (constructors.size() == 0) {
throw new InjectException("no accessible constructor for injection class " + clazz.getCanonicalName());
}
processingInstances.add(clazz); // 放入表示未完成的容器
target = createFromConstructor(constructors.get(0)); // 構(gòu)造器注入
injectField(target);
injectMethod(target);
processingInstances.remove(clazz); // 從未完成的容器取出
boolean isSingleton = clazz.isAnnotationPresent(Singleton.class);
if (isSingleton) {
finalSingletonMap.put(clazz, target);
}
return target;
}
private <T> T createFromConstructor(Constructor<T> con) {
Object[] params = new Object[con.getParameterCount()];
int i = 0;
for (Parameter parameter : con.getParameters()) {
if (processingInstances.contains(parameter.getType())) {
throw new InjectException(
String.format("循環(huán)依賴 on constructor, class is %s", con.getDeclaringClass().getCanonicalName()));
}
Object param = createFromParameter(parameter);
params[i++] = param;
}
try {
return con.newInstance(params);
} catch (Exception e) {
throw new InjectException("create instance from constructor error", e);
}
}
/**
* 注入成員
*
* @param t
*/
private <T> void injectField(T body) {
List<Field> fields = new ArrayList<>();
for (Field field : body.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class) && ReflectUtil.trySetAccessible(field)) {
fields.add(field);
}
}
for (Field field : fields) {
Object f = createFromField(field);
try {
field.set(body, f);
} catch (Exception e) {
throw new InjectException(
String.format("set field for %s@%s error", t.getClass().getCanonicalName(), field.getName()),
e);
}
}
}
private <T> void injectMethod(T body) {
List<Method> methods = new ArrayList<>();
Method[] declaredMethods = body.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.getParameterCount() > 0
&& declaredMethod.isAnnotationPresent(Inject.class)
&& ReflectUtil.trySetAccessible(declaredMethod)) {
methods.add(declaredMethod);
}
}
int i;
for (Method method : methods) {
Object[] params = new Object[method.getParameterCount()];
i = 0;
for (Parameter parameter : method.getParameters()) {
if (processingInstances.contains(parameter.getType())) {
throw new InjectException(
String.format("循環(huán)依賴 on method , the root class is %s", body.getClass().getCanonicalName()));
}
Object param = createFromParameter(parameter);
params[i++] = param;
}
try {
method.invoke(body, params);
} catch (Exception e) {
throw new InjectException("injectMethod ", e);
}
}
}
@SuppressWarnings("unchecked")
private <T> T createFromParameter(Parameter parameter) {
Class<?> clazz = parameter.getType();
return (T) createNew(clazz);
}
@SuppressWarnings("unchecked")
private <T> T createFromField(Field field) {
Class<?> clazz = field.getType();
return (T) createNew(clazz);
}
}