本文基于極客時間《設(shè)計模式之美》中的自定義DI框架而來,原文提供了大體設(shè)計,本文將示例進行了補全和完善。
本文的補充點:
- 使用Dom4j,實現(xiàn)了配置文件的解析,并轉(zhuǎn)化成BeanDefinition對象集合
- 解決了反射根據(jù)有參構(gòu)造函數(shù)創(chuàng)建對象的類型問題
DI,Dependency Injection,即依賴注入,spring使用這種方式創(chuàng)建對象放到一個容器中,業(yè)務(wù)代碼需要使用,直接從容器中拿。
分離了對象的創(chuàng)建和使用,使代碼解藕,業(yè)務(wù)代碼更加清晰。
底層使用工廠模式,封裝了復(fù)雜對象的創(chuàng)建過程,有些對象的創(chuàng)建過程繁瑣,有些對象需要依賴其它對象,這些創(chuàng)建過程可以通通交給spring即可。
一、實現(xiàn)思路
三步走:
- 定義配置文件。
容器不只是創(chuàng)建某類對象,不能在代碼中寫死,所以將需要被spring創(chuàng)建的對象放到配置文件中,也是隔離變化的體現(xiàn)。 - 解析配置文件。
采用Dom4j技術(shù),解析xml配置文件。
定義BeanDefinition對象,用于接收解析后的對象信息。 - 將解析得到的對象信息,通過反射生成對象,并放到容器中。
二、具體實現(xiàn)
-
代碼結(jié)構(gòu)
image.png
pom依賴
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
- 具體代碼
2.1 需要被創(chuàng)建的對象
package org.example.model;
import lombok.Data;
@Data
public class RateLimiter {
private RedisCounter redisCounter;
public RateLimiter(RedisCounter redisCounter) {
this.redisCounter = redisCounter;
}
public void test() {
System.out.println("Hello World!");
}
}
package org.example.model;
import lombok.Data;
@Data
public class RedisCounter {
private String ipAddress;
private int port;
public RedisCounter(String ipAddress, int port) {
this.ipAddress = ipAddress;
this.port = port;
}
}
2.2 配置文件applicationContext.xml
<beans>
<bean id="rateLimiter" class="org.example.model.RateLimiter">
<constructor-arg ref="redisCounter"/>
</bean>
<bean id="redisCounter" class="org.example.model.RedisCounter" scope="prototype">
<constructor-arg type="String" value="127.0.0.1"/>
<constructor-arg type="int" value="1234"/>
</bean>
</beans>
2.3 上下文對象
package org.example.context;
public interface ApplicationContext {
Object getBean(String beanId);
}
package org.example.context;
import org.example.BeanDefinition;
import org.example.factory.BeansFactory;
import org.example.parser.BeanConfigParser;
import org.example.parser.XmlBeanConfigParser;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* 1.組裝beansFactory和beanConfigParser
* 2.串聯(lián)執(zhí)行流程,讀取配置文件,通過beanConfigParser解析到beanDefinitions
* 3.beansFactory根據(jù)beanDefinitions加載對象
*/
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);
}
/**
* 配置文件解析,并將配置信息讀取轉(zhuǎn)化到BeanDefinition集合
* @param configLocation
*/
private void loadBeanDefinitions(String configLocation) {
InputStream in = null;
try {
in = this.getClass().getResourceAsStream("/"+configLocation);
if (in==null){
throw new RuntimeException("找不到配置文件:"+configLocation);
}
List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);
beansFactory.addBeanDefinitions(beanDefinitions);
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
try {
if (in!=null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public Object getBean(String beanId) {
return beansFactory.getBean(beanId);
}
}
2.4 工廠類
package org.example.factory;
import org.example.BeanDefinition;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* 1.提供獲取Bean對象的方法
* 2.根據(jù)beanDefinitions創(chuàng)建對象
*/
public class BeansFactory {
private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
public Object getBean(String beanId){
BeanDefinition beanDefinition = beanDefinitions.get(beanId);
if (beanDefinition==null){
throw new RuntimeException("bean is not defined,"+beanId);
}
return createBean(beanDefinition);
}
public void addBeanDefinitions(List<BeanDefinition> beanDefinitions) {
for (BeanDefinition beanDefinition : beanDefinitions) {
this.beanDefinitions.putIfAbsent(beanDefinition.getId(),beanDefinition);
}
for (BeanDefinition beanDefinition : beanDefinitions) {
if (!beanDefinition.isLazyInit()&&beanDefinition.isSingleton()){
createBean(beanDefinition);
}
}
}
private 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.getValue();
} else {
BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getRef());
if (refBeanDefinition == null) {
throw new RuntimeException("Bean is not defined: " + arg.getValue());
}
argClasses[i] = Class.forName(refBeanDefinition.getClassName());
argObjects[i] = createBean(refBeanDefinition);
}
}
//反射,根據(jù)有參構(gòu)造方法創(chuàng)建對象
bean = beanClass.getConstructor(argClasses).newInstance(argObjects);
}
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
if (bean != null && beanDefinition.isSingleton()) {
singletonObjects.putIfAbsent(beanDefinition.getId(), bean);
return singletonObjects.get(beanDefinition.getId());
}
return bean;
}
}
2.5 配置文件解析類
package org.example.parser;
import org.example.BeanDefinition;
import java.io.InputStream;
import java.util.List;
public interface BeanConfigParser {
List<BeanDefinition> parse(InputStream inputStream);
List<BeanDefinition> parser(String content);
}
package org.example.parser;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.example.BeanDefinition;
import org.example.enums.ClassEnums;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 配置文件內(nèi)容解析成BeanDefinition集合對象
*/
public class XmlBeanConfigParser implements BeanConfigParser {
@Override
public List<BeanDefinition> parse(InputStream inputStream) {
List<BeanDefinition> beanDefinitions = new ArrayList<>();
SAXReader saxReader = new SAXReader();
Document document = null;
try {
document = saxReader.read(inputStream);
//獲取bean節(jié)點集合
Element root = document.getRootElement();
List<Element> elements = root.selectNodes("http://bean");
if (elements == null || elements.size() == 0) {
throw new RuntimeException("無bean標簽");
}
for (Element element : elements) {
BeanDefinition beanDefinition = new BeanDefinition();
//獲取id和class屬性值
String id = element.attributeValue("id");
String className = element.attributeValue("class");
System.out.println("id:" + id + ",clazz:" + className);
List<Element> childElements = element.elements("constructor-arg");
List<BeanDefinition.ConstructorArg> constructorArgs = new ArrayList<>();
for (Element childElement : childElements) {
BeanDefinition.ConstructorArg constructorArg = new BeanDefinition.ConstructorArg();
String ref = childElement.attributeValue("ref");
String typeClassName = childElement.attributeValue("type");
Class typeClazz = null;
if (typeClassName != null) {
typeClazz = ClassEnums.getClass(typeClassName);
}
constructorArg.setRef(ref);
constructorArg.setType(typeClazz);
if (childElement.attributeValue("value") != null) {
if ("int".equals(typeClassName)) {//坑點!如果是int類型,需要將類型轉(zhuǎn)換,轉(zhuǎn)化為Integer類型
constructorArg.setValue(Integer.valueOf(childElement.attributeValue("value")));
} else {
constructorArg.setValue(childElement.attributeValue("value"));
}
}
constructorArgs.add(constructorArg);
}
if (element.attributeValue("scope")!=null){
beanDefinition.setScope(element.attributeValue("scope"));
}
beanDefinition.setId(id);
beanDefinition.setClassName(className);
beanDefinition.setConstructorArgs(constructorArgs);
beanDefinitions.add(beanDefinition);
}
} catch (DocumentException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println(beanDefinitions);
return beanDefinitions;
}
@Override
public List<BeanDefinition> parser(String content) {
return null;
}
}
2.6 接收配置文件解析數(shù)據(jù)的對象
package org.example;
import lombok.Data;
import java.util.List;
/**
* 配置文件中bean節(jié)點的映射對象
*/
@Data
public class BeanDefinition {
private String id;
private String className;
private List<ConstructorArg> constructorArgs;
private String scope = "singleton";
private boolean lazyInit = false;
public boolean isSingleton() {
return "singleton".equals(this.scope);
}
@Data
public static class ConstructorArg {
private String ref;
private Class type;
private Object value;
public boolean getIsRef() {
return ref != null || "".equals(ref);
}
}
}
2.7 基本數(shù)據(jù)類型的字節(jié)碼枚舉類。
package org.example.enums;
public enum ClassEnums {
STRING("String", String.class),
INT("int", Integer.TYPE);
private String type;
private Class typeClazz;
ClassEnums(String type, Class typeClazz) {
this.type = type;
this.typeClazz = typeClazz;
}
public static Class getClass(String type) {
for (ClassEnums classEnum : ClassEnums.values()) {
if (classEnum.type.equalsIgnoreCase(type)){
return classEnum.typeClazz;
}
}
throw new RuntimeException("ClassEnums沒有該類型");
}
}
三、說明
- 為什么要定義ClassEnums類?
因為在BeansFactory中,使用反射,對有參構(gòu)造方法實例化對象,getConstructor方法需要接受參數(shù)的字節(jié)碼數(shù)組。
我們需要得到配置文件中的“int”、“String”對應(yīng)的字節(jié)碼對象。
不能使用Class.forName("String")的方式獲取,所以就采用了枚舉的方式
//反射,根據(jù)有參構(gòu)造方法創(chuàng)建對象
bean = beanClass.getConstructor(argClasses).newInstance(argObjects);
Class源碼
@CallerSensitive
public Constructor<T> getConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return getConstructor0(parameterTypes, Member.PUBLIC);
}
使用反射,根據(jù)有參數(shù)構(gòu)造方法創(chuàng)建對象,示例
package org.example.model;
import lombok.Data;
import java.lang.reflect.InvocationTargetException;
@Data
public class RedisCounter {
private String ipAddress;
private int port;
public RedisCounter(String ipAddress, int port) {
this.ipAddress = ipAddress;
this.port = port;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
Class clazz = RedisCounter.class;
Object[] values = {"123","8888"};
Class[] classes = {String.class,Integer.TYPE};//{String.class,Integer.type};
RedisCounter redisCounter = (RedisCounter) clazz.getConstructor(classes).newInstance(values);
System.out.println(redisCounter);
System.out.println(classes);
}
}
四、踩坑
RedisCounter對象中port屬性是int類型,所以需要在使用方式創(chuàng)建對象時,將從配置文件獲取的值轉(zhuǎn)化成Integer類型。
說明:xml配置文件的屬性值必須帶雙引號,所以這步轉(zhuǎn)化是必不可少的。
if (childElement.attributeValue("value") != null) {
if ("int".equals(typeClassName)) {//坑點!如果是int類型,需要將類型轉(zhuǎn)換,轉(zhuǎn)化為Integer類型
constructorArg.setValue(Integer.valueOf(childElement.attributeValue("value")));
} else {
constructorArg.setValue(childElement.attributeValue("value"));
}
}
五、收獲
- 掌握了dom4j
- 學(xué)會了工廠模式設(shè)計思想
- 深刻理解了DI框架設(shè)計原理
- 加深了對面向?qū)ο笤O(shè)計思想的理解
比如,使用BeanDefinition接收配置解析信息,ClassPathXmlApplicationContext組裝對象,串聯(lián)流程,讓類的職責、代碼流程更加清晰
參考:
https://time.geekbang.org/column/article/198614
https://blog.csdn.net/zhouyingge1104/article/details/83069886
