聊聊最近撸Spring源码感悟

简介: Spring IOC简单实现

一、前言


   最近一段时间撸了Spring IOC、AOP、Transactional源码,这篇文章聊聊我写了哪些小玩意,这可能就是阅读源码以后最大收获。希望大家在里面能学习一些什么东西吧;


二、Spring IOC简单实现


   第一步首先看一下配置文件,配置文件模拟Spring Bean注入时候的样子,少了XML验证头的一些东西;

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="student" class="org.handwriting.spring.entity.Student">
        <property name="name"  value="wtz" />
        <property name="age"  value="20" />
    </bean>
</beans>

   第二步是实体类;

public class Student {
    private String name;
    private String age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
}

  第三步也是最重要的一步,实现从配置中解析文件,然后通过反射创建Student对象,解析配置中Student的属性,赋值给Student对象完成初始化,最后加载到Map容器中;

 

public class SpringIoc {
    private HashMap<String, Object> beanMap = new HashMap<String, Object>();
    public SpringIoc(String location) throws SAXException, IllegalAccessException, IOException, InstantiationException, ParserConfigurationException, NoSuchFieldException {
        loadBeans(location);
    }
    /**
     * 获取 bean
     *
     * @param name bean 名称
     * @return bean
     */
    public Object getBean(String name) {
        Object bean = beanMap.get(name);
        if (bean == null) {
            throw new IllegalArgumentException(" bean is null"+name);
        }
        return bean;
    }
    /**
     * 加载配置和初始化 bean 属性
     *
     * @param location
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchFieldException
     */
    private void loadBeans(String location) throws IOException, ParserConfigurationException, SAXException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        //加载 xml 配置文件
        InputStream inputStream = new FileInputStream(location);
        DocumentBuilderFactory documentBuilderFactory =
                DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder =
                documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.parse(inputStream);
        Element root = document.getDocumentElement();
        NodeList nodeList = root.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node instanceof Element) {
                Element element = (Element) node;
                String id = element.getAttribute("id");
                String className = element.getAttribute("class");
                //通过反射获取当前类
                Class beanClass = null;
                try {
                    beanClass = Class.forName(className);
                } catch (ClassNotFoundException ex) {
                    ex.printStackTrace();
                }
                Object bean = beanClass.newInstance();
                //将属性值赋值给 bean 对象
                NodeList propertyList = element.getElementsByTagName("property");
                for (int j = 0; j < propertyList.getLength(); j++) {
                    Node propertyNode = propertyList.item(j);
                    Element propertyElement = (Element) propertyNode;
                    String name = propertyElement.getAttribute("name");
                    String value = propertyElement.getAttribute("value");
                    Field declaredField = bean.getClass().getDeclaredField(name);
                    declaredField.setAccessible(true);
                    if (value != null && value.length() > 0) {
                        declaredField.set(bean, value);
                    }
                }
                beanMap.put(id, bean);
            }
        }
    }
}

 第四部测试调用;


public class SpringIocTest {
    public static void main(String[] args) throws IllegalAccessException, ParserConfigurationException, IOException, InstantiationException, SAXException, NoSuchFieldException {
        //通过 ClassLoader 加载文件信息
        String location=
                SpringIoc.class.getClassLoader().getResource("spring.xml").getFile();
        //完成 bean 对象的初始化
        SpringIoc ioc=new SpringIoc(location);
        //获取 student 对象
        Student student=(Student) ioc.getBean("student");
        System.out.println(student);
    }
}

这简简单单的几步,其实已经反应出Bean的创建流程,首先来看一下我们通过BeanFactory怎么来获取一个对象,这里先不看ApplicationContext这个家伙不是一个纯粹的人;

1005447-20191221215617809-1489312168.png

 如上图的标注,第一步主要是初始化配置文件信息,生成Resource,该过程也就是我写的读取文件的流程;

 第二步主要是初始化BeanFactory,DefaultListableBeanFactory该类BeaFactory默认实现,这个也是Spring常用的手段,凡是复杂实现总要抽象一个多功能抽象类和一个默认实现,这样做的好处就在于我们只需要重写抽象类的方法就能实现新的一些扩展功能;

  第三部分完成了Resource转化为Document,Document解析为BeanDefinition,最终加入到Map容器中过程,第二、三部就相当于我实现Spring IOC的loadBeans的方法前半部分,这个时候还不是真正可用的对象;

 第四部分是最重要的部分,该部分完成BeanDefinition到实体Bean的创建,针对这部分我把Bean整个生命周期用到的接口做了具体的实现;

 总结一下,Spring IOC就是将配置加入容器中,然后从容器取出配置信息,利用反射或者CGLIB方式创建对应实例,然后填充属性信息,最终完成初始化;


三、Spring Bean生命周期


 首先来看下继承了哪些接口和实现;

public class LifeCycleBean implements BeanNameAware, BeanClassLoaderAware,
        BeanFactoryAware, InitializingBean, DisposableBean, BeanPostProcessor {
    private String test;
    public String getTest() {
        return test;
    }
    public void setTest(String test) {
        System.out.println("属性注入");
        this.test = test;
    }
    public void method(){
        System.out.println("方法被调用");
    }
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("BeanClassLoaderAware 被调用");
    }
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("BeanFactoryAware 被调用");
    }
    public void setBeanName(String name) {
        System.out.println("BeanNameAware 被调用");
    }
    public void destroy() throws Exception {
        System.out.println("DisposableBean 被调用");
    }
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean 被调用");
    }
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor 开始被调用");
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor 结束被调用");
        return bean;
    }
    public void initMethod(){
        System.out.println("init-method 被调用");
    }
    public void destroyMethod(){
        System.out.println("destroy-method 被调用");
    }
}

 接下来看下运行结果和怎么调用;

 

public class SpringIocTest {
    public static void main(String[] args) {
        //加载配置文件
        ClassPathResource classPathResource = new ClassPathResource("spring.xml");
        //初始化 beanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
        //读取配置文件信息
        XmlBeanDefinitionReader definitionReader =
                new XmlBeanDefinitionReader(defaultListableBeanFactory);
        definitionReader.loadBeanDefinitions(classPathResource);
        // beanFactory 必须手动调用 BeanPostProcessor 注入
        defaultListableBeanFactory.addBeanPostProcessor(new LifeCycleBean());
        //获取 bean
        LifeCycleBean lifeCycleBean=
                (LifeCycleBean)defaultListableBeanFactory.getBean(LifeCycleBean.class);
        //执行方法
        lifeCycleBean.method();
        defaultListableBeanFactory.destroySingletons();
    }
}

 1005447-20191222102855344-5935383.png

这就是整个Spring Bean的生命周期,了解整个生命周期再去看Spring IOC部分的源码相信大家会更好理解,接下来介绍下这些接口的作用;

 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware这个三个接口是在Bean创建完成以后执行,实现Aware类型的接口其实就是将Spring提供的能力按照Bean的形式暴露出去;

 BeanPostProcessor是Bean在实例化前后提供的重要的扩展点,允许在Bean的实例化阶段对Bean进行修改,Spring经典的实现AOP、事务都是借助其扩展方法postProcessAfterInitialization实现;

 InitializingBean、init-method在进行 Aware 接口和 BeanPostProcessor 前置处理之后,会接着检测当前 Bean 对象是否实现了 InitializingBean 接口,如果是,则会调用其 afterPropertiesSet方法,进一步调整 Bean 实例对象的状态,init-method指定的方法在afterPropertiesSet执行,其执行效果一样,但是在实现上init-method指定的方法采用反射实现,消除了继承接口的依赖;

 DisposableBean、destory-method是在容器被注销的时候触发的,两者的不同请参考InitializingBean和init-method;

 接下来我们就可以聊聊mybatis-spring插件机制,这样就能体现生命周期的价值,也可以体会Spring之美;


四、mybatis-spring插件


我们可以思考下如何实现这个功能,我们需要完成那些步骤:

1.解析Mapper文件;

2.将Mapper解析以后的对象转化为Spring的Bean;

3.将转化以后的Bean注入到Spring容器当中;

完成以上3步,我们就可以将Mapper委托Spring去管理,我们来看一下mybatis-spring是如何实现的,整个过程中我们又用到了哪些Spring Bean生命周期,首先先来把这个插件整合起来;

第一步先在Maven中添加依赖;

1005447-20191228110158225-2007989850.png

 第二步在Spring配置中添加Mybatis的依赖;

1005447-20191228105948071-544370678.png

接下来看下原理,从配置流程可以看到mybatis把SqlSessionFactory交给了Spring容器进行管理,看下SqlSessionFactoryBean的继承结构,

1005447-20191228112220558-2117487939.png

圈出了重点两个接口,FactoryBean是Spring Bean注入的一种方式,关键的重点在于InitializingBean,看到这个就想到生命周期,这个动作发生于Bean创建完成以后,我们看一下他做那些事;

1005447-20191228113844489-250234433.png

在Spring实例化Bean的时候会调用FactoryBeangetObject()方法。所以Mybatis与Spring的集成的入口就是org.mybatis.spring.SqlSessionFactoryBean#getObject()方法,是通过XMLConfigBuilderXMLMapperBuilderXMLStatementBuilder三个建造者来完成了对Mybatis XML文件的解析。

现在已经将Spring与Mybatis整合到一起了,接下来可以看下如何将Mapper动态加载到Spring容器中,配置中MapperScannerConfigurer实现这一过程,接下来我们一起看一下该类的继承结构;

1005447-20191228125028652-1044357419.png

重点是BeanDefinitionRegistryPostProcessor,会执行BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法来完成Bean的装载;

1005447-20191228131649418-1727643516.png

 我们可以看到通过包扫描,将会扫描出所有的Mapper类,然后注册Bean定义到Spring容器。Mapper是一个接口,不能直接实例化,在ClassPathMapperScanner中,它将所有Mapper对象的BeanDefinition给改了,将所有Mapper的接口对象指向MapperFactoryBean工厂Bean,所以在Spring中Mybatis所有的Mapper接口对应的类是MapperFactoryBean,源码如下:

 1005447-20191228135524303-1706398898.png

 至此我们完成Mapper注入到Spring容器中,至于Mybatis后面的东西暂不去讨论了。整个过程中出现的一个从未出现过的接口BeanDefinitionRegistryPostProcessor,接下来我们一起来探究下这个类


五、BeanDefinitionRegistryPostProcessor


 这里通过一个例子看下这个接口作用;

public class BeanDefinitionRegistryPostProcessorDemo implements BeanDefinitionRegistryPostProcessor {
    private static final String beanName = "student";
    /**
     * 加载 teacher 类
     *
     * @param registry
     * @throws BeansException
     */
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //设置 bean 相关属性
        GenericBeanDefinition definition = new GenericBeanDefinition();
        definition.setBeanClass(Student.class);
        definition.setScope("singleton");
        // bean 属性赋值
        MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
        mutablePropertyValues.add("name", "wtz");
        mutablePropertyValues.add("age", 20);
        definition.setPropertyValues(mutablePropertyValues);
        //注入 bean
        registry.registerBeanDefinition("student", definition);
    }
    /**
     * 获取 Bean 对象并修改其属性
     * 
     * @param beanFactory
     * @throws BeansException
     */
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition definition = beanFactory.getBeanDefinition("teacher");
        MutablePropertyValues mutablePropertyValues = null;
        mutablePropertyValues = definition.getPropertyValues();
        if (definition != null && mutablePropertyValues != null) {
            PropertyValue propertyValue=
                    mutablePropertyValues.getPropertyValue("name");
            System.out.println("获取到的值"+propertyValue.getValue());
            propertyValue.setConvertedValue("修改属性为go");
        }
    }
}

 接下来看下实体和配置;

/**
 * student
 *
 * @author wtz
 */
public class Student {
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "student name  " + name + " age " + age;
    }
}
/**
 * teacher
 *
 * @author wtz
 */
public class Teacher {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "teacher name " + name;
    }
}
    <bean id="teacher" class="org.handwriting.springdemo.Teacher">
        <property name="name" value="www" />
    </bean>
    <bean id="beanDefinitionRegistryPostProcessorDemo"
            class="org.handwriting.springdemo.BeanDefinitionRegistryPostProcessorDemo" />

我们来看一下运行结果;1005447-20191228160713958-557050310.png


BeanDefinitionRegistryPostProcessor接口的方法postProcessBeanFactory是由BeanFactoryPostProcessor继承得来的,所以我们就对这两个接口进行总结一下;

BeanFactoryPostProcessor该接口可以获取BeanFactory对象,通过操作BeanFactory修改BeanDefinition中的属性,但不支持实例化该Bean;

BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子类除了继承父类功能以外,我们可以获取到BeanDefinitionRegistry,通过该对象向容器中注入Bean;

这两个接口是实现自动扫描的关键,比如ConfigurationClassPostProcessor这里我就不展开讨论了,这里我们还需要知道是什么时候加载的BeanDefinitionRegistryPostProcessor,上面给大家提到一个不纯粹的人;

这个家伙有个refresh方法,该方法中有个invokeBeanFactoryPostProcessors方法,该方法就是自动扫描的关键,也就是这个时候执行的该接口的实现,源码展开说明,送大家一张图结束本文;

1005447-20191228162823587-1889659687.png


六、总结


 文章有些散乱,总体还是讲明白了三件事情:

 1.Spring IOC到底是在做一件什么事情;

 2.Spring Bean的生命周期都经历什么;

 3.插件是怎么加入到Spring容器中的;

 希望大家看过以后,能做到有需求写一个插件注入到Spring容器中的时候能不虚,很快将这个功能实现,另外还小小提了一下自动扫描,有兴趣自己去了解下;Spring加载过程中其实还有很多细节问题,这里都没有提及,希望大家努力去看源码,真的能学习到很多东西!


七、结束


相关文章
|
14天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
28 0
|
17天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
1月前
|
消息中间件 NoSQL Java
Spring Cloud项目实战Spring Cloud视频教程 含源码
Spring Cloud项目实战Spring Cloud视频教程 含源码
32 1
|
2月前
|
设计模式 Java Spring
【Spring源码】WebSocket做推送动作的底层实例是谁
我们都知道WebSocket可以主动推送消息给用户,那做推送动作的底层实例究竟是谁?我们先整体看下整个模块的组织机构。可以看到handleMessage方法定义了每个消息格式采用不同的消息处理方法,而这些方法该类并**没有实现**,而是留给了子类去实现。
23 0
【Spring源码】WebSocket做推送动作的底层实例是谁
|
2月前
|
存储 设计模式 Java
【Spring源码】Bean采用什么数据结构进行存储
我们再来看看中间新加入的阅读线索4,不知大家忘记了没。我们可以对照图片1的代码组织结构,发现这些没存储在包里的功能类都是比较杂乱的,想必是Spring觉得目前这些功能类还构不成一个包的体系,可能后面规模更大会统一集成起来管理。
28 0
【Spring源码】Bean采用什么数据结构进行存储
|
2月前
|
XML 缓存 Java
Spring源码之 Bean 的循环依赖
循环依赖是 Spring 中经典问题之一,那么到底什么是循环依赖?简单说就是对象之间相互引用, 如下图所示: 代码层面上很好理解,在 bean 创建过程中 class A 和 class B 又经历了怎样的过程呢? 可以看出形成了一个闭环,如果想解决这个问题,那么在属性填充时要保证不二次创建 A对象 的步骤,也就是必须保证从容器中能够直接获取到 B。 一、复现循环依赖问题 Spring 中默认允许循环依赖的存在,但在 Spring Boot 2.6.x 版本开始默认禁用了循环依赖 1. 基于xml复现循环依赖 定义实体 Bean java复制代码public class A {
|
2月前
|
Java 测试技术 数据库连接
【Spring源码解读!底层原理高级进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨
【Spring源码解读!底层原理高级进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨
|
8天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
|
12天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
18天前
|
Java Maven Nacos
Spring Cloud Eureka 服务注册和服务发现超详细(附加--源码实现案例--及实现逻辑图)
Spring Cloud Eureka 服务注册和服务发现超详细(附加--源码实现案例--及实现逻辑图)
28 0