java|手撸一个Spring IOC容器——渐进式实现

前言 本文原载于我的博客,地址:https://blog.guoziyang.top/archives/53/
最近阅读了Spring Framework中的IOC容器部分的实现,手痒,决定自己实现一个比较简单的版本。
和我一起手写一个,面试的时候丝毫不虚!
具体代码可以查看我的Github的仓库:https://github.com/CN-GuoZiyang/My-Spring-IOC
目前实现的功能有:

  • xml配置文件读取
  • 属性注入
  • 引用依赖注入
  • 递归引用注入
  • singleton与prototype模式注入
  • 注解配置
  • 基于该容器的SpringMVC的实现(下一篇)
待实现:
  • AOP实现
  • 循环依赖
基于xml配置文件的注入 该部分对应的提交在:
https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/82967670e52fe66ad55a6b2a539dbb4d48b46805
最终效果
主要过程按自顶向下的方式实现,最终实现的是将以下的配置文件读取后,在容器中注入Bean:

该配置文件仿照Spring的配置文件格式,注入以下的两个Bean:
package top.guoziyang.main.service; public class HelloWorldServiceImpl implements HelloWorldService { private String text; @Override public void saySomething() { System.out.println(text); } }

package top.guoziyang.main.service; public class WrapService { private HelloWorldService helloWorldService; public void say() { helloWorldService.saySomething(); } }

ApplicationContext的实现
ApplicationContext,即应用程序上下文,是Spring框架中最为核心的类,也是Spring的入口类。该接口继承自BeanFactory接口,实现了BeanFactory(实例工厂)的所有功能,还支持资源访问(如URL和文件)、事务传播等功能。但是我们还是只实现其核心的功能。
我们首先定义ApplicationContext接口:
package top.guoziyang.springframework.context; /** * 应用程序上下文接口 * * @author ziyang */ public interface ApplicationContext { Object getBean(Class clazz) throws Exception; Object getBean(String beanName) throws Exception; }

这个接口只定义了两个方法,分别通过类对象和实例的名称从容器中获取对象。
我们接着仿照Spring,编写一个抽象类AbstractApplicationContext,来实现ApplicationContext接口,书写一些通用的方法。注意,在Spring中,ApplicationContext实现BeanFactory的方式,是在ApplicationContext对象的内部,保存了一个BeanFactory对象的实例,实质上类似一种代理模式:
package top.guoziyang.springframework.context; import top.guoziyang.springframework.factory.BeanFactory; public abstract class AbstractApplicationContext implements ApplicationContext {BeanFactory beanFactory; @Override public Object getBean(Class clazz) throws Exception { return beanFactory.getBean(clazz); } @Override public Object getBean(String beanName) throws Exception { return beanFactory.getBean(beanName); } }

那么现在,从ApplicationContext中取出对象的方法都实现完了,那么ApplicationContext的具体实现类的工作,就是用某种方式读取配置,然后把对象信息存入到BeanFactory中,等待用户来取。
那么在我们查看ApplicationContext的具体实现类之前,我们先来看看BeanFactory,这个实例工厂。
从AbstractApplicationContext中,我们可以知道,这个接口,有getBean这两种方法,除此以外,我还定义了一个方法:void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception; ,表示像工厂中注册Bean的定义,至于BeanDefinition的实现,后面再说。
BeanFactory的实现
BeanFactory,毫无疑问就是一个工厂,而且ApplicationContext就是从它这儿拿Bean的。根据名字来拿Bean,显而易见是一个类似Map的结构,这里我们采用ConcurrentHashMap来存储这个结构。那么这样,两个getBean的实现也就很显然了,仿照Spring的结构,我们还是先创建一个抽象类来实现BeanFactory接口:
package top.guoziyang.springframework.factory; import top.guoziyang.springframework.entity.BeanDefinition; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public abstract class AbstractBeanFactory implements BeanFactory {ConcurrentHashMap, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); @Override public Object getBean(String name) throws Exception { BeanDefinition beanDefinition = beanDefinitionMap.get(name); if(beanDefinition == null) return null; if(!beanDefinition.isSingleton() || beanDefinition.getBean() == null) { return doCreateBean(beanDefinition); } else { return doCreateBean(beanDefinition); } }@Override public Object getBean(Class clazz) throws Exception { BeanDefinition beanDefinition = null; for(Map.Entry, BeanDefinition> entry : beanDefinitionMap.entrySet()) { Class tmpClass = entry.getValue().getBeanClass(); if(tmpClass == clazz || clazz.isAssignableFrom(tmpClass)) { beanDefinition = entry.getValue(); } } if(beanDefinition == null) { return null; } if(!beanDefinition.isSingleton() || beanDefinition.getBean() == null) { return doCreateBean(beanDefinition); } else { return beanDefinition.getBean(); } }@Override public void registerBeanDefinition(String name, BeanDefinition beanDefinition) { beanDefinitionMap.put(name, beanDefinition); }/** * 创建Bean实例 * @param beanDefinition Bean定义对象 * @return Bean实例对象 * @throws Exception 可能出现的异常 */ abstract Object doCreateBean(BeanDefinition beanDefinition) throws Exception; public void populateBeans() throws Exception { for(Map.Entry, BeanDefinition> entry : beanDefinitionMap.entrySet()) { doCreateBean(entry.getValue()); } } }

这里,我们留了一个doCreateBean方法作为抽象方法,表示真正创建Bean实例对象的操作,留给具体的实现类来实现。
我们要实现的BeanFactory,是一个可以自动注入属性的BeanFactory,可以创建完成实例对象后,注入其中的属性,如果属性是一个对象引用,那么就去创建那个被引用的实例对象,并递归地完成属性注入。在Spring中,这个实现类叫做AutowiredCapableBeanFactory。于是,我们的AutowiredCapableBeanFactory的实现是这样的:
package top.guoziyang.springframework.factory; import top.guoziyang.springframework.entity.BeanDefinition; import top.guoziyang.springframework.entity.BeanReference; import top.guoziyang.springframework.entity.PropertyValue; import java.lang.reflect.Field; public class AutowiredCapableBeanFactory extends AbstractBeanFactory {@Override Object doCreateBean(BeanDefinition beanDefinition) throws Exception { if(beanDefinition.isSingleton() && beanDefinition.getBean() != null) { return beanDefinition.getBean(); } Object bean = beanDefinition.getBeanClass().newInstance(); if(beanDefinition.isSingleton()) { beanDefinition.setBean(bean); } applyPropertyValues(bean, beanDefinition); return bean; }/** * 为新创建了bean注入属性 * @param bean 待注入属性的bean * @param beanDefinition bean的定义 * @throws Exception 反射异常 */ void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception { for(PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValues()) { Field field = bean.getClass().getDeclaredField(propertyValue.getName()); Object value = https://www.it610.com/article/propertyValue.getValue(); if(value instanceof BeanReference) { BeanReference beanReference = (BeanReference) propertyValue.getValue(); BeanDefinition refDefinition = beanDefinitionMap.get(beanReference.getName()); if(refDefinition.getBean() == null) { value = doCreateBean(refDefinition); } } field.setAccessible(true); field.set(bean, value); } } }

这里主要还是使用了反射来创建对象实例,原理比较简单,就不过多说明。
那么说了这么多,BeanDefinition到底是什么呢,又从哪里来呢?
BeanDefinition的定义如下:
public class BeanDefinition {private Object bean; // 实例化后的对象 private Class beanClass; private String beanClassName; private Boolean singleton; // 是否是单例模式 private PropertyValues propertyValues; // Bean的属性}

PropertyValues实际上是一个List,表示一组属性的定义,内部存储的对象是PropertyValue对象,表示一个属性定义和其对应的注入属性:
public class PropertyValue {private final String name; private final Object value; }

注意这里的value,如果是引用其他对象的话,value就是一个BeanReference实例,表示对一个对象的引用,而不是立即初始化,因为BeanDefinition是在读取配置文件时就被创建的,这时还没有任何Bean被初始化,BeanReference仅仅是一个记录而已:
public class BeanReference { private String name; private Object bean; }

BeanDefinitionReader的实现
回到正题,BeanDefinition从哪里来?目前是从文件中读取的,定义一个抽象的AbstractBeanDefinitionReader,如下:
/** * BeanDefinitionReader实现的抽象类 * * @author ziyang */ public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {private Map, BeanDefinition> registry; private ResourceLoader resourceLoader; public AbstractBeanDefinitionReader(ResourceLoader resourceLoader) { this.registry = new HashMap<>(); this.resourceLoader = resourceLoader; }public Map, BeanDefinition> getRegistry() { return registry; }public ResourceLoader getResourceLoader() { return resourceLoader; } }

registry也是一个Map,用于暂存Bean的名称和BeanDefinition的映射。
最终,最后的具体实现类实现了对配置文件的读取,由于我们读取的是Xml配置文件,所以我们的实现类名叫XmlBeanDefinitionReader,使用Java内置的XML解析器,可以将其解析为Document,具体的解析过程较长,不贴代码了,文件参考这里。
回到ApplicationContext
这就是完整的,一个Bean从配置文件到被实例化的过程。那么,第一节的ApplicationContext的具体实现类所要做的,就很简单了,只需要创建一个BeanDefinitionReader读取配置文件,并且将读取到的配置存到BeanFactory中,并且由BeanFactory创建对应的实例对象即可。由于我们是读取xml文件,那么这个ApplicationContext的实现类,就叫ClassPathXmlApplicationContext,具体的逻辑在obtainBeanFactory()方法中:
private AbstractBeanFactory obtainBeanFactory() throws Exception { XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader()); beanDefinitionReader.loadBeanDefinitions(location); AbstractBeanFactory beanFactory = new AutowiredCapableBeanFactory(); for (Map.Entry, BeanDefinition> beanDefinitionEntry : beanDefinitionReader.getRegistry().entrySet()) { beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue()); } return beanFactory; }

看看效果!
让我们书写一些测试代码,看看效果:
public class Main {public static void main(String[] args) throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); WrapService wrapService = (WrapService) applicationContext.getBean("wrapService"); wrapService.say(); HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService"); HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService"); System.out.println("prototype验证:" + (helloWorldService == helloWorldService2)); WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService"); System.out.println("singleton验证:" + (wrapService == wrapService2)); }}

运行结果如下:
Hello World prototype验证:false singleton验证:true

这里验证了一下prototype和singleton,这里首先获取了两次HelloWorldService的实例,由于这个Bean在配置文件中被标为prototype,所以两次获取到的都不是同一个对象,使用等号比较时得到了false。而后面获取的wrapService,和第一次获取的WrapService比较,由于是singleton的,所以使用等号比较时返回true。
基于注解的注入 该部分对应的提交在
https://github.com/CN-GuoZiyang/My-Spring-IOC/tree/8a3a9c640e532c5d4aa8d62f18b42fa336c94f2e
声明注解
首先我们需要自定义一些注解,仿照Spring,我们声明一下五个注解:Autowired、Component、Qualifier、Scope和Value,用过Spring的人应该都知道以下注解的作用。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Autowired{}

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Component { String name() default ""; }

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Qualifier { String value(); }

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Scope { String value() default "singleton"; }

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Value { public String value(); }

由于不是SpringBoot,我们仍然需要在配置文件中书写自动注入的扫描范围,配置文件如下:

启动后,会自动扫描该包及其子包下所有使用注解标明的Bean,并注入容器。
扫描注解
由于配置文件发生了改变,自然我们需要改变xml文件的解析方式,在XmlBeanDefinitionReader的parseBeanDefinitions()方法中,一旦我们发现了component-scan标签,说明我们是使用注解来注入Bean的:
protected void parseBeanDefinitions(Element root) { ... for(int i = 0; i < nodeList.getLength(); i ++) { if(nodeList.item(i) instanceof Element) { Element ele = (Element)nodeList.item(i); if(ele.getTagName().equals("component-scan")) { basePackage = ele.getAttribute("base-package"); break; } } } if(basePackage != null) { parseAnnotation(basePackage); return; } ... }

我们增加了parseAnnotation方法,来对目标包进行注解扫描,实质上需要递归地扫描到该包下的所有类,并使用反射来查看该类是否使用了@Component注解,并获取相关的信息,如属性注入或者singleton或者prototype之类的信息。并将beanDefinition存入registry中:
protected void processAnnotationBeanDefinition(Class clazz) { if(clazz.isAnnotationPresent(Component.class)) { String name = clazz.getAnnotation(Component.class).name(); if(name == null || name.length() == 0) { name = clazz.getName(); } String className = clazz.getName(); boolean singleton = true; if(clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) { singleton = false; } BeanDefinition beanDefinition = new BeanDefinition(); processAnnotationProperty(clazz, beanDefinition); beanDefinition.setBeanClassName(className); beanDefinition.setSingleton(singleton); getRegistry().put(name, beanDefinition); } }

具体的实现可以看本文件
实际上,由于产生的结果一致(产生beanDefinition存入registry),可以仿照Spring的实现使用委托模式,这样耦合度就不会太高。但是由于使用注解同样还需要读取配置文件,较为繁琐,就没有解耦(实际上是我偷懒了)。
看看效果!
这时,我们就可以去测试一下。测试所用的两个类加上相应的注解即可:
@Component(name = "helloWorldService") @Scope("prototype") public class HelloWorldServiceImpl implements HelloWorldService { @Value("Hello, world") private String text; @Override public void saySomething() { System.out.println(text); } }

@Component(name = "wrapService") public class WrapService { @Autowired private HelloWorldService helloWorldService; public void say() { helloWorldService.saySomething(); } }

测试代码如下:
public class Main() { public static void annotationTest() throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-annotation.xml"); WrapService wrapService = (WrapService) applicationContext.getBean("wrapService"); wrapService.say(); HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService"); HelloWorldService helloWorldService2 = (HelloWorldService) applicationContext.getBean("helloWorldService"); System.out.println("prototype验证:相等" + (helloWorldService == helloWorldService2)); WrapService wrapService2 = (WrapService) applicationContext.getBean("wrapService"); System.out.println("singleton验证:相等" + (wrapService == wrapService2)); } }

结果和第一次测试一致。
最后 到这里,自己手撸的Spring的控制反转容器的简单实现就完成了!还是挺有成就感的。使用体验和Spring基本没啥差别(误)。
下一篇文章,会基于已经实现的IOC容器,在其上层手撸一个SpringMVC的简单实现。
【java|手撸一个Spring IOC容器——渐进式实现】挺晚了,睡觉!

    推荐阅读