聊聊最近擼Spring源碼感悟


一、前言

    最近一段時間擼了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>
View Code

    第二步是實體類;

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;
    }
}
View Code

   第三步也是最重要的一步,實現從配置中解析文件,然后通過反射創建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);
            }

        }


    }

}
View Code

   第四部測試調用;

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);
    }
}
View Code

  這簡簡單單的幾步,其實已經反應出Bean的創建流程,首先來看一下我們通過BeanFactory怎么來獲取一個對象,這里先不看ApplicationContext這個家伙不是一個純粹的人;

  

  如上圖的標注,第一步主要是初始化配置文件信息,生成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 被調用");
    }
}
View Code

  接下來看下運行結果和怎么調用;

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();
    }
}
View Code

  

  這就是整個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中添加依賴;

 

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

 

 接下來看下原理,從配置流程可以看到mybatis把SqlSessionFactory交給了Spring容器進行管理,看下SqlSessionFactoryBean的繼承結構,

 

 圈出了重點兩個接口,FactoryBean是Spring Bean注入的一種方式,關鍵的重點在於InitializingBean,看到這個就想到生命周期,這個動作發生於Bean創建完成以后,我們看一下他做那些事;

 

 在Spring實例化Bean的時候會調用FactoryBeangetObject()方法。所以Mybatis與Spring的集成的入口就是org.mybatis.spring.SqlSessionFactoryBean#getObject()方法,是通過XMLConfigBuilderXMLMapperBuilderXMLStatementBuilder三個建造者來完成了對Mybatis XML文件的解析。

 現在已經將Spring與Mybatis整合到一起了,接下來可以看下如何將Mapper動態加載到Spring容器中,配置中MapperScannerConfigurer實現這一過程,接下來我們一起看一下該類的繼承結構;

 重點是BeanDefinitionRegistryPostProcessor,會執行BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法來完成Bean的裝載;

 

  我們可以看到通過包掃描,將會掃描出所有的Mapper類,然后注冊Bean定義到Spring容器。Mapper是一個接口,不能直接實例化,在ClassPathMapperScanner中,它將所有Mapper對象的BeanDefinition給改了,將所有Mapper的接口對象指向MapperFactoryBean工廠Bean,所以在Spring中Mybatis所有的Mapper接口對應的類是MapperFactoryBean,源碼如下:

  

  至此我們完成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");
        }
    }
}
View Code

  接下來看下實體和配置;

/**
 * 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" />
View Code  

 我們來看一下運行結果;

 BeanDefinitionRegistryPostProcessor接口的方法postProcessBeanFactory是由BeanFactoryPostProcessor繼承得來的,所以我們就對這兩個接口進行總結一下;

 BeanFactoryPostProcessor該接口可以獲取BeanFactory對象,通過操作BeanFactory修改BeanDefinition中的屬性,但不支持實例化該Bean;

 BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子類除了繼承父類功能以外,我們可以獲取到BeanDefinitionRegistry,通過該對象向容器中注入Bean;

 這兩個接口是實現自動掃描的關鍵,比如ConfigurationClassPostProcessor這里我就不展開討論了,這里我們還需要知道是什么時候加載的BeanDefinitionRegistryPostProcessor,上面給大家提到一個不純粹的人;

 這個家伙有個refresh方法,該方法中有個invokeBeanFactoryPostProcessors方法,該方法就是自動掃描的關鍵,也就是這個時候執行的該接口的實現,源碼展開說明,送大家一張圖結束本文;

六、總結

  文章有些散亂,總體還是講明白了三件事情:

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

  2.Spring Bean的生命周期都經歷什么;

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

  希望大家看過以后,能做到有需求寫一個插件注入到Spring容器中的時候能不虛,很快將這個功能實現,另外還小小提了一下自動掃描,有興趣自己去了解下;Spring加載過程中其實還有很多細節問題,這里都沒有提及,希望大家努力去看源碼,真的能學習到很多東西!

七、結束

  歡迎大家加群438836709!歡迎大家關注我!

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM