Spring揭祕-筆記

任何技術或事物都有其適用常景。

 

關於setter注入和構造注入的討論就算了……沒有誰比誰好,只有看誰更適用於某個常景。選擇合適的就是最好的。

 

一、思想

IoC:控制反轉是一種思想,將原本客戶端的主動創建、直接依賴、事必躬親,反轉過來了:變成了衣來伸手飯來張口(當然這個伸手和張嘴還是必不可少的,不然你就啥都不做,就心想事成啊……你怎麼不去成仙呢)

DI:依賴注入是實現控制反轉的一種具體技術。只要你張口或者伸手了,我就給你煮飯餵你喫,買衣服來給你穿上……爲什麼說是一種實現技術呢,可能還有其他實現技術,目前我還不知道……可能以後不用我親自煮飯了啊,也不用我親自餵你了,我直接委託給機器人給你做飯餵飯就可以了,,,,,,,

很多人就初始時糾結於這兩個概念間的區別,拼命地想啊,就想把他們理清楚、分開來……但是有的資料又說是一樣的,有的又說不一樣……其實,這個重要嗎?除了在面試時那些無聊的面試官會問下這個問題。真的,可能他們自己理解的都是一知半解,我們應該跳出框框來看問題。如果你認爲是一樣的,那就繼續當做是一樣的吧;如果你接收的是不一樣的概念,那麼也請繼續,沒必要在這裏糾結了,後面還要學的東西多着呢,,,,,我這裏分開來也只是我自己給自己的解釋而已,不必當真。

 

1.2、注入方式

Martin Fowler的那篇文章中定義的三種依賴注入方式:構造注入、setter方法注入、接口注入。

有必要爭執哪種方式最好嗎?No!!!!!!why?請看本文第一句話。

構造注入:寫法繁瑣,但是可以在初始化時即檢測是否有bean被注入了,如果沒有就當場發飆弄掛JVM,不要讓它再啓動了,防止以後出現位置的問題。

setter方法注入:寫法簡單方便,but 可能由於沒有注入bean導致在使用時出現NPE。

發現沒有,這兩種方式的優缺點真好相對。第三種接口注入方式不說也罷,因爲沒人會這麼使用。之前在編碼時,如果使用@Autowired這個註解來要注入bean(張嘴、伸手),IDEA就會提示我:什麼不要使用這個啦,然後我就去查資料,在那糾結到底怎麼回事,告訴我說應該使用constructor injection ,什麼balabala之類的理由……我去,我以前還糾結了好久,現在就不糾結了,和我有什麼關係,我反正從沒用過@Autowired,我用的一直都是@Resource,只是看到別人用的Autowired時會糾結於IDE的提示……

 

我自己是一個很糾結的人的,但是隻有在說服自己之後,跳出原來的框才能解脫,所以我儘量用自己的想法來理解和看待。

 

沒必要非得選擇最好的,也沒有唯一,選擇只有合適的纔是最好的。

 

 

1.3、相關概念

IoC的職責:1、創建對象(煮飯);2、注入依賴對象(餵飯)。

IoC怎麼管理對象間的依賴關係(給誰喂什麼飯可不能錯了啊,給小孩餵奶,老人喂粥,其他人喫肉):N種方式:

1、文本記錄依賴關係:張三要喫肉、李四要喝粥、王五不喫不喝……

2、語音、圖像……

任何可以表示關係的且能被IoC方便識別的信息載體都可以用來管理依賴關係,but,畢竟我們還沒到那麼先進的地步,所以還是老老實實的想些可行的靠譜的方案吧:

1、編程式:在代碼中將依賴關係寫好;

2、文本配置:xml或者properties,格式隨便自己定,只要寫出對應的解析器就可以了;

3、註解方式:最後其實也是編程實現的;

 

 

二、Spring的設計

Spring的設計開始了:

既然強調的是基於POJO(相較於EJB說的好了很多,雖然我不清楚EJB是個什麼東西……),那麼就是管理所有的bean(也別問這個bean是什麼含義,沒有含義,就這麼叫!所有的對象都是一個bean),所以定義了這麼一個接口BeanFactory用來管理所有的bean。

2.1、BeanFactory

BeanFactory管理了所有的bean之後,如果需要bean對象(伸手張嘴)就是從中獲取了,上面的那麼多getBean方法看到了吧,就是用來幹這事的。至於怎麼將bean創建出來,注入進去,這個就得拆開來慢慢設計了。

這個是原型代碼:只顯示了獲取bean,創建及注入的事後面再說 

BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置文件路徑"));
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); 
newsProvider.getAndPersistNews(); 

 

BeanFactory裏面就12個方法,且還都是獲取bean和is判斷的方法,根本就沒有註冊對象並管理依賴的功能。那麼bean是怎麼進去的呢?

2.2、BeanDefinitionRegistry

這裏就是一個設計思想了:BeanFactory相當於一個對外的窗口(類似於facade模式),只提供查詢的方法,而具體的事由其他的專業類去完成。這就出現了這個BeanDefinitionRegistry,BeanDefinitionRegistry纔是真正存放對象的地方,BeanFactory只是一個對外暴露出來的接口。

2.3、BeanDefinition

按照面向對象的思想,每一類相同的事物都應該抽取出一個類來,因此在Spring的IoC容器中管理的POJO也抽取了一個共同類:BeanDefinition,每一個BeanDefinition的實例都表示一個被注入的對象,定義了一些bean的class類型信息、構造方法參數信息等。

BeanDefinitionRegistry就是用來操作BeanDefinition的。BeanDefinition主要有兩個實現類:RootBeanDefinition和ChildBeanDefinition

可以上一段代碼來看看怎麼使用的:

    @Test
    public void testCodeBeanRegister() {
        DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
        BeanFactory container = bindViaCode(beanRegistry);
        FXNewsProvider newsProvider = (FXNewsProvider) container.getBean("djNewsProvider");
        newsProvider.getAndPersistNews();
    }

    public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) {
        AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class);
        AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class);
        AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class);
        // 將bean定義註冊到容器中
        registry.registerBeanDefinition("djNewsProvider", newsProvider);
        registry.registerBeanDefinition("djListener", newsListener);
        registry.registerBeanDefinition("djPersister", newsPersister);
        // 指定依賴關係
        // 1. 可以通過構造方法注入方式
        ConstructorArgumentValues argValues = new ConstructorArgumentValues();
        argValues.addIndexedArgumentValue(0, newsListener);
        argValues.addIndexedArgumentValue(1, newsPersister);
        newsProvider.setConstructorArgumentValues(argValues);
        // 2. 或者通過setter方法注入方式
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.addPropertyValue(new PropertyValue("newsListener", newsListener));
        propertyValues.addPropertyValue(new PropertyValue("newPersistener", newsPersister));
        newsProvider.setPropertyValues(propertyValues);
        // 綁定完成
        return (BeanFactory) registry;
    }

因此上面這段代碼就是定義bean容器,包裝bean對象成BeanDefinition,然後將其注入到BeanDefinitionRegistry中,最後就可以從DefaultListableBeanFactory中獲取bean了。

2.4、DefaultListableBeanFactory

但是這裏的DefaultListableBeanFactory爲什麼當做BeanDefinitionRegistry來用了呢?因爲DefaultListableBeanFactory既實現了BeanFactory又實現了BeanDefinitionRegistry,可以來看一下依賴關係的類圖:

綠色的虛線表示實現implement(中間可能有很多abstract的class省略了,包括直接實現和間接實現),灰色的虛線表示關聯關係(即擁有某個對象)。不熟悉的可以去複習下UML哈。

DefaultListableBeanFactory內部是用Map來存儲beanname和beanDefinition對應關係的:

/** Map of bean definition objects, keyed by bean name */
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

2.5、編碼方式實現bean的創建和依賴注入 && 配置文件方式實現的bean創建和依賴注入

理解上面的編碼方式實現bean的創建和依賴注入是必須的,因爲不管換成什麼其他方式,最後都是落實到編碼實現上的。下面再來看看配置文件方式實現的bean創建和依賴管理方式

也是類似的思路:將具體的事扔給具體的人來處理(外包?),配置文件的讀取解析有專門的類來處理,首先還是定義接口了:BeanDefinitionReader,由BeanDefinitionReader相應的實現類讀取配置文件並創建BeanDefinition,然後將BeanDefinition註冊到BeanDefinitionRegistry中。

僞代碼:

BeanDefinitionRegistry beanRegistry = <某個BeanDefinitionRegistry實現類,通常爲DefaultListableBeanFactory>;  
BeanDefinitionReader   beanDefinitionReader = new BeanDefinitionReaderImpl(beanRegistry);  
beanDefinitionReader.loadBeanDefinitions("配置文件路徑");  
// 現在我們就取得了一個可用的BeanDefinitionRegistry實例 

Spring提供了PropertiesBeanDefinitionReader用於讀取properties配置文件來註冊bean,配置文件的格式有其自定義的要求,不過我是從來沒用過的,因爲一直用的都是xml文件配置,對於xml配置格式的Spring提供的實現類是XmlBeanDefinitionReader,這個具體的配置格式就不說了,無非就是那些scheme約束文件中的元素定義而已,什麼<beans><bean> 之類的了……這個具體去看spring的scheme約束文檔吧。如果不想用xml配置方式,想要創新?那也可以自定義一種配置方式來管理依賴關係(至於是文件還是語音,那就看你的心情了,記得要實現BeanDefinitionReader就好……)

加載xml配置文件的BeanFactory的使用:

    public static void main(String[] args)
    {
        DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
        BeanFactory container = bindViaXMLFile(beanRegistry);
        FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
        newsProvider.getAndPersistNews();
    }

    public static BeanFactory bindViaXMLFile
            (BeanDefinitionRegistry registry)
    {
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
        reader.loadBeanDefinitions("classpath:../ news-config.xml");  
        return (BeanFactory)registry;
        // 或者直接  
        //return new XmlBeanFactory(new ClassPathResource("../news-config.xml"));
    }

 

第三個就是使用註解方式來實現的依賴注入了:@Resource、@Autowired、@Controller之類的,以前需要在xml文件中指定這個配置:<context:component-scan base-package="com.xxx.xxx……">

 

三、Spring容器的具體實現

兩階段:容器啓動、bean實例化

 

3.1、容器啓動階段

通過BeanDefinitionReader加載配置文件,解析,創建相應的BeanDefinition對象,註冊到BeanDefinitionRegistry。

3.1.1、插手容器的啓動

Spring提供了一種BeanFactoryPostProcessor的容器擴展機制,在容器註冊好了BeanDefinition之後,在bean實例化之前,允許我們對BeanDefinition修改。

實現:實現接口BeanFactoryPostProcessor,並通過Ordered接口指定順序,將此Processor註冊到BeanFactory中。

Spring提供了幾個現成的BeanFactoryPostProcessor實現類,很少需要自己去寫吧,除非有此業務需求。

PropertyPlaceholderConfigurer:修改佔位符的數據,在xml中使用了佔位符${username.name}之後BeanFactory在加載完所有配置後,BeanDefinition中保存的仍然是佔位符的信息,當PropertyPlaceholderConfigurer被作爲BeanFactoryPostProcessor應用後,纔會使用properties配置文件中的配置信息來替換掉BeanDefinition中佔位符所代表的屬性值。

PropertyPlaceholderConfigurer同時也會從System類的Properties中查找數據來替換,可以配置是否使用System的配置信息。

應用:可以使用加密的DB賬號密碼,然後在PropertyPlaceholderConfigurer中解密此字符,給DBpool注入真實的賬戶密碼信息。

PropertyOverrideConfigurer:這個沒什麼好說的,就是使用properties中的信息來替換掉原來的數據。多個只會保留最後一個。

使用示例代碼:

    // 聲明將被後處理的BeanFactory實例  
    ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
    // 聲明要使用的BeanFactoryPostProcessor  
    PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();
    propertyPostProcessor.setLocation(new ClassPathResource("..."));
    // 執行後處理操作  
    propertyPostProcessor.postProcessBeanFactory(beanFactory);

CustomEditorConfigurer:對BeanDefinition不做改變,只是輔助使用的:因爲xml中的都是字符串,所以需要將此字符串正確的給對象賦值,就需要轉換,就需要CustomEditorConfigurer來做。Spring內部通過PropertyEditor來幫助進行string到具體類型的轉換,只要每種對象類型都提供一個PropertyEditor,Spring就可以根據該類型來獲取其對應的PropertyEditor然後做具體的轉換。Spring已經提供了一些類型轉換的實現:StringArrayPropertyEditor(逗號,分隔的字符串轉數組)、ClassEditor、FileEditor、LocaleEditor、PatternEditor、、、、、這些PropertyEditor Spring都會默認加載,如果是自定義的,那需要自己注入到容器中去。

 

1、自定義PropertyEditor,繼承PropertyEditorSupport(爲了簡化不必要實現PropertyEditor所有的方法),

2、將自定義的PropertyEditor註冊到容器中,

public class DatePropertyEditor extends PropertyEditorSupport {
    private String datePattern;

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(getDatePattern());
        Date dateValue = dateTimeFormatter.parseDateTime(text).toDate();
        setValue(dateValue);
    }
    public String getDatePattern() {
        return datePattern;
    }
    public void setDatePattern(String datePattern) {
        this.datePattern = datePattern;
    }
} 

 

    XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("..."));
    //
    CustomEditorConfigurer ceConfigurer = new CustomEditorConfigurer();
    Map customerEditors = new HashMap();
    customerEditors.put(java.util.Date.class,new DatePropertyEditor());
    ceConfigurer.setCustomEditors(customerEditors);
    //
    ceConfigurer.postProcessBeanFactory(beanFactory);

使用:

public class DateFoo {
    private Date date;

    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
}

配置類似於  
<bean id="dateFoo" class="...DateFoo">  
    <property name="date">  
        <value>2007/10/16</value>  
    </property>  
</bean> 

在ApplicationContext中可以使用xml來註冊bean。

 

3.2、bean實例化階段

當請求getBean時,即實例化bean對象。ApplicationContext是在refresh()方法裏立即實例化了所有的bean。

ææ¯å享

Spring使用策略模式來決定採用何種方式實現bean的初始化。反射直接生成bean還是cglib生成其子類。

策略接口:InstantiationStrategy,實現類:SimpleInstantiationStrategy(以反射方式實現的)、CglibSubclassingInstantiationStrategy(extends SimpleInstantiationStrategy 同時又添加了cglib的方式),Spring默認使用的是CglibSubclassingInstantiationStrategy實現類。

Spring並不是直接返回的bean,而是以BeanWrapper包裹了bean實例後的對象。

BeanWrapper:繼承了PropertyAccessor ,可以以統一的方式來訪問對象屬性。BeanWrapper定義同時又直接或者間接繼承了PropertyEditorRegistry和TypeConverter接口。當把各種PropertyEditor註冊給容器時,就會被BeanWrapper用到!在第一步構造完成對象之後,Spring會根據對象實例構造一個BeanWrapperImpl實例,然後將之前CustomEditorConfigurer註冊的PropertyEditor複製一份給BeanWrapperImpl實例(這就是BeanWrapper同時又是PropertyEditorRegistry的原因)。

使用BeanWrapper操作bean:

    Object provider = Class.forName("package.name.FXNewsProvider").newInstance();
    Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();
    Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance();

    BeanWrapper newsProvider = new BeanWrapperImpl(provider);  
    newsProvider.setPropertyValue("newsListener", listener);
    newsProvider.setPropertyValue("newPersistener", persister);

    assertTrue(newsProvider.getWrappedInstance() instanceof FXNewsProvider);
    assertSame(provider, newsProvider.getWrappedInstance());
    assertSame(listener, newsProvider.getPropertyValue("newsListener"));
    assertSame(persister, newsProvider.getPropertyValue("newPersistener")); 

 

各個Aware接口:只要實現了以這個Aware命名結尾的接口,那麼Spring就會給其注入相應的對象。

BeanNameAware:

BeanClassLoaderAware:

BeanFactoryAware:

ResourceLoaderAware:

ApplicationContextAware:

ApplicationEventPublisherAware:

MessageSourceAware:

 

BeanPostProcessor:上圖中的前置處理和後置處理。使用:Aware各種接口的實現類就是通過BeanPostProcessor來實現的,當ApplicationContext中每個對象的實例化過程走到BeanPost- Processor前置處理這一步時,ApplicationContext容器會檢測到之前註冊到容器的Application-ContextAwareProcessor這個BeanPostProcessor的實現類,然後就會調用其postProcessBefore- Initialization()方法,檢查並設置Aware相關依賴。Spring的AOP則更多地使用BeanPostProcessor來爲對象生成相應的代理對象。

自定義BeanPostProcessor:先定義,然後註冊到容器中,

ConfigurableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(...));  
beanFactory.addBeanPostProcessor(new PasswordDecodePostProcessor());  
...  
// getBean(); 

當然xml配置方式有其格式,上面所有的代碼配置方式,最後真的是用都是用xml配置的,沒見過使用BeanFactory編碼實現注入的。

InitializingBean和init-method:在bean實例化完成後調用的方法。

 

 

 

……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章