本文基于极客时间《设计模式之美》中的自定义DI框架而来,原文提供了大体设计,本文将示例进行了补全和完善。
本文的补充点:
- 使用Dom4j,实现了配置文件的解析,并转化成BeanDefinition对象集合
- 解决了反射根据有参构造函数创建对象的类型问题
DI,Dependency Injection,即依赖注入,spring使用这种方式创建对象放到一个容器中,业务代码需要使用,直接从容器中拿。
分离了对象的创建和使用,使代码解藕,业务代码更加清晰。
底层使用工厂模式,封装了复杂对象的创建过程,有些对象的创建过程繁琐,有些对象需要依赖其它对象,这些创建过程可以通通交给spring即可。
一、实现思路
三步走:
- 定义配置文件。
容器不只是创建某类对象,不能在代码中写死,所以将需要被spring创建的对象放到配置文件中,也是隔离变化的体现。 - 解析配置文件。
采用Dom4j技术,解析xml配置文件。
定义BeanDefinition对象,用于接收解析后的对象信息。 - 将解析得到的对象信息,通过反射生成对象,并放到容器中。
二、具体实现
-
代码结构
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 需要被创建的对象
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.串联执行流程,读取配置文件,通过beanConfigParser解析到beanDefinitions
* 3.beansFactory根据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);
}
/**
* 配置文件解析,并将配置信息读取转化到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.根据beanDefinitions创建对象
*/
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);
}
}
//反射,根据有参构造方法创建对象
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;
/**
* 配置文件内容解析成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节点集合
Element root = document.getRootElement();
List<Element> elements = root.selectNodes("//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类型,需要将类型转换,转化为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 接收配置文件解析数据的对象
package org.example;
import lombok.Data;
import java.util.List;
/**
* 配置文件中bean节点的映射对象
*/
@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 基本数据类型的字节码枚举类。
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中,使用反射,对有参构造方法实例化对象,getConstructor方法需要接受参数的字节码数组。
我们需要得到配置文件中的“int”、“String”对应的字节码对象。
不能使用Class.forName("String")的方式获取,所以就采用了枚举的方式
//反射,根据有参构造方法创建对象
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);
}
使用反射,根据有参数构造方法创建对象,示例
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类型,所以需要在使用方式创建对象时,将从配置文件获取的值转化成Integer类型。
说明:xml配置文件的属性值必须带双引号,所以这步转化是必不可少的。
if (childElement.attributeValue("value") != null) {
if ("int".equals(typeClassName)) {//坑点!如果是int类型,需要将类型转换,转化为Integer类型
constructorArg.setValue(Integer.valueOf(childElement.attributeValue("value")));
} else {
constructorArg.setValue(childElement.attributeValue("value"));
}
}
五、收获
- 掌握了dom4j
- 学会了工厂模式设计思想
- 深刻理解了DI框架设计原理
- 加深了对面向对象设计思想的理解
比如,使用BeanDefinition接收配置解析信息,ClassPathXmlApplicationContext组装对象,串联流程,让类的职责、代码流程更加清晰
参考:
https://time.geekbang.org/column/article/198614
https://blog.csdn.net/zhouyingge1104/article/details/83069886
网友评论