一起来学tiny-spring

in 笔记 with 0 comment

背景

最近想深入学习一下Spring的两大核心功能:IOC、AOP。想看源码但是Spring的源码层次复杂,封装繁琐,简单的逻辑写的非常“啰嗦”,阅读起来很费劲。然后发现了多年前的一个精简版的Spring学习项目,叫tiny-spring,作者对spring核心的IOC和AOP进行了临摹实现,也很细心的对实现步骤进行了拆分。我看完了tiny-spring收获许多,自己也参考该项目进行了模仿与实践,从我学习的角度,拆分步骤更加细粒,也想在此基础上后期增加一些新的功能,命名曰:tiny-spring-practice

Spring之IOC容器

step1-最基本的容器

git checkout Spring-IOC-1

就两个类:

测试代码:

@Test
public void test() throws Exception {
    // 1、创建bean工厂
    BeanFactory beanFactory = new BeanFactory();

    // 2、注册bean
    BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
    beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

    // 3、获取bean
    HelloWorldService helloWorldService = (HelloWorldService)beanFactory.getBean("helloWorldService");
    helloWorldService.helloWorld();

}

step2-bean的注册和获取由工厂负责

git checkout Spring-IOC-2

step1中bean是由我们自己创建的,用模板设计模式优化BeanFactory,这里把bean创建交给工厂,为了保证扩展性,我们使用Extract Interface的方法,将BeanFactory替换成接口,而使用AbstractBeanFactory和AutowireCapableBeanFactory作为其实现,这里用到了模板设计模式。

测试代码:

@Test
public void test() throws Exception {

    // 1、初始化bean工厂
    BeanFactory beanFactory = new AutowireCapableBeanFactory();

    // 2、定义bean(等比读取xml文件)
    BeanDefinition beanDefinition = new BeanDefinition();
    beanDefinition.setBeanClassName("com.wirechen.ioc.HelloWorldService");

    // 3、向工厂注册bean
    beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

    // 4、从工厂获取bean
    HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
    helloWorldService.helloWorld();

}

step3-为bean注入属性

git checkout Spring-IOC-3

目前我们的bean还是一个没有任何属性的,这一步将对bean注入属性。Spring本身使用了setter来进行注入,这里为了代码简洁,我们使用Field的形式来注入,创建一个PropertyValue类,一个bean可以有多个属性,那么再创建一个PropertyValues类保存PropertyValue。

测试方法:

@Test
public void test() throws Exception {

    // 1、初始化bean工厂
    BeanFactory beanFactory = new AutowireCapableBeanFactory();

    // 2、定义bean(等比读取xml文件)
    BeanDefinition beanDefinition = new BeanDefinition();
    beanDefinition.setBeanClassName("com.wirechen.ioc.HelloWorldService");
    PropertyValues propertyValues = new PropertyValues();
    propertyValues.addPropertyValue(new PropertyValue("text", "Hello World"));
    beanDefinition.setPropertyValues(propertyValues);

    // 3、注册bean
    beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

    // 4、获取bean
    HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
    helloWorldService.helloWorld();

}

step4-用读取xml的方式来注册bean

git checkout Spring-IOC-4

这一步我们要走的就是替换step3中的第二步,把定义bean放在xml中,然后用读取xml的方式来将bean注册到工厂中。
增加一个io包,把资源获取和加载类放进去。

测试代码:

@Test
public void test() throws Exception {

    // 1、初始化bean工厂
    BeanFactory beanFactory = new AutowireCapableBeanFactory();

    // 2、读取并解析xml文件
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(new UrlResourceLoader());
    beanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

    // 3、注册bean
    beanDefinitionReader.getRegistryMap().forEach((name, beanDefinition) -> {
        beanFactory.registerBeanDefinition(name, beanDefinition);
    });

    // 4、获取bean
    HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
    helloWorldService.helloWorld();
}

step5-为bean注入bean属性

git checkout Spring-IOC-5

之前我们为bean注入的属性都是普通类型的(String),现在为bean注入其他bean(处理bean与bean之间的依赖),定义一个BeanReference,来表示这个属性是对另一个bean的引用。

    String name = propertyEle.getAttribute("name");
    String value = propertyEle.getAttribute("value");
    // 注意看xml的结构
    if (value != null && value.length() > 0) {
        beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
    } else {
        String ref = propertyEle.getAttribute("ref");
        if (ref == null || ref.length() == 0) {
            throw new IllegalArgumentException("Configuration problem: <property> element for property '" + name + "' must specify a ref or value");
        }
        BeanReference beanReference = new BeanReference(ref);
        beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
    }
    Object value = propertyValue.getValue();
    if (value instanceof BeanReference) {
        BeanReference beanReference = (BeanReference) value;
        value = getBean(beanReference.getName());
    }
    field.set(bean, value);

测试代码:

@Test
public void test() throws Exception {

    // 1、初始化bean工厂
    BeanFactory beanFactory = new AutowireCapableBeanFactory();

    // 2、读取并解析xml文件
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(new UrlResourceLoader());
    beanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

    // 3、注册bean
    beanDefinitionReader.getRegistryMap().forEach((name, beanDefinition) -> {
        beanFactory.registerBeanDefinition(name, beanDefinition);
    });

    // 4、获取bean
    OutputService outputService = (OutputService) beanFactory.getBean("outputService");
    HelloWorldService helloWorldService = outputService.getHelloWorldService();
    helloWorldService.helloWorld();

}

step6-使用lazy-init懒加载解决循环依赖问题

git checkout Spring-IOC-6

其实在step5中是有很多问题的,比如注册顺序,如果被依赖的bean后注册就使依赖的bean找到被依赖的bean。还比如两个bean相互依赖注入到各自的属性中的时候就会造成循环依赖一直不会被创建。为了解决这两个问题这一步我们将doCreateBean放在getBean的中。这样在注入bean的时候,如果该属性对应的bean找不到,那么就先创建!因为总是先创建后注入,所以不会存在两个循环依赖的bean创建死锁的问题。

@Override
public Object getBean(String name) {
    BeanDefinition beanDefinition = beanDefinitionMap.get(name);
    if (beanDefinition == null) {
        throw new IllegalArgumentException("No bean named " + name + " is defined");
    }
    Object bean = beanDefinition.getBean();
    if (bean == null) {
        bean = doCreateBean(beanDefinition);
    }
    return bean;
}

step7-使用单例预加载

git checkout Spring-IOC-7

step6中将doCreateBean放在getBean中瞬间解决了注册顺序和循环依赖的问题,解决思路是用懒加载的方式在获取bean的时候才创建bean。我们知道Spring有单例和多例模式,我们在bean工厂中使用beanDefinitionMap就是实现的单例模式,单例模式在Spring中默认是预加载的方式,如果要实现预加载的话解决思路是在介于工厂注册bean和获取bea之间增加一个加载bean的方法。

public void preInstantiateSingletons() {
    beanDefinitionMap.forEach((name, beanDefinition) -> {
        getBean(name);
    });
}

测试代码:

@Test
public void test() throws Exception {

    // 1、初始化bean工厂
    BeanFactory beanFactory = new AutowireCapableBeanFactory();

    // 2、读取并解析xml文件
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(new UrlResourceLoader());
    beanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

    // 3、注册bean
    beanDefinitionReader.getRegistryMap().forEach((name, beanDefinition) -> {
        beanFactory.registerBeanDefinition(name, beanDefinition);
    });

    // 4、初始化bean(使用预加载)
    beanFactory.preInstantiateSingletons();

    // 4、获取bean
    OutputService outputService = (OutputService) beanFactory.getBean("outputService");
    HelloWorldService helloWorldService = outputService.getHelloWorldService();
    helloWorldService.helloWorld();

}

step8-ApplicationContext登场

git checkout Spring-IOC-8

测试代码中我们可以将初始化bean工厂读取解析xml文件注册bean三个预准备工作全都放在我们熟悉的ApplicationContext中去完成。

测试代码:

@Test
public void testApplicationContext() throws Exception{
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
    HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
    helloWorldService.helloWorld();
}

step9-用注解方式来实现运行时注入bean

git checkout Spring-IOC-9

原tiny-spring是没有这功能的,我自己实现了这部分。

当然实现也是在自己定义的AnnotationBeanDefinitionReader中像之前在XmlBeanDefinitionReader中实现processProperty方法。也和ClassPathXmlApplicationContext一样定义一个AnnotationApplicationContext
不得不佩服spring的这种高扩展性面向接口开发的思想真的值得我们好好学习。

这里只定了两个重要的注解@Component@Autowired,在目标对象中加入定义bean的@Component和被注入的对象bean@Autowired即可,如:

@Component
public class HelloWorldController {

    @Autowired
    private HelloWorldService helloWorldService;

    public String doController() {
        String result = helloWorldService.doService();
        System.out.println(result);
        return result;
    }
}
@Component
public class HelloWorldService {

    @Autowired
    private HelloWorldRepository helloWorldRepository;

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String doService() {
        return helloWorldRepository.queryTest();
    }
}
@Component
public class HelloWorldRepository {

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String queryTest() {
        setText("Hello World!");
        return text;
    }
}

测试代码:

@Test
public void testAnnotationApplicationContext() throws Exception{
    ApplicationContext applicationContext = new AnnotationApplicationContext("applicationContext.xml");
    HelloWorldController helloWorldController = (HelloWorldController) applicationContext.getBean("helloWorldController");
    helloWorldController.doController();
}