框架源碼系列四:手寫Spring-配置(爲何要提供配置的方法、選擇什麼樣的配置方式、配置方式的工做過程是怎樣的、分步驟一個一個的去分析和設計)

1、爲何要提供配置的方法

通過前面的手寫Spring IOC、手寫Spring DI、手寫Spring AOP,咱們知道要建立一個bean對象,須要用戶先定義好bean,而後註冊到bean工廠才能建立一個bean對象。代碼以下:html

        static PreBuildBeanFactory bf = new PreBuildBeanFactory();
GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setBeanClass(ABean.class); List<Object> args = new ArrayList<>(); args.add("abean01"); args.add(new BeanReference("cbean")); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("abean", bd); bd = new GenericBeanDefinition(); bd.setBeanClass(CBean.class); args = new ArrayList<>(); args.add("cbean01"); bd.setConstructorArgumentValues(args); bf.registerBeanDefinition("cbean", bd);

那麼若是咱們上面的過程換成配置的方式會是什麼樣的呢?java

    <bean id="abean" class="com.study.spring.samples.ABean">
        <constructor-arg type="String" value="abean01"></constructor-arg>
        <constructor-arg ref="cbean"></constructor-arg>
    </bean>
    <bean id="cbean" class="com.study.spring.samples.CBean">
        <constructor-arg type="String" value="cbean01"></constructor-arg>
    </bean>

通過上面的建立bean對象的過程由ava代碼轉爲xml配置的方式,能夠看出使用配置有以下優點:git

1. 由於咱們是寫框架的,提供配置的方式,別人使用更簡單,改動更加靈活github

2. 要新增修改東西時不須要改代碼正則表達式

2、選擇什麼樣的配置方式

用過Spring的朋友都知道,配置方式有兩種:spring

1. xml編程

2. 註解設計模式

3、配置方式的工做過程是怎樣的

 

4、分步驟一個一個的去分析和設計

1. 定義xml標準和註解標準

首先咱們須要理清楚定義xml標準、定義註解標準的目的是什麼,定義它們的目的是讓用戶可使用它們去配置bean定義和標註bean定義。那麼負載均衡

問題1:bean定義須要指定些什麼信息呢?框架

  須要指定的信息咱們能夠從以前寫的BeanDefinition裏面看到

 

能夠看到bean定義須要上面的這些信息

若是使用xml配置的方式,咱們須要爲上面的這些信息定義一個DTD(Document Type Definition)文件或者XSD(XML Schemas Definition)文件,具體實現利用了Spring提供的可擴展Schema機制實現,實現方式查看我前面的文章:

dubbo系列三:dubbo源碼分析(dubbo框架是如何跟spring進行整合的,消費者獲取的代理實例是如何建立的呢、生產者怎麼把內容註冊到註冊中心的,而後消費者是如何獲取這個信息的、dubbo負載均衡策略)

而後用戶根據提供的DTD文件定義bean定義須要的信息便可:

    <bean id="abean" class="com.study.spring.samples.ABean" init-method="init" 
        destroy-method="destroy" scope="prototype" >
        <constructor-arg type="String" value="abean01"></constructor-arg>
        <constructor-arg ref="cbean"></constructor-arg>
        <property name="name" value="leSmall"></property>
        <property name="age" value="18"></property>
    </bean>

問題2:若是使用註解的方式,須要定義一些什麼註解?

 從須要的bean定義信息裏面咱們可能須要作以下的步驟:

1) 指定類

2)指定beanName

3)指定scope

4)指定工廠bean

5)指定工廠方法

6)指定init method

7)指定銷燬方法

8)指定構造參數依賴

9)指定屬性依賴

 前面的1)到 7)咱們能夠定義一個註解@Component,裏面持有1)到 7)須要的bean定義的信息,在建立bean定義的時候經過反射獲取這些信息

package com.dn.spring.context.config.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.study.spring.beans.BeanDefinition;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {

    String value() default "";

    String name() default "";

    String scope() default BeanDefinition.SCOPE_SINGLETION;

    String factoryMethodName() default "";

    String factoryBeanName() default "";

    String initMethodName() default "";

    String destroyMethodName() default "";
}

注意:註解 Component裏面沒有定義 1)指定類 須要的元素,由於經過在類上加上@Component就能獲取到類的名稱了

前面的8)到 9)咱們能夠定義三個註解@Autowired、@Qualifier、@Value,其中@Autowired用來指定屬性依賴的bean依賴或者有多個構造函數時指定使用哪一個構造函數;@Qualifier用來指定bean依賴的具體bean,好比一個類有多個bean,能夠指定具體的一個bean,如@Qualifier("cbean01") CBean cb;@Value用來指定屬性依賴的非bean依賴。

@Autowired代碼:

package com.study.spring.context.config.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
        ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    /**
     * Declares whether the annotated dependency is required.
     * <p>
     * Defaults to {@code true}.
     */
    boolean required() default true;
}

@Qualifier代碼:

package com.study.spring.context.config.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {

    String value() default "";

}

@Value代碼:

package com.study.spring.context.config.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
    String value();
}

4個註解的使用示例代碼:

package com.study.spring.samples;

import com.study.spring.context.config.annotation.Autowired;
import com.study.spring.context.config.annotation.Component;
import com.study.spring.context.config.annotation.Qualifier;
import com.study.spring.context.config.annotation.Value;

@Component(initMethodName = "init", destroyMethodName = "destroy")
public class ABean {

    private String name;

    private CBean cb;

    @Autowired
    private DBean dbean;

    @Autowired
    public ABean(@Value("mike") String name, @Qualifier("cbean01") CBean cb) {
        super();
        this.name = name;
        this.cb = cb;
        System.out.println("調用了含有CBean參數的構造方法");
    }

    public ABean(String name, CCBean cb) {
        super();
        this.name = name;
        this.cb = cb;
        System.out.println("調用了含有CCBean參數的構造方法");
    }

    public ABean(CBean cb) {
        super();
        this.cb = cb;
    }
}

2.  用戶怎麼指定配置的xml文件的位置?用戶怎麼指定要掃描的包?

一樣的咱們須要分析用戶指定xml配置文件的位置和指定掃描的包的目的是什麼,目的是讓提供方去加載xml文件和掃描包下的類,那麼

問題1:怎麼指定?

  咱們須要爲用戶提供一種方式

問題2:下面這些事情在哪裏作好?

這裏所作的事情是解析xml配置和反射獲取bean定義註解、建立bean定義、向bean工廠註冊bean定義,他不是bean工廠的事,咱們應該單獨定義接口和類完成這些事

 

不管是XmlApplicationContext仍是AnnotationApplicationContext都要使用BeanFactory和BeanDefinitionRegistry,因此能夠進一步優化類圖

 

如今用戶要使用咱們的框架須要知道下面的接口和類:

那麼讓用戶只須要知道ApplicationContext接口及其子類是否對用戶更簡單?

  是的,這裏咱們能夠用到前面學習的設計模式——外觀模式,即提供一個新的外觀,用戶只須要知道要調用哪些接口和類,而不須要知道具體的實現。正確的作法是把上面類圖中的BeanFactory和ApplicationContext合併到一塊兒

 

 3.怎麼加載xml文件的配置?怎麼掃描用戶指定包下的類?

 

3.1 加載用戶指定的xml配置文件

思考1:xml的來源會有多種嗎?

  會 

 

那麼它們的加載方式同樣嗎?

  不同

對於xml解析來講,從加載過程它但願得到什麼? 

   xml解析但願得到流InputStream

咱們但願能加載不一樣來源的xml,向解析xml配置提供統一的使用接口,那麼該如何來設計接口和類呢?

   讓解析xml配置面向接口編程,不一樣的xml來源都實現該接口,提供不一樣的實現去加載xml配置文件最終都生產出一個InputStream

 

問題:這裏咱們定義不一樣的Resource類對應不一樣的xml來源,誰去負責分辨建立他們的對象?由於用戶給定的是一個一個的字符串(這對他們來講是最簡單的方式)

   這個分辨字符串 ,建立對應的Resource對象的工做就是加載xml配置文件,這個事情有ApplicationContext來作。

  這裏就須要使用前面學過的設計模式工廠模式了:根據不一樣的字符串建立不一樣的對象

   給ApplicationContext定義一個加載xml配置文件的行爲ResourceLoader::

 

問題:怎麼分辨字符串?

  定義一套規則

工廠根據不一樣的前綴來區分,建立不一樣的Resource對象

3.2 註解的方式如何掃描

掃描過程以下:

到指定的包目錄下找出全部的類文件(包含子孫包下的)

根據上面的掃描過程分析,咱們須要定義一個正則表達式的匹配器來看掃描到的路徑是否匹配用戶指定的掃描包的路徑:

思考:若是要掃描的是com.study下全部service包下的類,如今知足嗎?

   com.study/**/service/*     這種寫法是ant path表達式

這時就須要在PathMatcher匹配器的基礎上擴展一個ant path表達式的匹配器了:

 

思考:掃到了指定包下的class文件,咱們最終須要的是什麼?

   咱們最終須要的是類名,由於要拿類名去獲取bean定義信息、建立bean定義、註冊bean定義到bean工廠

  那麼這裏咱們須要設計什麼樣的接口和類呢?

   在加載xml文件的時候存放掃描到的類能夠嗎?

    不能夠

 

思考:掃描的事情是由AnnotationApplicationContext這個類來作仍是外包給其餘的類來作?

     外包給ClassPathBeandefinitionScanner這個類來作

說明:

掃描外包給ClassPathBeandefinitionScanner這個類來作,ClassPathBeandefinitionScanner裏面的scan方法掃描完成之後獲得bean定義信息,而後建立bean定義,把bean定義經過-registry : BeanDefinitionRegistry註冊到bean工廠

思考:在哪裏啓動掃描調用ClassPathBeandefinitionScanner的scan方法?

  在AnnotationApplicationContext的構造方法裏面調用scan方法

4. 提供方解析xml配置文件、提供方反射獲取bean定義註解

 

問題1:加載和掃描的輸出是什麼?

     加載和掃描的輸出是Resource

 說明:

上面這張圖紅色部分要作的事情就是解析xml、反射獲取註解,而後獲得Resource讀取bean定義信息,把bean定義信息註冊到bean工廠裏面去建立bean對象,由此咱們能夠作下面的設計:

 說明:

BeanDefinitionReader負責解析xml、反射獲取註解獲得Resource,而後AbstractBeanDefinitionReader從Resource裏面獲取bean定義信息、建立bean定義、註冊bean定義到bean工廠,具體的實現交給XmlBeanDefinitionReader和AnnotationBeanDefinitionReader這兩個讀取器來作

 那麼誰應該持有BeanDefinitionReader呢?請看下面完整的類圖:

 

下面根據上面完整的類圖開始寫代碼:

先定義接口,再定義抽象類、最後再定義具體類,把架子搭起來,而後再開始寫具體的業務邏輯

 完整代碼獲取地址:

 https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/spring-v4

相關文章
相關標籤/搜索