手撸一个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:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="helloWorldService" class="top.guoziyang.main.service.HelloWorldServiceImpl" scope="prototype">
        <property name="text" value="Hello World"></property>
    </bean>

    <bean id="wrapService" class="top.guoziyang.main.service.WrapService">
        <property name="helloWorldService" ref="helloWorldService"></property>
    </bean>

</beans>

该配置文件仿照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<String, 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<String, 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<String, 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 = 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<String, BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    public AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<>();
        this.resourceLoader = resourceLoader;
    }

    public Map<String, 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<String, 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,我们仍然需要在配置文件中书写自动注入的扫描范围,配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <component-scan base-package="top.guoziyang.main"></component-scan>
</beans>

启动后,会自动扫描该包及其子包下所有使用注解标明的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的简单实现。

挺晚了,睡觉!

猜你喜欢

转载自blog.csdn.net/qq_40856284/article/details/106522532