简单的 DI 容器的实现原理,其核心逻辑主要包括:
- 配置文件解析
- 以及根据配置文件通过“反射”语法来创建对象
其中,创建对象的过程就应用到了我们在学的工厂模式。对象创建、组装、管理完全有 DI 容器来负责,跟具体业务代码解耦,让程序员聚焦在业务代码的开发上。
目录
一、工厂模式和 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;
}
}