1.1.1 XML配置的結構
一般配置文件結構如下:
<beans> <import resource=”resource1.xml” /> <bean id=”bean1” class=””></bean> <bean name=”bean2” class=””></bean> <alias alias="bean3" name="bean2" /> <import resource=”resource2.xml” /> </beans>
1、<bean>標簽主要用來進行Bean定義;
2、alias用於定義Bean別名的;
3、import用於導入其他配置文件的Bean定義,這是為了加載多個配置文件,當然也可以把這些配置文件構造為一個數組(new String[] {“config1.xml”, config2.xml})傳給ApplicationContext實現進行加載多個配置文件,那一個更適合由用戶決定;這兩種方式都是通過調用Bean Definition Reader 讀取Bean定義,內部實現沒有任何區別。<import>標簽可以放在<beans>下的任何位置,沒有順序關系。
1.1.2 Bean的配置
Spring IoC容器目的就是管理Bean,這些Bean將根據配置文件中的Bean定義進行創建,而Bean定義在容器內部由BeanDefinition對象表示,該定義主要包含以下信息:
●全限定類名(FQN):用於定義Bean的實現類;
●Bean行為定義:這些定義了Bean在容器中的行為;包括作用域(單例、原型創建)、是否惰性初始化及生命周期等;
●Bean創建方式定義:說明是通過構造器還是工廠方法創建Bean;
●Bean之間關系定義:即對其他bean的引用,也就是依賴關系定義,這些引用bean也可以稱之為同事bean或依賴bean,也就是依賴注入。
Bean定義只有“全限定類名”在當使用構造器或靜態工廠方法進行實例化bean時是必須的,其他都是可選的定義。難道Spring只能通過配置方式來創建Bean嗎?回答當然不是,某些SingletonBeanRegistry接口實現類實現也允許將那些非BeanFactory創建的、已有的用戶對象注冊到容器中,這些對象必須是共享的,比如使用DefaultListableBeanFactory 的registerSingleton() 方法。不過建議采用元數據定義。
1.1.3 Bean的命名
每個Bean可以有一個或多個id(或稱之為標識符或名字),在這里我們把第一個id稱為“標識符”,其余id叫做“別名”;這些id在IoC容器中必須唯一。如何為Bean指定id呢,有以下幾種方式:
一、不指定id,只配置必須的全限定類名,由IoC容器為其生成一個標識,客戶端必須通過接口“T getBean(Class<T> requiredType)”獲取Bean;例如:
<bean class=”com.ljq.test.HelloWorldImpl”/>
測試代碼片段如下:
@Test public void sayHello() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("helloworld.xml"); //根據類型獲取bean HelloService helloService = beanFactory.getBean(HelloService.class); helloService.sayHello(); }
二、指定id,必須在Ioc容器中唯一;例如:
<bean id="helloWorld" class=”com.ljq.test.HelloWorldImpl”/>
測試代碼片段如下:
@Test public void sayHello() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("helloworld.xml"); //根據id獲取bean HelloService helloService = beanFactory.getBean("helloWorld", HelloService.class); helloService.sayHello(); }
三、指定name,這樣name就是“標識符”,必須在Ioc容器中唯一;例如:
<bean name="helloWorld" class=”com.ljq.test.HelloWorldImpl”/>
測試代碼片段如下:
@Test public void sayHello() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("helloworld.xml"); //根據name獲取bean HelloService helloService = beanFactory.getBean("helloWorld", HelloService.class); helloService.sayHello(); }
四、指定id和name,id就是標識符,而name就是別名,必須在Ioc容器中唯一;例如:
<bean id=”bean1” name=”alias1” class=”com.ljq.test.HelloWorldImpl”/> <!-- 如果id和name一樣,IoC容器能檢測到,並消除沖突 --> <bean id="bean3" name="bean3" class=”com.ljq.test.HelloWorldImpl”/>
測試代碼片段如下:
@Test public void sayHello() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("helloworld.xml"); //根據id獲取bean HelloService bean1 = beanFactory.getBean("bean1", HelloService.class); bean1.sayHello(); //根據別名獲取bean HelloService bean2 = beanFactory.getBean("alias1", HelloService.class); bean2.sayHello(); //根據id獲取bean HelloService bean3 = beanFactory.getBean("bean3", HelloService.class); bean3.sayHello(); String[] bean3Alias = beanFactory.getAliases("bean3"); //因此別名不能和id一樣,如果一樣則由IoC容器負責消除沖突 Assert.assertEquals(0, bean3Alias.length); }
五、指定多個name,多個name用“,”、“;”、“ ”分割,第一個被用作標識符,其他的(alias1、alias2、alias3)是別名,所有標識符也必須在Ioc容器中唯一;
<bean name=”bean1;alias11,alias12;alias13 alias14” class=”com.ljq.test.HelloWorldImpl”/> <!-- 當指定id時,name指定的標識符全部為別名 --> <bean id="bean2" name="alias21;alias22" class=”com.ljq.test.HelloWorldImpl”/>
測試代碼片段如下:
@Test public void sayHello() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("helloworld.xml"); //1根據id獲取bean HelloService bean1 = beanFactory.getBean("bean1", HelloService.class); bean1.sayHello(); //2根據別名獲取bean HelloService alias11 = beanFactory.getBean("alias11", HelloService.class); alias11.sayHello(); //3驗證確實是四個別名 String[] bean1Alias = beanFactory.getAliases("bean1"); System.out.println("=======namingbean5.xml bean1 別名========"); for(String alias : bean1Alias) { System.out.println(alias); } Assert.assertEquals(4, bean1Alias.length); //根據id獲取bean HelloService bean2 = beanFactory.getBean("bean2", HelloService.class); bean2.sayHello(); //2根據別名獲取bean HelloService alias21 = beanFactory.getBean("alias21", HelloService.class); alias21.sayHello(); //驗證確實是兩個別名 String[] bean2Alias = beanFactory.getAliases("bean2"); System.out.println("=======namingbean5.xml bean2 別名========"); for(String alias : bean2Alias) { System.out.println(alias); } Assert.assertEquals(2, bean2Alias.length); }
六、使用<alias>標簽指定別名,別名也必須在IoC容器中唯一
<bean name="bean" class=”com.ljq.test.HelloWorldImpl”/>
<alias alias="alias1" name="bean"/>
<alias alias="alias2" name="bean"/>
測試代碼片段如下:
@Test public void test6() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("chapter2/namingbean6.xml"); //根據id獲取bean HelloService bean = beanFactory.getBean("bean", HelloService.class); bean.sayHello(); //根據別名獲取bean HelloService alias1 = beanFactory.getBean("alias1", HelloService.class); alias1.sayHello(); HelloService alias2 = beanFactory.getBean("alias2", HelloService.class); alias2.sayHello(); String[] beanAlias = beanFactory.getAliases("bean"); System.out.println("=======namingbean6.xml bean 別名========"); for(String alias : beanAlias) { System.out.println(alias); } System.out.println("=======namingbean6.xml bean 別名========"); Assert.assertEquals(2, beanAlias.length); }
從定義來看,name或id如果指定它們中的一個時都作為“標識符”,那為什么還要有id和name同時存在呢?這是因為當使用基於XML的配置元數據時,在XML中id是一個真正的XML id屬性,因此當其他的定義來引用這個id時就體現出id的好處了,可以利用XML解析器來驗證引用的這個id是否存在,從而更早的發現是否引用了一個不存在的bean,而使用name,則可能要在真正使用bean時才能發現引用一個不存在的bean。
●Bean命名約定:Bean的命名遵循XML命名規范,但最好符合Java命名規范,由“字母、數字、下划線組成“,而且應該養成一個良好的命名習慣, 比如采用“駝峰式”,即第一個單詞首字母開始,從第二個單詞開始首字母大寫開始,這樣可以增加可讀性。
1.1.4 實例化Bean
Spring IOC容器如何實例化Bean呢?傳統應用程序可以通過new和反射方式進行實例化Bean。而Spring IOC容器則需要根據Bean定義里的配置元數據使用反射機制來創建Bean。在Spring IOC容器中根據Bean定義創建Bean主要有以下幾種方式:
一、使用構造器實例化Bean:這是最簡單的方式,Spring IOC容器即能使用默認空構造器也能使用有參數構造器兩種方式創建Bean,如以下方式指定要創建的Bean類型:
使用空構造器進行定義,使用此種方式,class屬性指定的類必須有空構造器
<bean id="helloServiceNoWithArgs" class="com.ljq.test.HelloServiceImpl2" />
使用有參數構造器進行定義,使用此種方式,可以使用<constructor-arg>標簽指定構造器參數值,其中index表示位置,value表示常量值,也可以指定引用,指定引用使用ref來引用另一個Bean定義,后邊會詳細介紹:
<bean id="helloServiceWithArgs" class="com.ljq.test.HelloServiceImpl2">
<!-- 指定構造器參數 -->
<constructor-arg index="0" value="Hello Spring!"/>
</bean>
知道如何配置了,讓我們做個例子來實踐一下:
a、准備Bean class(HelloWorldImpl2.java),該類有一個空構造器和一個有參構造器:
package com.ljq.test; public class HelloServiceImpl2 implements HelloService { private String message; /** * 空構造器 */ public HelloServiceImpl2() { this.message = "Hello World!"; } /** * 帶參構造器 * * @param message */ public HelloServiceImpl2(String message) { this.message = message; } public void sayHello() { System.out.println(message); } }
b、在配置文件helloworld.xml配置Bean定義,如下所示:
<!--使用默認構造參數--> <bean id="helloServiceNoWithArgs" class="com.ljq.test.HelloServiceImpl2" /> <!--使用有參數構造參數--> <bean id="helloServiceWithArgs" class="com.ljq.test.HelloServiceImpl2"> <!-- 指定構造器參數 --> <constructor-arg index="0" value="Hello Spring!"/> </bean>
c、配置完了,寫段測試代碼看是否工作:
/** * 使用默認構造參數 */ @Test public void testHelloWorld2() { // 1、讀取配置文件實例化一個IOC容器 ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml"); // 2、從容器中獲取Bean,注意此處完全“面向接口編程,而不是面向實現” HelloService helloService = context.getBean("helloServiceNoWithArgs", HelloService.class); // 3、執行業務邏輯 helloService.sayHello(); } /** * 使用有參數構造參數 */ @Test public void testHelloWorld3() { // 1、讀取配置文件實例化一個IOC容器 ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml"); // 2、從容器中獲取Bean,注意此處完全“面向接口編程,而不是面向實現” HelloService helloService = context.getBean("helloServiceWithArgs", HelloService.class); // 3、執行業務邏輯 helloService.sayHello(); }
二、使用靜態工廠方式實例化Bean,使用這種方式除了指定必須的class屬性,還要指定factory-method屬性來指定實例化Bean的方法,而且使用靜態工廠方法也允許指定方法參數,spring IOC容器將調用此屬性指定的方法來獲取Bean,配置如下所示:
a、先來看看靜態工廠類代碼HelloApiStaticFactory:
package com.ljq.test; /** * 靜態工廠類 * * @author 林計欽 * @version 1.0 2013-11-5 下午10:03:49 */ public class HelloWorldStaticFactory { /** * 工廠方法 * * @param message * @return */ public static HelloService newInstance(String message) { // 返回需要的Bean實例 return new HelloServiceImpl2(message); } }
b、在配置文件helloworld.xml配置Bean定義,如下所示:
<!--使用有參數構造參數-->
<bean id="helloServiceStaticFactory" class="com.ljq.test.HelloWorldStaticFactory" factory-method="newInstance">
<!-- 指定構造器參數 -->
<constructor-arg index="0" value="Hello Static Factory"/>
</bean>
c、配置完了,寫段測試代碼看是否工作:
/** * 使用有參數構造參數 */ @Test public void helloServiceStaticFactory() { // 1、讀取配置文件實例化一個IOC容器 ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml"); // 2、從容器中獲取Bean,注意此處完全“面向接口編程,而不是面向實現” HelloService helloService = context.getBean("helloServiceStaticFactory", HelloService.class); // 3、執行業務邏輯 helloService.sayHello(); }
三、使用實例工廠方法實例化Bean,使用這種方式不能指定class屬性,此時必須使用factory-bean屬性來指定工廠Bean,factory-method屬性指定實例化Bean的方法,而且使用實例工廠方法允許指定方法參數,方式和使用構造器方式一樣,配置如下:
a、准備Bean class(HelloWorldImpl2.java),該類有一個空構造器和一個有參構造器:
package com.ljq.test; /** * 靜態工廠類 * * @author 林計欽 * @version 1.0 2013-11-5 下午10:03:49 */ public class HelloWorldStaticFactory { /** * 工廠方法 * * @param message * @return */ public HelloService newInstance2(String message) { // 返回需要的Bean實例 return new HelloServiceImpl2(message); } }
b、在配置文件helloworld.xml配置Bean定義,如下所示:
<!-- 1、定義實例工廠Bean -->
<bean id="beanInstanceFactory" class="com.ljq.test.HelloWorldStaticFactory" />
<!-- 2、使用實例工廠Bean創建Bean -->
<bean id="bean4" factory-bean="beanInstanceFactory" factory-method="newInstance2">
<constructor-arg index="0" value="Hello Spring!"></constructor-arg>
</bean>
c、配置完了,寫段測試代碼看是否工作:
@Test public void helloServiceStaticFactory2() { // 1、讀取配置文件實例化一個IOC容器 ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml"); // 2、從容器中獲取Bean,注意此處完全“面向接口編程,而不是面向實現” HelloService helloService = context.getBean("bean4", HelloService.class); // 3、執行業務邏輯 helloService.sayHello(); }
通過以上例子我們已經基本掌握了如何實例化Bean了,大家是否注意到?這三種方式只是配置不一樣,從獲取方式看完全一樣,沒有任何不同。這也是Spring IOC的魅力,Spring IOC幫你創建Bean,我們只管使用就可以了,是不是很簡單。
1.1.5 小結
到此我們已經講完了Spring IOC基礎部分,包括IOC容器概念,如何實例化容器,Bean配置、命名及實例化,Bean獲取等等。不知大家是否注意到到目前為止,我們只能通過簡單的實例化Bean,沒有涉及Bean之間關系。接下來一章讓我們進入配置Bean之間關系章節,也就是依賴注入。
---------------------------------------------------------------------------------
Blog:http://www.cnblogs.com/linjiqin/
Hadoop交流群(250363249)、Java+Oracle交流群(158560018)
題外話:
本人來自鐵觀音的發源地——泉州安溪,有需要正宗安溪鐵觀音的友友歡迎Q我:416501600。