聊聊最近撸Spring源码感悟

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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加载过程中其实还有很多细节问题,这里都没有提及,希望大家努力去看源码,真的能学习到很多东西!


七、结束


相关文章
|
7天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
164 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
2月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
160 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
2月前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
23天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
23天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
117 9
|
29天前
|
设计模式 JavaScript Java
Spring 事件监听机制源码
Spring 提供了事件发布订阅机制,广泛应用于项目中。本文介绍了如何通过自定义事件类、订阅类和发布类实现这一机制,并展示了如何监听 SpringBoot 启动过程中的多个事件(如 `ApplicationStartingEvent`、`ApplicationEnvironmentPreparedEvent` 等)。通过掌握这些事件,可以更好地理解 SpringBoot 的启动流程。示例代码展示了从事件发布到接收的完整过程。
|
29天前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
20 1
|
1月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
22 0
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码