一、預設裝配方式 代碼通過getBean();方式從容器中獲取指定的Bean實例,容器首先會調用Bean類的無參構造器,創建空值的實例對象。 舉例: 首先我在applicationContext.xml配置文件中配置了一個bean: 創建SomeServiceImpl對象,但需要註意的是該類的只具有 ...
一、預設裝配方式
代碼通過getBean();方式從容器中獲取指定的Bean實例,容器首先會調用Bean類的無參構造器,創建空值的實例對象。
舉例:
首先我在applicationContext.xml配置文件中配置了一個bean:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 註冊Service 這裡相當於容器做了SomeServiceImpl myService = new SomeServiceImpl(); --> <bean id="myService" class="com.ietree.spring.basic.ioc.SomeServiceImpl"/> </beans>
創建SomeServiceImpl對象,但需要註意的是該類的只具有帶參構造函器,沒有無參構造器:
package com.ietree.spring.basic.ioc; /** * 實現類 * * @author Root */ public class SomeServiceImpl implements ISomeService { private int a; // 這裡註釋掉了無參構造函數,希望容器通過帶參構造函數創建對象 // public SomeServiceImpl() { // System.out.println("執行無參構造器,創建SomeServiceImpl對象"); // } public SomeServiceImpl(int a) { this.a = a; } @Override public void doSomeThing() { System.out.println("執行doSomeThing()方法..."); } }
測試:
@Test public void testConstructor() { // 創建容器對象,載入Spring配置文件 // ClassPathXmlApplicationContext會從類路徑下查找配置文件 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); ISomeService service = (ISomeService) ac.getBean("myService"); service.doSomeThing(); }
此時程式會報以下的錯誤:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myService' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.ietree.spring.basic.ioc.SomeServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.ietree.spring.basic.ioc.SomeServiceImpl.<init>() at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1155) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1099) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83) at com.ietree.spring.basic.test.MyTest.testConstrutor(MyTest.java:67) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.ietree.spring.basic.ioc.SomeServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.ietree.spring.basic.ioc.SomeServiceImpl.<init>() at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:85) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1147) ... 36 more Caused by: java.lang.NoSuchMethodException: com.ietree.spring.basic.ioc.SomeServiceImpl.<init>() at java.lang.Class.getConstructor0(Unknown Source) at java.lang.Class.getDeclaredConstructor(Unknown Source) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:80) ... 37 more
解析:這裡的錯誤報的很明顯,沒有發現預設的構造器。
修改:為該類加上無參構造器:
package com.ietree.spring.basic.ioc; /** * 實現類 * * @author Root */ public class SomeServiceImpl implements ISomeService { private int a; public SomeServiceImpl() { System.out.println("執行無參構造器,創建SomeServiceImpl對象"); } public SomeServiceImpl(int a) { this.a = a; } @Override public void doSomeThing() { System.out.println("執行doSomeThing()方法..."); } }
此時,再次運行測試用例,會發現運行成功。
結論:Spring容器實際上是使用了類的反射機制,會首先調用Bean類的無參構造器創建實例對象。
二、動態工廠Bean
創建SomeServiceImpl類:
package com.ietree.spring.basic.ioc; /** * 實現類 * * @author Root */ public class SomeServiceImpl implements ISomeService { public SomeServiceImpl() { System.out.println("執行無參構造器,創建SomeServiceImpl對象"); } @Override public void doSomeThing() { System.out.println("執行doSomeThing()方法..."); } }
創建工廠類ServiceFactory:
package com.ietree.spring.basic.ioc; /** * 工廠類 * * @author Root */ public class ServiceFactory { public ISomeService getSomeService() { return new SomeServiceImpl(); } }
使用動態工廠方式獲取Bean對象,配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 註冊動態工廠 --> <bean id="factory" class="com.ietree.spring.basic.ioc.ServiceFactory"/> <!-- 註冊Service:動態工廠Bean --> <bean id="myService" factory-bean="factory" factory-method="getSomeService"/> </beans>
在這裡並沒有註冊SomeServiceImpl類,而是通過ServiceFactory工廠的getSomeService方法獲取的。
測試:
@Test public void testFactory1() { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); ISomeService service = (ISomeService) ac.getBean("myService"); service.doSomeThing(); }
運行成功。
三、靜態工廠Bean
靜態工廠和動態工廠不同的是,靜態工廠中使用的是靜態方法創建對象,如:
package com.ietree.spring.basic.ioc; /** * 工廠類 * * @author Root */ public class ServiceFactory { // 使用靜態方法創建對象 public static ISomeService getSomeService() { return new SomeServiceImpl(); } }
對應的配置文件修改如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 註冊Service:靜態工廠Bean --> <bean id="myService" class="com.ietree.spring.basic.ioc.ServiceFactory" factory-method="getSomeService"/> </beans>
測試:
@Test public void testFactory1() { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); ISomeService service = (ISomeService) ac.getBean("myService"); service.doSomeThing(); }
成功創建SomeServiceImpl對象。
四、容器中的Bean的作用域
Bean的作用域(scope)分為四種,分別是singleton、prototype、request、session。
scope:
singleton(預設):單例模式,其對象的創建時機是在Spring容器初始化時創建,是預設值
prototype:原型模式,其對象的創建時機不是在Spring容器初始化時創建,而是在代碼中真正訪問時才創建,每次使用getBean方法獲取的同一個<bean/>的實例都是一個新的實例
request:對於每次HTTP請求,都將會產生一個不同的Bean實例
session:對於每個不同的HTTP session,都將會產生一個不同的Bean實例
驗證:
首先配置作用域為singleton:
<bean id="myService" class="com.ietree.spring.basic.ioc.SomeServiceImpl" scope="singleton"/>
測試:
@Test public void test05() { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); ISomeService service1 = (ISomeService) ac.getBean("myService"); ISomeService service2 = (ISomeService) ac.getBean("myService"); System.out.println("service1 = service2嗎?" + (service1 == service2)); }
程式輸出:
調用無參構造器 service1 = service2嗎?true
結論:當作用域為singleton單例模式時,只會創建一個對象實例,並且對象是在Spring容器初始化時創建。
同樣,當配置為prototype原型模式時:
<bean id="myService" class="com.ietree.spring.basic.ioc.SomeServiceImpl" scope="prototype"/>
程式輸出:
調用無參構造器 調用無參構造器 service1 = service2嗎?false
結論:構造器被調用了兩次,說明創建的service1和service2不是同一個對象,並且對象是在被使用到時才創建的。
五、Bean後處理器
Bean後處理器是一種特殊的Bean,容器中所有的Bean在初始化時,均會自動執行該類的兩個方法。由於該Bean是由其它Bean自動調用執行,不是程式員手工調用,故此Bean無須id屬性。
需要做的是,在Bean後處理器類方法中,只要對Bean類與Bean類中的方法進行判斷,就可實現對指定的Bean的指定的方法進行功能擴展與增強。方法返回的Bean對象,即是增強過的對象。
代碼中需要自定義Bean後處理器類,該類就是實現了介面BeanPostProcessor的類。該介面中包含兩個方法,分別在目標Bean初始化完畢之前與之後執行,它的返回值為功能被擴展或增強後的Bean對象。
舉例:利用Bean後處理器實現大小寫字元串轉換
介面類ISomeService:
/** * 介面類 * * @author Root */ public interface ISomeService { String doSomeThing(); }
實現類SomeServiceImpl:
/** * 實現類 * * @author Root */ public class SomeServiceImpl implements ISomeService { public SomeServiceImpl() { System.out.println("調用無參構造器"); } // 返回小寫字母“abcde” @Override public String doSomeThing() { return "abcde"; } }
定義Bean處理器MyBeanPostProcessor:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; /** * Bean後處理器 * * @author Root */ public class MyBeanPostProcessor implements BeanPostProcessor { // bean:表示當前正在進行初始化的Bean對象 // beanName:表示當前正在進行初始化的Bean對象的id @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("執行----before()方法---"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("執行----after()方法---"); Object obj = Proxy.newProxyInstance( bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object invoke = method.invoke(bean, args); return ((String) invoke).toUpperCase(); } }); return obj; } }
使用JDK動態代理實現大小寫轉換的功能。
配置文件:
<bean id="myService" class="com.ietree.spring.basic.ioc.method2.SomeServiceImpl"/> <!-- 註冊Bean後處理器,由於該Bean是由其它Bean自動調用執行,不是程式員手工調用,故此Bean無須id屬性 --> <bean class="com.ietree.spring.basic.ioc.method2.MyBeanPostProcessor"></bean>
註意:Bean後處理器不需要配置id的,因為它是隨著對象的創建自動調用的。
測試:
@Test public void test05() { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); ISomeService service = (ISomeService) ac.getBean("myService"); String result = service.doSomeThing(); System.out.println(result); }
程式輸出:
調用無參構造器 執行----before()方法--- 執行----after()方法--- ABCDE
增強成功。可以判斷代理類的類型,進行對單個或單獨一類對象做增強。
六、定製Bean的生命周期
Bean實例從創建到最後銷毀,需要經過很多過程,執行很多生命周期方法。
Step1:調用無參構造器,創建實例對象。
Step2:調用參數的setter,為屬性註入值。
Step3:若Bean實現了BeanNameAware介面,則會執行介面方法setBeanName(String beanId),使Bean類可以獲取其在容器中的id名稱。
Step4:若Bean實現了BeanFactoryAware介面,則執行介面方法setBeanFactory(BeanFactory factory),使Bean類可以獲取到BeanFactory對象。
Step5:若定義並註冊了Bean後處理器BeanPostProcessor,則執行介面方法postProcessBeforeInitialization()。
Step6:若Bean實現了InitializingBean介面,則執行介面方法afterPropertiesSet()方法。該方法在Bean的所有屬性的set方法執行完畢後執行,是Bean初始化結束的標誌,即Bean實例化結束。
Step7:若設置了init-method方法,則執行。
Step8:若定義並註冊了Bean後處理器BeanPostProcessor,則執行介面方法postProcessAfterInitialization().
Step9:執行業務方法。
Step10:若Bean實現了DisposableBean介面,則執行介面方法destroy()。
Step11:若設置了destroy-method方法,則執行。
舉例:
創建介面類ISomeService:
/** * 介面類 * * @author Root */ public interface ISomeService { void doSomeThing(); }
創建介面實現類SomeServiceImpl:
import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; /** * 實現類 * * @author Root */ public class SomeServiceImpl implements ISomeService, BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean { // 兩個屬性 private String adao; private String bdao; public void setAdao(String adao) { this.adao = adao; System.out.println("Step2:執行settter"); } public void setBdao(String bdao) { this.bdao = bdao; System.out.println("Step2:執行settter"); } public SomeServiceImpl() { System.out.println("Step1:調用無參構造器"); } @Override public void doSomeThing() { System.out.println("Step9:執行doSomeThing()"); } public void setUp(){ System.out.println("Step7:初始化完畢之後 "); } public void tearDown(){ System.out.println("Step11:銷毀之前"); } @Override public void setBeanName(String name) { System.out.println("Step3:獲取到bean的id = " + name); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("Step4:獲取到BeanFactory容器 "); } @Override public void afterPropertiesSet() throws Exception { System.out.println("Step6:Bean初始化完畢了 "); } @Override public void destroy() throws Exception { System.out.println("Step10:實現的介面銷毀之前 "); } }
創建BeanPostProcessor介面的實現類MyBeanPostProcessor:
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; /** * Bean後處理器 * * @author Root */ public class MyBeanPostProcessor implements BeanPostProcessor { // bean:表示當前正在進行初始化的Bean對象 // beanName:表示當前正在進行初始化的Bean對象的id @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Step5:執行----before()方法---"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Step8:執行----after()方法---"); return bean; } }
配置applicationContext.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 註冊Service --> <bean id="myService" class="com.ietree.spring.basic.ioc.method3.SomeServiceImpl" init-method="setUp" destroy-method="tearDown"> <property name="adao" value="aaa"></property> <property name="bdao" value="bbb"></property> </bean> <!-- 註冊Bean後處理器,由於該Bean是由其它Bean自動調用執行,不是程式員手工調用,故此Bean無須id屬性 --> <bean class="com.ietree.spring.basic.ioc.method3.MyBeanPostProcessor"></bean> </beans>
測試類:
@Test public void test05() { String resource = "com/ietree/spring/basic/ioc/method3/applicationContext.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(resource); ISomeService service = (ISomeService) ac.getBean("myService"); service.doSomeThing(); // 對於銷毀方法的執行,有兩個條件: // 1)當前的Bean需要是singleton的 // 2)要手工關閉容器 ((ClassPathXmlApplicationContext) ac).close(); }
程式輸出:
Step1:調用無參構造器 Step2:執行settter Step2:執行settter Step3:獲取到bean的id = myService Step4:獲取到BeanFactory容器 Step5:執行----before()方法--- Step6:Bean初始化完畢了 Step7:初始化完畢之後 Step8:執行----after()方法--- Step9:執行doSomeThing() Step10:實現的介面銷毀之前 Step11:銷毀之前
正如程式輸出的序列一樣,此順序即是對象創建的調用順序,在編程中可以在某一個過程對其進行增強操作。
七、<bean/>標簽的id屬性與name屬性
一般情況下,命名<bean/>使用id屬性,而不是用name屬性,在沒有id屬性的情況下,name屬性與id屬性作用是相同的。但,當<bean/>中含有一些特殊字元時,就需要使用name屬性了。
id的命名需要滿足XML對ID屬性命名規範:必須以字母開頭,可以包含字母、數字、下劃線、連字元、句號、冒號。
name屬性值可以包含各種字元。