Spring入門:The IoC Container,實踐篇(上)
- 2019 年 10 月 4 日
- 筆記

圖文無關
Spring 框架的 IoC 容器(Inversion of Control,Ioc)是 Spring 框架中最基礎、最重要的部分。Ioc 也被稱為依賴注入(Dependency Injection,DI),是一種將組件依賴項的創建和管理外部化的技術。

圖:Spring 架構圖
1. Spring IoC 容器簡介與 Beans
It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern. —— What is IoC ?
- IoC 包括「依賴注入」與「依賴查找」;
- org.springframework.beans、org.springframework.context 是 Spring Ioc 容器的基礎包;
- BeanFactory 是 Spring IoC 容器最核心的介面,提供了 IoC 的基礎配置機制。
- ApplicationContext 是建立在 BeanFactory 之上,提供了更多面嚮應用的功能(例如:國際化、框架事件體系、AOP集成) 。
- 凡是被 Spring IoC 容器管理的對象,即為 Beans。Spring IoC 容器負責這些 Bean 的構造、組裝、生命周期管理。

圖:BeanFactory 局部結構
2. 容器概述
- org.springframework.context.ApplicationContext 介面即代表 Spring IoC 容器。
- ClassPathXmlApplicationContext、FileSystemApplicationContext、AnnotationConfigApplicationContext 是幾個常見的 ApplicationContext 實現類。
- Spring IoC 容器通過「配置元數據(configuration metadata)」獲得如何初始化、配置、組裝對象的資訊。
- 「配置元數據」可以用 XML(經典、傳統)、Java註解(Spring 2.5)、Java程式碼(Spring 3.0)、Groovy程式碼表示。

圖:The Spring IoC container
最最最基礎的示例:
package webj2ee; public class HelloWorld { // Bean public void sayHello(){ System.out.println("Hello World!"); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd "> <bean id="helloWorld" class="webj2ee.HelloWorld"/> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); HelloWorld helloWorld = context.getBean("helloWorld", HelloWorld.class); helloWorld.sayHello(); } }

3. Bean 概述
Spring IoC 容器管理著那些構成你應用的對象(Bean),這些 Bean 如何初始化、組裝則是通過你提供給 IoC 容器的「配置元數據(XML、註解、Java、Groovy)」控制。不管是哪種形式的「配置元數據」,在 IoC 容器內部,都會轉換為 BeanDefinition。

圖:BeanDefinition 基本結構
3.1. 命名 Beans
- 只可以指定一個id;
- 可以指定多個別名;
示例:
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="hello" name="hello1, hello2, hello3" class="webj2ee.Hello"/> <alias name="hello" alias="hello4"/> </beans>
package webj2ee; import org.apache.commons.lang3.StringUtils; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); Object o = context.getBean("hello"); Object o1 = context.getBean("hello1"); Object o2 = context.getBean("hello2"); Object o3 = context.getBean("hello3"); Object o4 = context.getBean("hello4"); System.out.println(o == o1); System.out.println(o1 == o2); System.out.println(o2 == o3); System.out.println(o3 == o4); String[] aliases = context.getAliases("hello"); System.out.println(StringUtils.join(aliases, ", ")); } }

- 命名規範:建議參考 java beans 命名規範;(通常採用駝峰命名方式,但首字母小寫(例如:loginController、accountDao)。例外情況是,如果前兩個字母都是大寫,則保持原始命名不變(例如:URLConnection))
/** * Utility method to take a string and convert it to normal Java variable * name capitalization. This normally means converting the first * character from upper case to lower case, but in the (unusual) special * case when there is more than one character and both the first and * second characters are upper case, we leave it alone. * <p> * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays * as "URL". * * @param name The string to be decapitalized. * @return The decapitalized version of the string. */ public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); }
程式碼:java.beans 的命名規範程式碼;
3.2. 實例化 Beans
- 通過構造函數實例化Bean;
- 通過靜態工廠方法實例化Bean;
- 通過實例工廠方法實例化Bean;
示例1:(構造函數)
package webj2ee; public class HelloWorld { public void sayHello(){ System.out.println("Hello World!"); } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="helloWorld" class="webj2ee.HelloWorld"/> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); HelloWorld o = context.getBean("helloWorld", HelloWorld.class); o.sayHello(); } }
示例2:(靜態工廠方法實例化Bean)
package webj2ee; public class HelloWorld { private static HelloWorld instance = new HelloWorld(); private HelloWorld(){} public static HelloWorld getInstance(){ return instance; } public void sayHello(){ System.out.println("Hello World!"); } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="helloWorld" class="webj2ee.HelloWorld" factory-method="getInstance"/> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); HelloWorld o = context.getBean("helloWorld", HelloWorld.class); o.sayHello(); } }
示例3:(實例工廠方法實例化Bean)
package webj2ee; public class HelloWorld { private static HelloWorld instance = new HelloWorld(); private HelloWorld(){ } public HelloWorld getInstance(){ return instance; } public void sayHello(){ System.out.println("Hello World!"); } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="helloWorldGetter" class="webj2ee.HelloWorld"/> <bean id="helloWorld" factory-bean="helloWorldGetter" factory-method="getInstance"/> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Demo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); HelloWorld o = context.getBean("helloWorld", HelloWorld.class); o.sayHello(); } }
4. 依賴關係管理
4.1. 依賴注入
- 依賴注入包含「構造器注入」、「屬性注入」;
- 面向介面編程 + 依賴注入 => 程式更容易測試;
- 構造器注入,不允許循環依賴,常用於實現對不可變對象的依賴;
- 通過 <constructor-arg> 描述構造器注入參數;
- 通過 <property> 描述屬性注入參數;
- <constructor-arg>,不能通過編寫順序標記參數 ;
- <constructor-arg>,可通過「類型(type)」標記參數;
- <constructor-arg>,可通過「索引(index)」標記參數;
- <constructor-arg>,可通過「名稱(name)」標記參數;(需配合 @ConstructorProperties 註解)
package webj2ee; public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } @Override public String toString() { return "ExampleBean{" + "years=" + years + ", ultimateAnswer='" + ultimateAnswer + ''' + '}'; } }
示例1:通過 type 標記構造函數參數的順序
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean> </beans>
示例2:通過 index 標記構造函數參數的順序
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean> </beans>
示例3:通過 name 標記構造函數參數的順序
package webj2ee; import java.beans.ConstructorProperties; public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } @Override public String toString() { return "ExampleBean{" + "years=" + years + ", ultimateAnswer='" + ultimateAnswer + ''' + '}'; } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean> </beans>
示例4:靜態工廠方法——構造器注入
package webj2ee; public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; private ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } private static ExampleBean getInstance(int years, String ultimateAnswer) { return new ExampleBean(years, ultimateAnswer); } @Override public String toString() { return "ExampleBean{" + "years=" + years + ", ultimateAnswer='" + ultimateAnswer + ''' + '}'; } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean" factory-method="getInstance"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean> </beans>
示例5:構造器注入——循環依賴
package webj2ee; public class Abean { public Abean(Bbean b){ } }
package webj2ee; public class Bbean { public Bbean(Abean a){ } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="abean" class="webj2ee.Abean"> <constructor-arg ref="bbean"/> </bean> <bean id="bbean" class="webj2ee.Bbean"> <constructor-arg ref="abean"/> </bean> </beans>


4.2. 依賴注入細節
- 構造器參數,可通過 <constructor-arg/> 指定;
- 屬性參數,可通過 <property/> 指定;
- 構造器參數,可通過 c-namespace 簡化 <constructor-arg/> 複雜度;
- 屬性參數,可通過 p-namespace 簡化 <property/> 複雜度;
- 構造器參數、屬性參數,都可指定多種類型變數值;
示例1:通過 <property> 指定基本數據類型;
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/mydb" /> <property name="username" value="root" /> <property name="password" value="oracledba" /> <property name="initialSize" value="1" /> <property name="minIdle" value="1" /> <property name="maxActive" value="20" /> <property name="maxWait" value="60000" /> </bean> </beans>
示例2:通過 p-namespace 簡化 <property> 語法;
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close" p:url="jdbc:mysql://localhost:3306/mydb" p:username="root" p:password="oracledba" p:initialSize="1" p:minIdle="1" p:maxActive="20" p:maxWait="6000"> </bean> </beans>
示例3:引用 bean(java.util.Properties);
package webj2ee; import java.util.Properties; public class ExampleBean { private Properties props; public ExampleBean(Properties props){ this.props = props; } @Override public String toString() { return "ExampleBean{" + "props=" + props + '}'; } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean"> <constructor-arg> <!-- 通過 <props> 構造 java.util.Properties 對象 --> <props> <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop> <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop> </props> </constructor-arg> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <!-- 通過 utils.properties 構造 java.util.Properties 對象 --> <util:properties id="props"> <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop> <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop> </util:properties> <!-- 完整版 --> <bean id="exampleBean" class="webj2ee.ExampleBean"> <constructor-arg> <ref bean="props"/> </constructor-arg> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <util:properties id="props"> <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop> <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop> </util:properties> <!-- 簡化版 --> <bean id="exampleBean" class="webj2ee.ExampleBean"> <constructor-arg ref="props"/> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <util:properties id="props"> <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop> <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop> </util:properties> <!-- 再簡化 --> <bean id="exampleBean" class="webj2ee.ExampleBean" c:props-ref="props"> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean"> <constructor-arg> <!-- InnerBean 形式注入 java.util.Properties 參數 --> <util:properties> <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop> <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop> </util:properties> </constructor-arg> </bean> </beans>

示例4:引用父容器 bean(java.util.Properties);
parent.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <util:properties id="props"> <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop> <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop> </util:properties> </beans>
child.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean"> <constructor-arg> <ref parent="props"/> </constructor-arg> </bean> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // parent context ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("parent.xml"); // child context ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(new String[]{"child.xml"}, parent); ExampleBean exampleBean = child.getBean("exampleBean", ExampleBean.class); System.out.println(exampleBean); } }


4.3. depends-on、lazy-init
- 某個 Bean 並沒通過構造器注入、屬性注入顯式描述與其他 Bean 的依賴關係,但又想保證自己的初始化必須建立在某些 Bean 已經初始化完成的基礎上,就需要利用 depends-on 特性。
示例:
package webj2ee; public class DependOnBeanA { public DependOnBeanA() { System.out.println("DependOnBeanA Init Success!"); } }
package webj2ee; public class DependOnBeanB { public DependOnBeanB(){ System.out.println("DependOnBeanB Init Success!"); } }
package webj2ee; public class ExampleBean { public ExampleBean(){ System.out.println("ExampleBean Init Success!"); } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean" depends-on="dependOnBeanA,dependOnBeanB"></bean> <bean id="dependOnBeanA" class="webj2ee.DependOnBeanA"></bean> <bean id="dependOnBeanB" class="webj2ee.DependOnBeanB"></bean> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // parent context ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class); System.out.println(exampleBean); } }

- Spring默認情況下,所有的單例 Bean,在裝配的時候,就實例化。如果想把Bean的實例化過程延後到第一次 getBean 的時候,就需要 lazy-init 特性;
package webj2ee; public class NotLazyBean { public NotLazyBean(){ System.out.println("NotLazyBean Init Success!"); } }
package webj2ee; public class LazyBean { public LazyBean(){ System.out.println("LazyBean Init Success!"); } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="lazyBean" class="webj2ee.LazyBean" lazy-init="true"></bean> <bean id="notLazyBean" class="webj2ee.NotLazyBean"></bean> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // parent context ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); System.out.println("After Container Init!"); System.out.println("Before get notLazyBean!"); NotLazyBean notLazyBean = context.getBean("notLazyBean", NotLazyBean.class); System.out.println("After get notLazyBean!"); System.out.println("Before get lazyBean!"); LazyBean lazyBean = context.getBean("lazyBean", LazyBean.class); System.out.println("After get lazyBean!"); } }

4.4. Method Injection(Lookup Method)
- 當一個 Singleton Bean A依賴一個 Prototype Bean B,每次對 Bean A 方法的調用,都期望得到(操作)一個新的 Bean B,這時候 Method Injection(Lookup Method)就會派上用場。
反例:Singleton 依賴 Prototype,無法每次都得到新的 Prototype Bean;
package webj2ee; public class PrototypeBean { }
package webj2ee; public class SingletonBean { PrototypeBean prototypeBean; public void setPrototypeBean(PrototypeBean prototypeBean) { this.prototypeBean = prototypeBean; } public PrototypeBean getNewPrototypeBean(){ return prototypeBean; } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="singletonBean" class="webj2ee.SingletonBean" p:prototypeBean-ref="prototypeBean"/> <bean id="prototypeBean" class="webj2ee.PrototypeBean" scope="prototype"></bean> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); SingletonBean singletonBean = context.getBean("singletonBean", SingletonBean.class); System.out.println(singletonBean.getNewPrototypeBean() == singletonBean.getNewPrototypeBean()); } }

正例1:通過 ApplicationContextAware 實現;
package webj2ee; public class PrototypeBean { }
package webj2ee; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class SingletonBean implements ApplicationContextAware { ApplicationContext applicationContext; public PrototypeBean getNewPrototypeBean(){ return applicationContext.getBean("prototypeBean", PrototypeBean.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="singletonBean" class="webj2ee.SingletonBean"/> <bean id="prototypeBean" class="webj2ee.PrototypeBean" scope="prototype"></bean> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); SingletonBean singletonBean = context.getBean("singletonBean", SingletonBean.class); System.out.println(singletonBean.getNewPrototypeBean() == singletonBean.getNewPrototypeBean()); } }

正例2:通過 <lookup-method> 實現(CGLib)
package webj2ee; public class PrototypeBean { }
package webj2ee; public abstract class SingletonBean { public abstract PrototypeBean getNewPrototypeBean(); }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="singletonBean" class="webj2ee.SingletonBean"> <lookup-method name="getNewPrototypeBean" bean="prototypeBean" ></lookup-method> </bean> <bean id="prototypeBean" class="webj2ee.PrototypeBean" scope="prototype"></bean> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); SingletonBean singletonBean = context.getBean("singletonBean", SingletonBean.class); System.out.println(singletonBean.getNewPrototypeBean() == singletonBean.getNewPrototypeBean()); } }


5. Bean 作用域
singleton:
- 默認作用域;
- per-container and per-bean;

prototype:
- 每次 getBean(),都是一個新的 Bean 實例;
- 通常 DAO(data access object)不是 prototype 類型;
- Spring 容器負責實例化、配置、組裝 prototype 類型 Bean,但不負責銷毀;
- 可考慮通過自定義容器級生命周期管理 Bean Post-Processor 銷毀 prototype 類型 Bean。
- 若 singleton 類型 Bean 有對 prototype 類型 Bean 的依賴,可通過 Method Injection 方法,每次都構造一個全新的 prototype 類型 Bean。

request、session、application:
需要結合AOP說明,暫時跳過;
6. Bean 生命周期控制、xxAware 介面
Initialization Callbacks:
- org.springframework.beans.factory.InitializingBean,容器對Bean完成必要的屬性填充後,即觸發此介面中的【void afterPropertiesSet() throws Exception】方法。
- XML 中定義 Bean 時,init-method,用於實現同樣效果;
- JSR-250 規範中的 @PostConstruct,用於實現同樣效果;
- 如果3者混用,回調順序:@PostConstruct > afterPropertiesSet > init-method
Destruction Callbacks:
- org.springframework.beans.factory.DisposableBean,容器要銷毀的時候,即觸發此介面中的【void destory() throws Exception】方法
- XML 中定義 Bean 時,destory-method,用於實現同樣效果;
- JSR-250規範中的 @PreDestory,用於實現同樣效果;
- 如果3者混用,回調順序:@PreDescoty> destory > destory-method
示例:
package webj2ee; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class ExampleBean implements InitializingBean, DisposableBean { //////////////Initialization Callbacks////////////// @PostConstruct private void postConstruct(){ System.out.println("@PostConstruct"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("afterPropertiesSet"); } private void initMethod(){ System.out.println("init-method"); } //////////////Destruction Callbacks////////////// @PreDestroy private void preDestory(){ System.out.println("@PreDestory"); } @Override public void destroy() throws Exception { System.out.println("destory"); } private void destoryMethod(){ System.out.println("destory-method"); } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <context:annotation-config/> <bean id="exampleBean" class="webj2ee.ExampleBean" init-method="initMethod" destroy-method="destoryMethod"></bean> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class); System.out.println(">>>>"); context.close(); } }

xxAware 介面:
- 實際是在告訴 Spring 容器,你想得到某些特殊依賴關係;
- 常見的 xxAware 介面有:ApplicationContextAware(Method Injection的一種事項方式)、BeanFactoryAware、BeanNameAware、ServletConfigAware、ServletContextAware;
示例:
package webj2ee; import org.springframework.beans.factory.BeanNameAware; public class ExampleBean implements BeanNameAware { @Override public void setBeanName(String name) { System.out.println("setBeanName triggered with: "+name); } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <bean id="exampleBean" class="webj2ee.ExampleBean"></bean> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class); } }

7. 擴展 Spring 容器
Customizing Beans by Using a BeanPostProcessor:
- org.springframework.beans.factory.config.BeanPostProcessor
- BeanPostProcessor 的只會影響它所在 Spring 容器中的 Bean,不會影響到所在容器的父、子、兄弟容器中的 Bean。
- BeanPostProessor 操作的是 Bean 的實例,即 Spring 容器實例化 Bean 後,BeanPostProcessor 即介入。
- 包含兩個callback:一個在 Bean 所有初始化方法前調用、另一個在 Bean 所有初始化方法後調用;
- 如果存在多個 BeanPostProcessor,可通過 @Order 指明執行順序。
- Spring AOP 的一些實現,就是通過 BeanPostProcessor 給 Bean 包裝代理類的。

示例:
package webj2ee; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class ExampleBean implements InitializingBean, DisposableBean { //////////////Initialization Callbacks////////////// @PostConstruct private void postConstruct(){ System.out.println("@PostConstruct"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("afterPropertiesSet"); } private void initMethod(){ System.out.println("init-method"); } //////////////Destruction Callbacks////////////// @PreDestroy private void preDestory(){ System.out.println("@PreDestory"); } @Override public void destroy() throws Exception { System.out.println("destory"); } private void destoryMethod(){ System.out.println("destory-method"); } }
package webj2ee; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Bean '" + beanName + "' before-created : " + bean.toString()); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Bean '" + beanName + "' after-created : " + bean.toString()); return bean; } }
package webj2ee; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.annotation.Order; @Order(2) public class AnotherBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("AnotherBeanPostProcessor-Bean '" + beanName + "' before-created : " + bean.toString()); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("AnotherBeanPostProcessor-Bean '" + beanName + "' after-created : " + bean.toString()); return bean; } }
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <context:annotation-config/> <bean id="exampleBean" class="webj2ee.ExampleBean" init-method="initMethod" destroy-method="destoryMethod"></bean> <bean class="webj2ee.InstantiationTracingBeanPostProcessor"/> <bean class="webj2ee.AnotherBeanPostProcessor"/> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class); context.close(); } }

Customizing Configuration Metadata with a BeanFactoryPostProcessor:
- org.springframework.beans.factory.config.BeanFactoryPostProcessor
- BeanFactoryPostProcessor 操作的是 Bean 的配置元數據,所以它在 Spring 容器實例化 Bean 之前生效。
- 如果有多個 BeanFactoryPostProcessor,可以通過 @Order 控制順序。
- 生效範圍只是它所在的 Spring 容器。
- PropertyOverrideConfigurer、PropertyPlaceholderConfigurer 就是 Spring 中自帶的 BeanFactoryPostProcessor。
示例1:(PropertyPlaceholderConfigurer )
- 用於實現配置項的外部化。
- 元素 <context:property-placeholder/> 即代表 PropertyPlaceholderConfigurer 的一個子類。
- PropertyPlaceholderConfigurer 不僅會查找自身的配置,而且會在 Java System 屬性中查找(systemPropertiesMode 可以細粒度會控制搜索方式(nerver、fallback(default)、override)) 。
- 使用 ${xyz} 形式表示(與 JSP EL、Ant EL、Log4j EL 表達式貼合)(注意與 EL 表達式 #{xyz} 作區分)
package webj2ee; public interface LanePromptDao { LanePrompt getLanePromot(String promptId); void saveLanePrompt(String promptId, LanePrompt lanePrompt); }
package webj2ee; public class SessionLanePromptDao implements LanePromptDao { public SessionLanePromptDao() { System.out.println("SessionLanePromptDao"); } @Override public LanePrompt getLanePromot(String promptId) { return null; } @Override public void saveLanePrompt(String promptId, LanePrompt lanePrompt) { } }
package webj2ee; public class RedisLanePromptDao implements LanePromptDao { public RedisLanePromptDao() { System.out.println("RedisLanePromptDao"); } @Override public LanePrompt getLanePromot(String promptId) { return null; } @Override public void saveLanePrompt(String promptId, LanePrompt lanePrompt) { } }
package webj2ee; public class LanePrompt { private LanePromptDao dao; public void setDao(LanePromptDao dao) { this.dao = dao; } // ... business logic }
// application.properties dw.sef.SAVE_PROMPT_IN_REDIS=true
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <context:property-placeholder location="classpath:application.properties"/> <bean id="lanePromptDao" class="#{'${dw.sef.SAVE_PROMPT_IN_REDIS}'=='true' ? 'webj2ee.RedisLanePromptDao': 'webj2ee.SessionLanePromptDao'}"></bean> <bean id="lanePrompt" class="webj2ee.LanePrompt" p:dao-ref="lanePromptDao"/> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); LanePrompt lanePrompt = context.getBean("lanePrompt", LanePrompt.class); context.close(); } }

示例2:(PropertyOverrideConfigurer)
- 跟 PropertyOverrideConfigurer 很類似,但作用是用於覆蓋 Bean 中的屬性;
- 格式:beanName.property=value,支援嵌套:tom.fred.bob.sammy=123
- 如果有多個 PropertyOverrideConfigurer,最後一個生效(覆蓋策略)。
package webj2ee; public class ExampleBean { private String name; public void setName(String name) { this.name = name; } @Override public String toString() { return "ExampleBean{" + "name='" + name + ''' + '}'; } }
## override.properties ## beanName.property=value exampleBean.name=xiaogang
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <context:property-override location="classpath:override.properties"/> <bean id="exampleBean" class="webj2ee.ExampleBean" p:name="xiaohong"/> </beans>
package webj2ee; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml"); ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class); System.out.println(exampleBean); context.close(); } }

Customizing Instantiation Logic with a FactoryBean:
- FactoryBean 是一個 Bean,只不過是用於生成其他 Bean 的工廠 Bean。
- 常用於封裝過於複雜的初始化邏輯;
- Spring 中自帶超過 50 個 FactoryBean 的實現。
- 可通過 getBean("&xxx"),獲取 FactoryBean,而不是它產生的 Bean 本身。

圖:Spring 自帶的 FactoryBean 實現類
package org.springframework.web.context.support; import javax.servlet.ServletContext; import org.springframework.beans.factory.FactoryBean; import org.springframework.lang.Nullable; import org.springframework.web.context.ServletContextAware; public class ServletContextParameterFactoryBean implements FactoryBean<String>, ServletContextAware { @Nullable private String initParamName; @Nullable private String paramValue; /** * Set the name of the ServletContext init parameter to expose. */ public void setInitParamName(String initParamName) { this.initParamName = initParamName; } @Override public void setServletContext(ServletContext servletContext) { if (this.initParamName == null) { throw new IllegalArgumentException("initParamName is required"); } this.paramValue = servletContext.getInitParameter(this.initParamName); if (this.paramValue == null) { throw new IllegalStateException("No ServletContext init parameter '" + this.initParamName + "' found"); } } @Override @Nullable public String getObject() { return this.paramValue; } @Override public Class<String> getObjectType() { return String.class; } @Override public boolean isSingleton() { return true; } }
太長了….先寫到這….下一篇繼續….

參考:
Spring Framework Runtime: https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/overview.html The IoC Container: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core Inversion of Control Containers and the Dependency Injection pattern: https://martinfowler.com/articles/injection.html 《Java Web 高級編程技術》 《Spring 入門經典》 《精通 Spring 4.x 企業應用開發實戰》 《Spring5 高級編程》 《Spring實戰(第3版)》