javascript
框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)...
一、為什么要提供配置的方法
經(jīng)過前面的手寫Spring IOC、手寫Spring DI、手寫Spring AOP,我們知道要創(chuàng)建一個bean對象,需要用戶先定義好bean,然后注冊到bean工廠才能創(chuàng)建一個bean對象。代碼如下:
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);
那么如果我們上面的過程換成配置的方式會是什么樣的呢?
<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>經(jīng)過上面的創(chuàng)建bean對象的過程由ava代碼轉(zhuǎn)為xml配置的方式,可以看出使用配置有如下優(yōu)勢:
1. 因為我們是寫框架的,提供配置的方式,別人使用更簡單,改動更加靈活
2. 要新增修改東西時不需要改代碼
二、選擇什么樣的配置方式
用過Spring的朋友都知道,配置方式有兩種:
1. xml
2. 注解
三、配置方式的工作過程是怎樣的
?
四、分步驟一個一個的去分析和設(shè)計
1. 定義xml標(biāo)準(zhǔn)和注解標(biāo)準(zhǔn)
首先我們需要理清楚定義xml標(biāo)準(zhǔn)、定義注解標(biāo)準(zhǔn)的目的是什么,定義它們的目的是讓用戶可以使用它們?nèi)ヅ渲胋ean定義和標(biāo)注bean定義。那么
問題1:bean定義需要指定些什么信息呢?
需要指定的信息我們可以從之前寫的BeanDefinition里面看到
?
可以看到bean定義需要上面的這些信息
如果使用xml配置的方式,我們需要為上面的這些信息定義一個DTD(Document Type Definition)文件或者XSD(XML Schemas Definition)文件,具體實現(xiàn)利用了Spring提供的可擴展Schema機制實現(xiàn),實現(xiàn)方式查看我前面的文章:
dubbo系列三:dubbo源碼分析(dubbo框架是如何跟spring進行整合的,消費者獲取的代理實例是如何創(chuàng)建的呢、生產(chǎn)者怎么把內(nèi)容注冊到注冊中心的,然后消費者是如何獲取這個信息的、dubbo負載均衡策略)
然后用戶根據(jù)提供的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)指定構(gòu)造參數(shù)依賴
9)指定屬性依賴
?前面的1)到 7)我們可以定義一個注解@Component,里面持有1)到 7)需要的bean定義的信息,在創(chuàng)建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依賴或者有多個構(gòu)造函數(shù)時指定使用哪個構(gòu)造函數(shù);@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;@Autowiredprivate DBean dbean;@Autowiredpublic ABean(@Value("mike") String name, @Qualifier("cbean01") CBean cb) {super();this.name = name;this.cb = cb;System.out.println("調(diào)用了含有CBean參數(shù)的構(gòu)造方法");}public ABean(String name, CCBean cb) {super();this.name = name;this.cb = cb;System.out.println("調(diào)用了含有CCBean參數(shù)的構(gòu)造方法");}public ABean(CBean cb) {super();this.cb = cb;} }2.??用戶怎么指定配置的xml文件的位置?用戶怎么指定要掃描的包?
同樣的我們需要分析用戶指定xml配置文件的位置和指定掃描的包的目的是什么,目的是讓提供方去加載xml文件和掃描包下的類,那么
問題1:怎么指定?
我們需要為用戶提供一種方式
問題2:下面這些事情在哪里做好?
這里所做的事情是解析xml配置和反射獲取bean定義注解、創(chuàng)建bean定義、向bean工廠注冊bean定義,他不是bean工廠的事,我們應(yīng)該單獨定義接口和類完成這些事
?
無論是XmlApplicationContext還是AnnotationApplicationContext都要使用BeanFactory和BeanDefinitionRegistry,所以可以進一步優(yōu)化類圖
?
現(xiàn)在用戶要使用我們的框架需要知道下面的接口和類:
那么讓用戶只需要知道ApplicationContext接口及其子類是否對用戶更簡單?
是的,這里我們可以用到前面學(xué)習(xí)的設(shè)計模式——外觀模式,即提供一個新的外觀,用戶只需要知道要調(diào)用哪些接口和類,而不需要知道具體的實現(xiàn)。正確的做法是把上面類圖中的BeanFactory和ApplicationContext合并到一起
?
?3.怎么加載xml文件的配置?怎么掃描用戶指定包下的類?
?
3.1 加載用戶指定的xml配置文件
思考1:xml的來源會有多種嗎?
會?
?
那么它們的加載方式一樣嗎?
不一樣
對于xml解析來說,從加載過程它希望獲得什么??
? xml解析希望獲得流InputStream
我們希望能加載不同來源的xml,向解析xml配置提供統(tǒng)一的使用接口,那么該如何來設(shè)計接口和類呢?
? 讓解析xml配置面向接口編程,不同的xml來源都實現(xiàn)該接口,提供不同的實現(xiàn)去加載xml配置文件最終都生產(chǎn)出一個InputStream
?
問題:這里我們定義不同的Resource類對應(yīng)不同的xml來源,誰去負責(zé)分辨創(chuàng)建他們的對象?因為用戶給定的是一個一個的字符串(這對他們來說是最簡單的方式)
? 這個分辨字符串 ,創(chuàng)建對應(yīng)的Resource對象的工作就是加載xml配置文件,這個事情有ApplicationContext來做。
這里就需要使用前面學(xué)過的設(shè)計模式工廠模式了:根據(jù)不同的字符串創(chuàng)建不同的對象
? 給ApplicationContext定義一個加載xml配置文件的行為ResourceLoader::
?
問題:怎么分辨字符串?
定義一套規(guī)則
工廠根據(jù)不同的前綴來區(qū)分,創(chuàng)建不同的Resource對象
3.2 注解的方式如何掃描
掃描過程如下:
到指定的包目錄下找出所有的類文件(包含子孫包下的)
根據(jù)上面的掃描過程分析,我們需要定義一個正則表達式的匹配器來看掃描到的路徑是否匹配用戶指定的掃描包的路徑:
思考:如果要掃描的是com.study下所有service包下的類,現(xiàn)在滿足嗎?
? com.study/**/service/*? ? ?這種寫法是ant path表達式
這時就需要在PathMatcher匹配器的基礎(chǔ)上擴展一個ant path表達式的匹配器了:
?
思考:掃到了指定包下的class文件,我們最終需要的是什么?
? 我們最終需要的是類名,因為要拿類名去獲取bean定義信息、創(chuàng)建bean定義、注冊bean定義到bean工廠
那么這里我們需要設(shè)計什么樣的接口和類呢?
? 在加載xml文件的時候存放掃描到的類可以嗎?
不可以
?
思考:掃描的事情是由AnnotationApplicationContext這個類來做還是外包給其他的類來做?
? 外包給ClassPathBeandefinitionScanner這個類來做
說明:
掃描外包給ClassPathBeandefinitionScanner這個類來做,ClassPathBeandefinitionScanner里面的scan方法掃描完成以后得到bean定義信息,然后創(chuàng)建bean定義,把bean定義通過-registry : BeanDefinitionRegistry注冊到bean工廠
思考:在哪里啟動掃描調(diào)用ClassPathBeandefinitionScanner的scan方法?
在AnnotationApplicationContext的構(gòu)造方法里面調(diào)用scan方法
4. 提供方解析xml配置文件、提供方反射獲取bean定義注解
?
問題1:加載和掃描的輸出是什么?
? ? ?加載和掃描的輸出是Resource
?說明:
上面這張圖紅色部分要做的事情就是解析xml、反射獲取注解,然后得到Resource讀取bean定義信息,把bean定義信息注冊到bean工廠里面去創(chuàng)建bean對象,由此我們可以做下面的設(shè)計:
?說明:
BeanDefinitionReader負責(zé)解析xml、反射獲取注解得到Resource,然后AbstractBeanDefinitionReader從Resource里面獲取bean定義信息、創(chuàng)建bean定義、注冊bean定義到bean工廠,具體的實現(xiàn)交給XmlBeanDefinitionReader和AnnotationBeanDefinitionReader這兩個讀取器來做
?那么誰應(yīng)該持有BeanDefinitionReader呢?請看下面完整的類圖:
?
下面根據(jù)上面完整的類圖開始寫代碼:
先定義接口,再定義抽象類、最后再定義具體類,把架子搭起來,然后再開始寫具體的業(yè)務(wù)邏輯
?完整代碼獲取地址:
?https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/spring-v4
轉(zhuǎn)載于:https://www.cnblogs.com/leeSmall/p/10073931.html
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 汽油进1000方罐多少压力是安全的有没有
- 下一篇: 常见的http错误