設計模式之美 - 45
簡單的 DI 容器的實作原理,其核心邏輯主要包括:
- 配置檔案解析
- 以及根據配置檔案通過“反射”文法來建立對象
其中,建立對象的過程就應用到了我們在學的工廠模式。對象建立、組裝、管理完全有 DI 容器來負責,跟具體業務代碼解耦,讓程式員聚焦在業務代碼的開發上。
目錄
一、工廠模式和 DI 容器有何差別?
二、DI 容器的核心功能有哪些?
1、配置解析
2、對象建立
3、對象生命周期管理
三、如何實作一個簡單的DI容器
1、最小原型設計
2、提供執行入口
3、配置檔案解析
4、核心工廠類設計
一、工廠模式和 DI 容器有何差別?
DI 容器底層最基本的設計思路就是基于工廠模式的。
DI 容器相當于一個大的工廠類,負責在程式啟動的時候,根據配置(要建立哪些類對象,每個類對象的建立需要依賴哪些其他類對象)事先建立好對象。當應用程式需要使用某個類對象的時候,直接從容器中擷取即可。正是因為它持有一堆對象,是以這個架構才被稱為“容器”。
二、DI 容器的核心功能有哪些?
簡單的DI容器的核心功能一般有三個:配置解析、對象建立 和 對象生命周期管理
1、配置解析
架構代碼跟應用代碼應該是高度解耦的,DI 容器事先并不知道應用會建立哪些對象,我們需要通過一種形式,讓應用告知 DI 容器要建立哪些對象。這種形式就是配置。
我們将需要由 DI 容器來建立的類對象和建立類對象的必要資訊(使用哪個構造函數以及對應的構造函數參數都是什麼等等),放到配置檔案中。容器讀取配置檔案,根據配置檔案提供的資訊來建立對象。
典型的Spring容器的配置檔案:
Spring 容器讀取這個配置檔案,解析出要建立的兩個對象:rateLimiter 和 redisCounter,并且得到兩者的依賴關系:rateLimiter 依賴 redisCounter。
<beans>
<bean id="rateLimiter" class="com.xzg.RateLimiter">
<constructor-arg ref="redisCounter"/>
</bean>
<bean id="redisCounter" class="com.xzg.redisCounter">
<constructor-arg type="String" value="127.0.0.1">
<constructor-arg type="int" value=1234>
</bean>
</beans>
2、對象建立
在 DI 容器中,如果我們給每個類都對應建立一個工廠類,那項目中類的個數會成倍增加,這會增加代碼的維護成本。要解決這個問題并不難。我們隻需要将所有類對象的建立都放到一個工廠類中完成就可以了,比如 BeansFactory。
問題:如果要建立的類對象非常多,BeansFactory 中的代碼會不會線性膨脹(代碼量跟建立對象的個數成正比)呢?
實際上并不會。“反射”這種機制,它能在程式運作的過程中,動态地加載類、建立對象,不需要事先在代碼中寫死要建立哪些對象。是以,不管是建立一個對象還是十個對象,BeansFactory 工廠類代碼都是一樣的。
3、對象生命周期管理
對象建立方式:通過配置scope屬性
- scope=prototype 表示傳回新建立的對象
- scope=singleton 表示傳回單例對象。
對象建立時間:
- lazy-init=true,對象在真正被使用到的時候(比如:BeansFactory.getBean(“userService”))才被被建立;
- lazy-init=false,對象在應用啟動的時候就事先建立好。
對象的初始化和銷毀:
- init-method=loadProperties(),DI 容器在建立好對象之後,會主動調用 init-method 屬性指定的方法來初始化對象。
- destroy-method=updateConfigFile(),在對象被最終銷毀之前,DI 容器會主動調用 destroy-method 屬性指定的方法來做一些清理工作,比如釋放資料庫連接配接池、關閉檔案。
三、如何實作一個簡單的DI容器
用 Java 語言來實作一個簡單的 DI 容器,核心邏輯隻需要包括這樣兩個部分:
- 配置檔案解析
- 根據配置檔案通過“反射”文法來建立對象。
1、最小原型設計
xml配置檔案
<beans>
<bean id="rateLimiter" class="com.xzg.RateLimiter">
<constructor-arg ref="redisCounter"/>
</bean>
<bean id="redisCounter" class="com.xzg.redisCounter" scope="singleton" lazy-init="true">
<constructor-arg type="String" value="127.0.0.1">
<constructor-arg type="int" value=1234>
</bean>
</bean
public class Demo {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"beans.xml");
RateLimiter rateLimiter = (RateLimiter) applicationContext.getBean("rateLimiter");
rateLimiter.test();
//...
}
}
2、提供執行入口
main方法中的執行入口主要包含兩部分:ApplicationContext 和 ClassPathXmlApplicationContext。
public interface ApplicationContext {
Object getBean(String beanId);
}
public class ClassPathXmlApplicationContext implements ApplicationContext {
private BeansFactory beansFactory;
private BeanConfigParser beanConfigParser;
public ClassPathXmlApplicationContext(String configLocation) {
this.beansFactory = new BeansFactory();
this.beanConfigParser = new XmlBeanConfigParser();
loadBeanDefinitions(configLocation);
}
private void loadBeanDefinitions(String configLocation) {
InputStream in = null;
try {
in = this.getClass().getResourceAsStream("/" + configLocation);
if (in == null) {
throw new RuntimeException("Can not find config file: " + configLocation);
}
List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);
beansFactory.addBeanDefinitions(beanDefinitions);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// TODO: log error
}
}
}
}
@Override
public Object getBean(String beanId) {
return beansFactory.getBean(beanId);
}
}
3、配置檔案解析
配置檔案解析主要包含 BeanConfigParser 接口和 XmlBeanConfigParser 實作類,負責将配置檔案解析為 BeanDefinition 結構,以便 BeansFactory 根據這個結構來建立對象。
public interface BeanConfigParser {
List<BeanDefinition> parse(InputStream inputStream);
List<BeanDefinition> parse(String configContent);
}
public class XmlBeanConfigParser implements BeanConfigParser {
@Override
public List<BeanDefinition> parse(InputStream inputStream) {
String content = null;
// TODO:...
return parse(content);
}
@Override
public List<BeanDefinition> parse(String configContent) {
List<BeanDefinition> beanDefinitions = new ArrayList<>();
// TODO:...
return beanDefinitions;
}
}
public class BeanDefinition {
private String id;
private String className;
private List<ConstructorArg> constructorArgs = new ArrayList<>();
private Scope scope = Scope.SINGLETON;
private boolean lazyInit = false;
// 省略必要的getter/setter/constructors
public boolean isSingleton() {
return scope.equals(Scope.SINGLETON);
}
public static enum Scope {
SINGLETON,
PROTOTYPE
}
public static class ConstructorArg {
private boolean isRef;
private Class type;
private Object arg;
// 省略必要的getter/setter/constructors
}
}
4、核心工廠類設計
BeansFactory 是如何設計和實作的。這也是我們這個 DI 容器最核心的一個類了。它負責根據從配置檔案解析得到的 BeanDefinition 來建立對象。
BeansFactory 建立對象用到的主要技術點就是 Java 中的反射文法:一種動态加載類和建立對象的機制。
JVM 在啟動的時候會根據代碼自動地加載類、建立對象。至于都要加載哪些類、建立哪些對象,這些都是在代碼中寫死的,或者說提前寫好的。但是,如果某個對象的建立并不是寫死在代碼中,而是放到配置檔案中,我們需要在程式運作期間,動态地根據配置檔案來加載類、建立對象,那這部分工作就沒法讓 JVM 幫我們自動完成了,我們需要利用 Java 提供的反射文法自己去編寫代碼。
public class BeansFactory {
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();
public void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) {
for (BeanDefinition beanDefinition : beanDefinitionList) {
this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition);
}
for (BeanDefinition beanDefinition : beanDefinitionList) {
if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton()) {
createBean(beanDefinition);
}
}
}
public Object getBean(String beanId) {
BeanDefinition beanDefinition = beanDefinitions.get(beanId);
if (beanDefinition == null) {
throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId);
}
return createBean(beanDefinition);
}
@VisibleForTesting
protected Object createBean(BeanDefinition beanDefinition) {
if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition.getId())) {
return singletonObjects.get(beanDefinition.getId());
}
Object bean = null;
try {
Class beanClass = Class.forName(beanDefinition.getClassName());
List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs();
if (args.isEmpty()) {
bean = beanClass.newInstance();
} else {
Class[] argClasses = new Class[args.size()];
Object[] argObjects = new Object[args.size()];
for (int i = 0; i < args.size(); ++i) {
BeanDefinition.ConstructorArg arg = args.get(i);
if (!arg.getIsRef()) {
argClasses[i] = arg.getType();
argObjects[i] = arg.getArg();
} else {
BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg());
if (refBeanDefinition == null) {
throw new NoSuchBeanDefinitionException("Bean is not defined: " + arg.getArg());
}
argClasses[i] = Class.forName(refBeanDefinition.getClassName());
argObjects[i] = createBean(refBeanDefinition);
}
}
bean = beanClass.getConstructor(argClasses).newInstance(argObjects);
}
} catch (ClassNotFoundException | IllegalAccessException
| InstantiationException | NoSuchMethodException | InvocationTargetException e) {
throw new BeanCreationFailureException("", e);
}
if (bean != null && beanDefinition.isSingleton()) {
singletonObjects.putIfAbsent(beanDefinition.getId(), bean);
return singletonObjects.get(beanDefinition.getId());
}
return bean;
}
}