依赖注入框架,或者叫依赖注入容器(Dependency Injection Container),简称 DI 容器。
DI 容器底层最基本的设计思路就是基于工厂模式的。DI 容器相当于一个大的工厂类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪些其他类对象)事先创建好对象。当应用程序需要使用某个类对象的时候,直接从容器中获取即可。正是因为它持有一堆对象,所以这个框架才被称为“容器”。
除此之外,DI 容器负责的事情要比单纯的工厂模式要多。比如,它还包括配置的解析、对象生命周期的管理。
DI 容器的核心功能
配置解析
我们将需要由 DI 容器来创建的类对象和创建类对象的必要信息(使用哪个构造函数以及对应的构造函数参数都是什么等等),放到配置文件中。容器读取配置文件,根据配置文件提供的信息来创建对象。
容器读取这个配置文件,解析出要创建的两个对象:rateLimiter
和 redisCounter
,并且得到两者的依赖关系:rateLimiter
依赖 redisCounter
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class RateLimiter { private final RedisCounter redisCounter; public RateLimiter(RedisCounter redisCounter) { this.redisCounter = redisCounter; } public RedisCounter getRedisCounter() { return redisCounter; } public void test() { System.out.println("Hello"); System.out.println(redisCounter); } }
public class RedisCounter { private String ipAddress; private String port; public RedisCounter(String ipAddress, String port) { this.ipAddress = ipAddress; this.port = port; } }
|
配置文件beans.xml
:
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="rateLimiter" class="com.mono.monochrome.bean.RateLimiter"> <constructor-arg ref="redisCounter"/> </bean> <bean id="redisCounter" class="com.mono.monochrome.bean.RedisCounter"> <constructor-arg type="java.lang.String">127.0.0.1</constructor-arg> <constructor-arg type="java.lang.String">1234</constructor-arg> </bean> </beans>
|
对象创建
在 DI 容器中,如果我们给每个类都对应创建一个工厂类,那项目中类的个数会成倍增加,这会增加代码的维护成本。要解决这个问题并不难。我们只需要将所有类对象的创建都放到一个工厂类中完成就可以了,比如 BeansFactory
。
如果要创建的类对象非常多,BeansFactory
利用Java
的“反射”机制,它能在程序运行的过程中,动态地加载类、创建对象,不需要事先在代码中写死要创建哪些对象。所以,不管是创建一个对象还是十个对象,BeansFactory 工厂类代码都是一样的。
对象的生命周期管理
简单工厂模式有两种实现方式,一种是每次都返回新创建的对象,另一种是每次都返回同一个事先创建好的对象,也就是所谓的单例对象。在 Spring 框架中,我们可以通过配置 scope 属性,来区分这两种不同类型的对象。scope=prototype 表示返回新创建的对象,scope=singleton 表示返回单例对象。
除此之外,我们还可以配置对象是否支持懒加载。如果 lazy-init=true,对象在真正被使用到的时候(比如:BeansFactory.getBean(“userService”))才被被创建;如果 lazy-init=false,对象在应用启动的时候就事先创建好。
实现一个简单的 DI 容器
最小原型设计
1 2 3 4 5 6 7 8 9 10
| public class Application { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); RedisCounter redisCounter = (RedisCounter) applicationContext.getBean("redisCounter"); RateLimiter rateLimiter = (RateLimiter) applicationContext.getBean("rateLimiter"); System.out.println(redisCounter); rateLimiter.test(); System.out.println(redisCounter == rateLimiter.getRedisCounter()); } }
|
提供执行入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public interface ApplicationContext { Object getBean(String beanId); }
public class ClassPathXmlApplicationContext implements ApplicationContext {
private final BeansFactory beansFactory; private final BeanConfigParser beanConfigParser;
public ClassPathXmlApplicationContext(String configLocation) { this.beansFactory = new BeansFactory(); this.beanConfigParser = new XmlBeanConfigParser(); this.loadBeanDefinitions(configLocation); }
private void loadBeanDefinitions(String configLocation) { InputStream inputStream = null; try { inputStream = this.getClass().getResourceAsStream("/" + configLocation); if (inputStream == null) { throw new RuntimeException("Can not find config file: " + configLocation); } List<BeanDefinition> beanDefinitions = beanConfigParser.parse(inputStream); beansFactory.addBeanDefinitions(beanDefinitions); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
@Override public Object getBean(String beanId) { return beansFactory.getBean(beanId); } }
|
ClassPathXmlApplicationContext
负责组装 BeansFactory
和 BeanConfigParser
两个类,串联执行流程:从 classpath
中加载 xml
格式的配置文件,通过 BeanConfigParser
解析为统一的 BeanDefinition
格式,然后,BeansFactory
根据 BeanDefinition
来创建对象。
配置文件解析
配置文件解析主要包含 BeanConfigParser 接口和 XmlBeanConfigParser 实现类,负责将配置文件解析为 BeanDefinition 结构,以便 BeansFactory 根据这个结构来创建对象。
pom文件中引入解析xml文件的依赖:
1 2 3 4 5
| <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.3</version> </dependency>
|
BeanDefinition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class BeanDefinition {
private String id; private String className; private List<ConstructorArg> constructorArgs = new ArrayList<>(); private Scope scope = Scope.SINGLETON; private boolean lazyInit = false;
public static enum Scope { SINGLETON, PROTOTYPE }
public static class ConstructorArg { private boolean isRef = false; private Class type; private Object arg; } }
|
BeanConfigParser:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public interface BeanConfigParser { List<BeanDefinition> parse(InputStream inputStream); }
public class XmlBeanConfigParser implements BeanConfigParser {
private SAXReader reader = null;
public XmlBeanConfigParser() { this.reader = new SAXReader(); }
@Override public List<BeanDefinition> parse(InputStream inputStream) { List<BeanDefinition> beanDefinitions = new ArrayList<>(); try { Document document = reader.read(inputStream); Element beans = document.getRootElement(); Iterator beanIt = beans.elementIterator(); while(beanIt.hasNext()){ BeanDefinition beanDefinition = new BeanDefinition(); Element bean = (Element) beanIt.next();
List<Attribute> attributes = bean.attributes(); for (Attribute attribute : attributes) { if ("id".equals(attribute.getName())) { beanDefinition.setId(attribute.getValue()); } else if ("class".equals(attribute.getName())) { beanDefinition.setClassName(attribute.getValue()); } } Iterator argsIt = bean.elementIterator(); List<BeanDefinition.ConstructorArg> constructorArgs = new ArrayList<>(); while(argsIt.hasNext()){ BeanDefinition.ConstructorArg constructorArg = new BeanDefinition.ConstructorArg(); Element arg = (Element) argsIt.next(); List<Attribute> argAttributes = arg.attributes(); for (Attribute attribute : argAttributes) { if ("type".equals(attribute.getName())) { try { constructorArg.setType(Class.forName(attribute.getValue())); } catch (ClassNotFoundException e) { e.printStackTrace(); } constructorArg.setArg(arg.getText()); } else if ("ref".equals(attribute.getName())) { constructorArg.setRef(true); constructorArg.setArg(attribute.getValue()); } } constructorArgs.add(constructorArg); beanDefinition.setConstructorArgs(constructorArgs); } beanDefinitions.add(beanDefinition); } } catch (DocumentException e) { throw new RuntimeException(e); } return beanDefinitions; } }
|
工厂类设计
如果对象的 scope
属性是 singleton
,那对象创建之后会缓存在 singletonObjects
这样一个 map
中,下次再请求此对象的时候,直接从 map
中取出返回,不需要重新创建。如果对象的 scope
属性是 prototype
,那每次请求对象,BeansFactory
都会创建一个新的对象返回。实际上,BeansFactory
创建对象用到的主要技术点就是 Java
中的反射语法:一种动态加载类和创建对象的机制。我们知道,JVM
在启动的时候会根据代码自动地加载类、创建对象。至于都要加载哪些类、创建哪些对象,这些都是在代码中写死的,或者说提前写好的。但是,如果某个对象的创建并不是写死在代码中,而是放到配置文件中,我们需要在程序运行期间,动态地根据配置文件来加载类、创建对象,那这部分工作就没法让 JVM
帮我们自动完成了,我们需要利用 Java
提供的反射语法自己去编写代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class BeansFactory {
private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();
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()) { Object bean = createBean(beanDefinition); singletonObjects.putIfAbsent(beanDefinition.getId(), bean); } } }
public Object getBean(String beanId) { BeanDefinition beanDefinition = beanDefinitions.get(beanId); if (beanDefinition == null) { throw new RuntimeException("Bean is not defined:" + beanId); } return createBean(beanDefinition); }
protected Object createBean(BeanDefinition beanDefinition) { if (beanDefinition.isSingleton() && singletonObjects.containsKey(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.isRef()) { 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 | InstantiationException | IllegalAccessException | NoSuchBeanDefinitionException | NoSuchMethodException | InvocationTargetException e) { throw new RuntimeException(e); } if (beanDefinition.isSingleton()) { singletonObjects.putIfAbsent(beanDefinition.getId(), bean); } return bean; } }
|
BeansFactory
类中的 createBean()
函数是一个递归函数。当构造函数的参数是 ref
类型时,会递归地创建 ref
属性指向的对象。如果我们在配置文件中错误地配置了对象之间的依赖关系,导致存在循环依赖,后续再解决循环依赖吧。