《partner4java 講述Spring入門》之第一步:Spring概述與Spring IoC

(導讀:希望在閱讀的過程中,伴隨着動手 -- 把講述的每一步Demo都自己書寫一遍,若遇到問題一時解決不了,可發郵件給我:[email protected];demo下載地址--http://download.csdn.net/detail/partner4java/4779657 ;也可以結合視頻:http://pan.baidu.com/share/link?shareid=122408&uk=4096755201 ,視頻說實話應該不咋地,是第一次錄製,不存在各種講課技巧 )

第一步:扯蛋

•很多人可能接觸了Spring MVC、SpringSecurity啥的,感覺Spring好混亂,親,這都是單獨項目(Spring被VMware收購之後很瘋狂,開闢了大量項目)
•Spring就是個IoC容器,然後實現了一部分aopalliance、AspectJ的規範(自己也定義一些AOP相關的接口)
•至於牛人說的輕量級,只是Spring不需要藉助EE容器,而EJB乾的活Spring幹起來也費勁

下面緊跟的很長一段,會講一些“大道理”,就當天書看一眼吧
begin….


Spring的設計目標 :

•在Java EE的應用開發中,支持POJO和使用JavaBean的開發方式,使應用面向接口開發,充分支持OO(面向對象)的設計方法。
•輕量級,無需像EJB依賴J2EE應用服務器。 
•把依賴關係的管理從Java對象中解放出來,交給IoC容器通過IoC容器實現的依賴反轉。 
•一方面,他通過IoC容器來管理POJO對象,以及他們相互之間的耦合關係,使企業的信息資源可以以簡單的Java語言來抽象和描述;另一方面,可以通過AOP,以動態和非侵入式的方式來增強服務的功能。 
•Spring的設計理念--面向接口開發而不依賴於具體的產品實現。
•實現AOP多種方式,比如他集成了AspectJ框架,同時也有ProxyFactory代理工廠的模式,而在代理工廠的實現中,既有直接使用JVM動態代理Proxy實現,也有使用第三方代理類庫的CGLIB的實現。

Spring架構圖:

•Spring IoC:包含了最基本的IoC容器BeanFactory的接口和實現,還提供了一些列這個接口的實現。
•Spring AOP:圍繞着AOP增強功能,Spring繼承了AspectJ作爲AOP的一個特定實現,同時還在JVM動態代理/CGLIB的基礎上,實現一個AOP框架,作爲Spring繼承其他模塊的工具。在這個模塊中,Spring AOP實現了一個完整的建立AOP代理對象,實現AOP攔截,直至實現各種Advice通知的過程。 
•SpringMVC:這個模塊以DispatcherServlet爲核心,實現了MVC模式,包括怎樣與Web容器環境的繼承,Web請求的攔擊、分發、處理和ModelAndView數據的返回,以及如何集成各種UI視圖展示和數據表現。 
•SpringJDBC/Spring ORM:對JDBC進行封裝,提供了RDBMS的操作對象。同時對現有的ORM框架進行template等封裝提供。 
•Spring的事務處理:Spring事務處理是通過Spring AOP實現自身功能增強的典型模塊。在這個模塊中,Spring把企業開發中事務處理的主要過程抽象出來,並且簡單的通過AOP的切面增強實現了聲明式事務的處理。 
•Spring應用:嚴格來說不屬於Spring的範圍。豐富了整個Spring生態圈,使Spring應用越來越廣泛。 
•Spring遠端調用:spring爲應用屏蔽了各種通訊和調用細節的實現。可以使用HTTP調用器(以HTTP協議爲基礎),可以使用第三方的二進制通訊實現Hessian/Burlap,甚至還封裝了傳統Java技術中的RMI調用。 


end
天書告一段落


現在Spring不都分包了麼?
大體看下包的作用

•spring-aop.jar:此JAR文件包含了所有你在應用中使用Spring AOP特性時需要的類。如果應用中使用了其他涉及AOP的Spring功能時,例如聲明式事務管理,你也需要將此JAR文件包含進來。 
•spring-beans.jar:此文件包含了所有Spring依賴注入需要的代碼。包括Bean工廠和相關支持類。 
•spring-context.jar: 大部分情況下,你會需要加入spring-context.jar文件,他包含了建立應用環境上下文需要的代碼。此JAR包含了建立Spring應用環境上下文所需要的代碼,他將主要的ApplicationContext接口和實現、說明、JNDI、調度、主題和驗證一起納入其中。
•spring-context-support.jar:這個包文件包含了Spring的工具代碼,其中包括緩存、說明、電子郵件、調度支持一級一個有趣的腳本語言支持。 
•spring-core.jar: 此文件包含了Spring框架的核心代碼。它用來處理註解、枚舉、任務執行、資源加載一級其它一些即便在Spring框架環境外也會有用的工具和異常類。
•spring-jdbc.jar:此文件包含了JDBC支持類的代碼,例如JdbcTemplate類和JdbcDaoSupport類。 
spring-jms.jar: 此文件包含JMC的代碼。

•spring-orm.jar: 此文件包含了對象-關係映射(ORM)工具需要的文件。把這個包加入到classpath上會提供對Hibernate3、iBATIS、JDO、JPA和TopLink的Spring支持
•spring-test.jar: 此文件包含了使用Spring框架編寫單元測試和繼承測試的支持代碼。他支持JUnit3、JUnit4和TestNG測試框架。另外,你也可以使用org.springframe-work.mock包中的類,他代表了JNDI類和Web相關類的模擬實現。
•spring-tx.jar:此文件提供了核心的數據訪問異常和事務技術支持。這兩個概念彼此關係密切,因爲一般情況下事務的同某些數據訪問代碼一起工作的。
•spring-web.jar:此文件包含了Spring Web應用支持(工具類、綁定器、分段文件解析器)的代碼。
•spring-webmvc.jar:此文件 包含了Spring MVC代碼。
•spring-webmvc-portlet.jar:此文件包含創建基於porlet(而不是servlet)Web應用所需要的代碼。
•spring-webmvc-struts.jar:此文件包含了在Spring中使用Jakarta Struts框架所需的代碼。 

哥,我走了,別說了,嚇到我了,東西太多了
親,別怕,其實Spring就那十個左右的註解就完事了
牛B的框架不會太難用,牛B的代碼不會太難懂。我們要躋身於那批能寫牛B的代碼人羣中。。。


第二步,簡易IoC:


控制反轉IoC  

begin:


控制反轉和依賴注入:

•控制反轉(IoC)。依賴注入(DI)。
•實際上依賴注入是控制反轉的一種特殊形式。
•控制反轉和依賴注入的核心目標是提供一個更爲簡單的機制去規定和組件之間的依賴關係(他通常被稱作爲對象的協助者),並在他們的生命週期中管理這些依賴。
•一個需要某些特定依賴的組件通常被稱爲依賴對象或者目標。控制反轉提供的服務使一個組件能夠在他的生命週期中訪問他的依賴和服務,使用這個這種方法與他的依賴進行交互,這是一件非常美妙的事情。
•大體來說,控制反轉可以分爲兩個子類型:依賴注入和依賴查找。這些字類型又能劃分成控制反轉服務的若干具體實現。以此定義我們可以清楚的知道,當我們談論依賴注入時我們總是在談論控制反轉,反之則不然。


不依賴Spring,我們先簡單的實現下控制反轉:

•Helloworld背景:
•我們要實現一個男人和多個女人,男人對女人有管理權限,但是這個男人還想能夠隨意的更換女人(當然所有男人都想,除非你懂得)。
•問題:
•當這個男人出生的時候(instance)的時候並不知道未來會是什麼樣的女人可以交予他管理
package com.partner4java.nospring.ioc;

public interface Girl {
	/**
	 * 目前只提供簡單服務
	 * 
	 * @return
	 */
	public String kiss();
}


package com.partner4java.nospring.ioc;

public interface Boy {
	/**
	 * 好吧,我只想關燈,但是關燈之前,我需要一個女孩
	 * 
	 */
	public void closeLight();
}



package com.partner4java.nospring.ioc;

/**
 * 
 * simple introduction 知道白富美麼?
 * <p>
 * detailed comment
 * 
 * @author 王昌龍 2012-9-3
 * @see
 * @since 1.0
 */
public class Baifumei implements Girl {

	@Override
	public String kiss() {
		return "白富美";
	}

}



package com.partner4java.nospring.ioc;

/**
 * 
 * simple introduction 懂蘿莉控麼?
 * <p>
 * detailed comment
 * 
 * @author 王昌龍 2012-9-3
 * @see
 * @since 1.0
 */
public class Luoli implements Girl {

	@Override
	public String kiss() {
		return "小蘿莉";
	}

}



package com.partner4java.nospring.ioc;

/**
 * 
 * simple introduction 對不起了哥們們,我自己寫的代碼,我就先自己當一把男一號了
 * <p>
 * detailed comment
 * 
 * @author 王昌龍 2012-9-3
 * @see
 * @since 1.0
 */
public class XiaoLong implements Boy {
	private Girl girl;

	public void setGirl(Girl girl) {
		this.girl = girl;
	}

	@Override
	public void closeLight() {
		System.out.println(this.getClass().getSimpleName() + " closeLight " + girl.getClass().getSimpleName() + " " + girl.kiss());
	}

}

類路徑下創建一個properties文件:boy2girl.properties

baifumei=com.partner4java.nospring.ioc.Baifumei
luoli=com.partner4java.nospring.ioc.Luoli
longge=com.partner4java.nospring.ioc.XiaoLong

工廠:

package com.partner4java.nospring.ioc;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class BeanFactory {
	private Map<String, String> beanDefinitions;

	public BeanFactory(String beanDefinitionSource) {
		readBeanDefinitions(beanDefinitionSource);
	}

	private void readBeanDefinitions(String beanDefinitionSource) {
		Properties props = new Properties();
		InputStream is = BeanFactory.class.getResourceAsStream(beanDefinitionSource);
		if (is == null) {
			throw new IllegalArgumentException("Could not load properties file " + beanDefinitionSource);
		}
		try {
			props.load(is);
			is.close();

			beanDefinitions = new HashMap<String, String>();

			for (Map.Entry<Object, Object> bean : props.entrySet()) {
				beanDefinitions.put((String) bean.getKey(), (String) bean.getValue());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public Object getBean(String name) {
		String className = beanDefinitions.get(name);
		if (className == null) {
			return null;
		}
		try {
			return Class.forName(className).newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}
開始幹大事了:

package com.partner4java.nospring.ioc;

import java.util.concurrent.TimeUnit;

public class BeanFactoryTest {

	public static void main(String[] args) throws InterruptedException {
		BeanFactory beanFactory = new BeanFactory("/boy2girl.properties");
		Girl baifumei = (Girl) beanFactory.getBean("baifumei");
		Girl luoli = (Girl) beanFactory.getBean("luoli");
		XiaoLong longge = (XiaoLong) beanFactory.getBean("longge");
		longge.setGirl(baifumei);
		longge.closeLight();
		System.out.println("休息下");
		TimeUnit.SECONDS.sleep(2);
		longge.setGirl(luoli);
		longge.closeLight();
	}

}

怎麼樣?我們這工廠。是不是想找什麼女人都可以了?

使用Spring簡單IoC:

•兩個例子:
•1、只是控制反轉
•2、使用了依賴注入

這倆簡單例子所用就是真實的Spring解析核心類,雖然我們平時不直接接觸,但是可以自己私下研究下

(我們還是藉助上面已經完成的男孩、女孩類 -- BeanFactory使用Spring提供的)

新建文件boy2girlspring1.properties:

baifumei.(class)=com.partner4java.nospring.ioc.Baifumei
luoli.(class)=com.partner4java.nospring.ioc.Luoli
longge.(class)=com.partner4java.nospring.ioc.XiaoLong

package com.partner4java.spring.ioc;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;

import com.partner4java.nospring.ioc.Girl;
import com.partner4java.nospring.ioc.XiaoLong;

public class HelloWorld1 {

	public static void main(String[] args) throws InterruptedException {
		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		BeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(
				beanFactory);
		beanDefinitionReader.loadBeanDefinitions("boy2girlspring1.properties");

		Girl baifumei = (Girl) beanFactory.getBean("baifumei");
		Girl luoli = (Girl) beanFactory.getBean("luoli");
		XiaoLong longge = (XiaoLong) beanFactory.getBean("longge");
		longge.setGirl(baifumei);
		longge.closeLight();
		System.out.println("休息下");
		TimeUnit.SECONDS.sleep(2);
		longge.setGirl(luoli);
		longge.closeLight();
	}

}
新建文件boy2girlspring2.properties:

baifumei.(class)=com.partner4java.nospring.ioc.Baifumei
luoli.(class)=com.partner4java.nospring.ioc.Luoli
longge1.(class)=com.partner4java.nospring.ioc.XiaoLong
longge2.(class)=com.partner4java.nospring.ioc.XiaoLong
longge1.girl(ref)=baifumei
longge2.girl(ref)=luoli

package com.partner4java.spring.ioc;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;

import com.partner4java.nospring.ioc.Boy;

public class HelloWorld2 {

	public static void main(String[] args) throws InterruptedException {
		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		BeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(beanFactory);
		beanDefinitionReader.loadBeanDefinitions("boy2girlspring2.properties");
		
		Boy longge1 = (Boy) beanFactory.getBean("longge1");
		longge1.closeLight();
		TimeUnit.SECONDS.sleep(2);
		Boy longge2 = (Boy) beanFactory.getBean("longge2");
		longge2.closeLight();
	}

}

基礎類:

•DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
•BeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(
•beanFactory);
•beanDefinitionReader.loadBeanDefinitions("boy2girlspring1.properties");

小結(白話):

•是不是感覺很爽啊?這樣,我們就可以簡單是使用“策略模式”等
•然後,我們就可以很容易的解耦,可以簡易使用設計模式核心思想“少用繼承、多用組合”(查看Spring的源碼,你也可以看到IoC無處不在)

第三步:再議IoC

再議Spring IoC,前面我們簡單的helloworld了下,先有個直觀的認識 下面我們繼續IoC (通過這幾個demo是不是對IoC有些直觀的認識了?)


控制反轉類型:

•依賴查找有兩種類型:依賴拖拽和上下文依賴查找(CDL)。
•依賴注入兩種類型:構造方法依賴注入和設置方法依賴注入。

依賴拖拽HelloWorld:

•注入的對象如何與組件發生聯繫,這個過程就是通過依賴拖拽實現。

上下文依賴查找(CDL):

•在某些方面跟依賴拖拽相似,但是上下文依賴查找中,查找的過程是在容器管理的資源中進行的,而不是從集中註冊表中,並且通常是作用在某些設置點上。


依賴注入方式:

•構造方法依賴注入:

在構造方法依賴注入中,組件的依賴從他們的構造方法中提供。組件聲明一個或一組構造方法,將他們的依賴作爲參數,控制反轉容器就會降依賴在組件實例化時傳給他。

•設置方法依賴注入:

在設置方法依賴注入中,控制反轉容器通過JavaBean風格的設置方法爲組件注入依賴。一個組件的設置方法向反轉容器公開一組依賴。


XmlBeanFactory:

•XmlBeanFactory源於DefaultListableBeanFactory且簡單的擴展了他,利用XmlBeanDefinitionReader進行自動配置
•現在這個類已經@Deprecated了
•代碼寫法和前面基本一樣,只是配置格式有很多需要學習的

我們前面利用Properties形式的配置改成XML格式:

在類路徑下創建文件:

/META-INF/spring/helloworld.xml

1、交給spring管理的基本單位<bean>;

2、一般需要一個標識id屬性來進行區分;

3、然後class指定<bean>標籤要交給Spring管理哪個類;

4、當你要利用依賴注入來實現某屬性的賦值,可利用<property>標籤;

5、<property>標籤的name屬性爲對應調用的POJO的set方法,ref爲一個bean的id(也就是傳入的賦值類 -- 此類也是Spring管理的)。

<?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:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

	<bean id="baifumei" class="com.partner4java.nospring.ioc.Baifumei" />
	<bean id="luoli" class="com.partner4java.nospring.ioc.Luoli" />
	<bean id="xiaoLong" class="com.partner4java.nospring.ioc.XiaoLong">
		<property name="girl" ref="baifumei" />
	</bean>

</beans>         


調用:

package com.partner4java.spring.ioc;


import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;


import com.partner4java.nospring.ioc.Boy;


public class XmlBeanFactoryDemo {


	public static void main(String[] args) {
		XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(
				"/META-INF/spring/helloworld.xml"));
		Boy xiaoLong = beanFactory.getBean("xiaoLong", Boy.class);
		xiaoLong.closeLight();
	}


}


叨叨兩句配置文件:

•首先都放到< beans></ beans>裏面
•然後聲明各種xmlns(XML命名空間)、 schemaLocation
•如果有多個xmlns,後面跟上特殊指定,如果不跟就是末尾不需指定(如:xmlns:tx=“http://www.springframework.org/schema/tx”)我們一般直接給bean了默認如: xmlns=“http://www.springframework.org/schema/beans”, 所以一般使用無需添加前綴


再來倆demo,構造器注入和set注入

public class Person {
	private String personName;
	private String address;

	public Person(String personName, String address) {
		super();
		this.personName = personName;
		this.address = address;
	}

	public void setPersonName(String personName) {
		this.personName = personName;
	}
....
創建文件:/META-INF/spring/constructor.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" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">
	<bean id="person" class="com.partner4java.spring.ioc.constructor.Person">
		<constructor-arg name="address" value="高鐵1號1號線"/>
		<constructor-arg name="personName" value="高鐵1號"/>
	</bean>
</beans>         
調用:
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/constructor.xml");
		System.out.println(applicationContext.getBean("person"));
	}

爲Bean配置集合:

•就是包括基本的List、Set、Map等,基本用法沒有什麼好說的
•但是,當你使用繼承的時候,可以使用<list merge="true">來在子類中聲明合併父類,但是會放在父類的序列之後

爲集合元素指定數據類型 :

•問題:
默認情況下,Spring將集合中所有元素作爲字符串對待。
如果你不打算將集合元素作爲字符串使用,就必須爲他們指定數據類型。
•解決方案:
可以使用<value>的type指定,也可以在集合標記中指定value-type

使用工廠Bean和Utility Schema定義集合 :

•問題:
使用基本集合標記定義集合時,你不能指定集合的實體類,例如LinkedList、TreeSet或TreeMap,而且,你不能通過將集合定義爲可供其他Bean引用的單獨Bean在不同的Bean中共享集合。
•解決方案:
兩種方式
1、使用對應的集合工廠Bean,如ListFactoryBean、SetFactoryBean和MapFactoryBean。
2、引入util schema中使用集合標記,如<util:list>、<util:set>和<util:map>。

bean的實例化模式:

•默認情況下,Spring中所有的bean均爲單例。也就是Spring只維護一個單獨的bean實例,所有的依賴對象都是用同一個實例,並且所有對BeanFactory.getBean(XXX)方法的調用都返回相同的實例。
•對此測試下:打印了true(要是你的bean裏有外部變量,你懂的,就等着掉坑吧)

非單例實例化模式:

•prototype:每次調用getBean()方法都返回一個bean的新實例。
•request:每次在Web應用中調用getBean()方法將爲每一個Http請求返回bean的唯一實例。此行爲只在WebApplicationContext和他的子接口中實現。
•session:每次調用getBean()方法將爲每個HttpSession返回bean的唯一實例。跟request一樣,此作用範圍只在WebApplicatoinContext和他的子接口中有效。
•globalsession:每次在portlet上下文中調用getBean()方法將爲全局HttpSession返回bean的唯一實例。跟request和session一樣,此實例化方式只被WebApplicationContext和他的子接口所支持。

選擇一種實例化模式:

•單例適用:
•無狀態的共享對象:當有一個無狀態且關聯很多依賴的對象時,使用單例。如果無狀態的差別,你就不需要做同步處理,當某個依賴對象在工作中需要使用這個bean時,你就不需要創建新的實例。
•只讀狀態共享對象:這跟前一點相似,但是有一些只讀的狀態,比如說一個只讀的屬性。這種情況下,你仍然不需要做同步處理,因此爲每一個請求創建bean的實例只會增加額外的開銷而已。
•共享狀態的共享對象:如果你有一個必須共享狀態的bean,單例便是理想選擇。這種情況下,你需要確保狀態寫的同步儘量原子化。
•具有可讀狀態的高吞吐量對象:如果某一個bean在你的應用程序中被大量的使用,你可能會發現保持單例並且對bean狀態的所有寫訪問進行同步會比持續的創建上百個bean實例具有更好的性能。使用此方法時,在不犧牲連貫性的前提下儘量保持同步的原子性。當應用程序在一個較大的時間跨度內創建了大量的實例,或者當你的共享對象只有少量可寫狀態,再或者當創建新實例花銷太大時,你會發現這個方法尤其有用。

使用非單例:

•具有可寫狀態的對象:如果你的bean有大量的可寫狀態,你會發現用以同步的成本比創建新實例來處理依賴對象的每一個請求的成本要高。
•具有私有狀態的對象:有時,依賴對象需要一個包含私有狀態的bean以便他們能夠同其他依賴此bean的對象區分開來獨立運行。在這樣的情況下,單例顯然是不合適的。

親,IoC知道是啥了麼? 下面倆小時我們來具體說下各種基本使用 begin…


用依賴檢查屬性:

•問題:
•在大規模的應用中,IoC容器中可能聲明瞭幾百個甚至幾千上萬個Bean,這些Bean之間的依賴往往非常複雜。
•設置方法注入的不足之一是無法確定一個屬性將會被注入。
•檢查所有必要的屬性是否已經設置是非常困難的。

解決方案(依賴檢查):

•dependency-check--
•none*不執行任何依賴檢查,任何屬性都可以保持未設置狀態
•simple如果任何簡單類型(原始和集合類型)的屬性未設置,將拋出UnsatisfiedDependencyException異常
•objects如果任何對象類型屬性沒有設置,將拋出UnsatisfiedDependencyException異常
•all如果任何類型的屬性爲設置,將拋出UnsatisfiedDependencyException異常
不過在新的版本中已經廢棄 ,以後遇到了面熟就行

用@Required註解檢查屬性:

•問題:
Spring的依賴檢查功能僅能檢查某些類型的所有屬性。他的靈活性不夠,不能僅檢查特定的屬性。
•解決方案:
RequiredAnnotationBeanPostProcessor是一個Spring bean後處理器,檢查帶有@Required註解的所有bean屬性是否設置。
bean後處理器是一類特殊的Spring bean,能夠在每個Bean初始化之前執行附加的工作。
爲了啓用這個Bean後處理器進行屬性檢查,必須在Spring IoC容器中註冊他。

(後處理器是什麼我們後面會講到)


檢查不合格後報錯:

Caused by: org.springframework.beans.factory.BeanInitializationException: Property 'username'is required for bean 'user'  


用法:
1、xml加上<context:annotation-config />
2、在你要檢測的字段上加上註解

package com.partner4java.spring.ioc.required;

import org.springframework.beans.factory.annotation.Required;

public class User {
	private String username;
	private String password;

	public User() {
		super();
	}

	public User(String username, String password) {
		super();
		this.username = username;
		this.password = password;
	}

	@Required
	public void setUsername(String username) {
		this.username = username;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "User [username=" + username + ", password=" + password + "]";
	}

}

<?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:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">
	<context:annotation-config />

	<bean id="user" class="com.partner4java.spring.ioc.required.User">
	</bean>
</beans>         

•調用:
		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		beanDefinitionReader.loadBeanDefinitions("/META-INF/spring/required.xml");
		System.out.println(beanFactory.getBean("user"));
•暈,以前面講的方式獲取Bean,咋操作正常,也沒報錯啊

(插曲)引入ApplicationContext:

•BeanFactory接口的實現,這個系列僅實現了容器的基本功能
•ApplicationContext應用上下文,他作爲容器的高級形態而存在
•具體可參照:

ApplicationContext的實現類:

•FileSystemXmlApplicationContext:可以在文件系統中應用程序有權訪問的任何位置加載。
•ClasspathXmlApplicationContext:可以從classpath上任何位置加載配置,這在你需要將配置文件跟一些類文件一起打包在一個JAR文件內時會很有用處。
•XmlWebApplicationContext:是專門在Web應用環境中使用的,通過使用ContextLoaderListener或者ContextLoaderServlet,你可以爲你的Web應用自動加載ApplicationContext配置。

那麼改一下上面的調用:

package com.partner4java.spring.ioc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class RequiredDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/required.xml");
		System.out.println(applicationContext.getBean("user"));
	}

}

這下是不是看到報錯了?

(插曲) ApplicationContext不多說(有興趣看前頁地址),記住就行了,他的Bean容器還是藉助了我們前面的DefaultListableBeanFactory,然後自己又加了一些高級特性。


使用ApplicationContextAware:

•一個bean可以實現BeanFactoryAware接口的方式獲得一個他所在BeanFactory的引用。同樣,也可以通過實現ApplicationContextAware接口獲得他在ApplicationContext的引用。


替換@Required:

•如果你有特殊語義的注意要替換@Required這個,只需要做兩步:
•1、定義一個註解
package com.partner4java.spring.ioc.required;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(value=RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NotNull {
	
}
修改POJO裏的註解:

	// @Required
	@NotNull
	public void setUsername(String username) {
		this.username = username;
	}

•2、注入給RequiredAnnotationBeanPostProcessor交給IoC
	<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor">
		<property name="requiredAnnotationType" value="com.partner4java.spring.ioc.required.NotNull"/>
	</bean>

這樣Spring就會自動掃描這個配置,知道你加了一個自定義註解

解析依賴:

•就是告訴BeanFactory解析某個bean需要依賴於另外一個bean的先解析
package com.partner4java.spring.ioc.dependson;

public class A {
	public A(){
		System.out.println("hello A");
	}
}



package com.partner4java.spring.ioc.dependson;

public class B {
	public B() {
		System.out.println("hello B");
	}
}

<?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:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

	<bean id="b" class="com.partner4java.spring.ioc.dependson.B" depends-on="a"/>
	<bean id="a" class="com.partner4java.spring.ioc.dependson.A"/>
</beans>         

來,自己寫一個Test加載一下,看看他們的打印順序。


解決構造程序歧義:

•問題:
當你爲Bean指定一個或者多個構造程序參數時,Spring將視圖在Bean類中查找對應的構造程序,並且傳遞用於Bean實例化的參數。
•解決方案:
你可以爲<constructor-arg>元素指定type和index屬性,幫助Spring查找預期的構造程序。

指定Bean引用:

•問題:
組成應用程序的Bean往往需要互相協作完成應用功能。爲了Bean之間的互相訪問,你必須在Bean配置文件中指定Bean引用。
•解決方案:
在Bean配置文件中,你可以用<ref>元素爲Bean屬性或者構造程序參數指定Bean引用。
只需要用<value>元素指定一個值。

自動裝配:

•有時候我們會感覺,這樣設置來設置去的會不會太麻煩啊?
•Spring難道不會智能點自己自動給我們塞個已有的對應值進去麼?

用XML配置自動裝配Bean:

•問題:
當一個Bean需要訪問另一個Bean時,你可以顯示指定引用裝配他。但是,如果你的容器能夠自動裝配bean,就可以免去手工手工配置裝配的麻煩。
•解決方案:
autowire屬性--
no* 不執行自動裝配。你必須顯示的裝配依賴
byName 對於每個Bean屬性,裝配一個同名的bean
byType 對於每個Bean屬性,裝配類型與之兼容的Bean。如果超過一個,將拋出UnsatisfiedDependencyException異常。
Constructor 對於每個構造程序參數,首先尋找與參數兼容的Bean。然後,選擇具有最多參數的構造程序。對於存在歧義的情況,將拋出UnsatisfiedDependencyException異常。
autoetect 如果找到一個沒有參數的默認構造程序,依賴講按照類型自動裝配。否則,將由構造程序自動裝配。
•還可以在beans裏設置全局類型:default-autowire="byName"

<?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:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">
	<bean id="student" class="com.partner4java.spring.ioc.autowire.Student">
		<constructor-arg name="name" value="小國"/>
		<!-- 此年齡只代表他的智商 -->
		<constructor-arg name="age" value="2"/>
	</bean>
	
	<bean id="teacher" class="com.partner4java.spring.ioc.autowire.Teacher" autowire="byName"/>
</beans>         
在我們的Teacher裏:

public class Teacher {
	private Student student;
	
	public void setStudent(Student student) {
		this.student = student;
	}

我們從Spring中獲取一下teacher會發現,student已經自動傳入。

用@Autowired和@Resource自動裝配Bean :

•問題:
在Bean配置文件中設置autowire屬性進行的自動裝配將裝配一個Bean的所有屬性。這樣的靈活性不足以緊緊裝配特定的屬性。
而且,你只能通過類型或者名稱自動裝配Bean。
如果這兩種策略都不能滿足你的需求,就必須明確的裝配Bean。
•解決方案:
你可以通過@Autowired或者@Resource註解一個設置方法、構造程序、字段甚至任意方法自動裝配特定的屬性。
這意味着你除了設置autowire屬性之外,還有一個能夠滿足需求的選擇。
•工作原理: 
配置文件加入:
<context:annotation-config /> 
然後利用如下其中一個註解:
@Autowired、@Qualifier("mainCatalog")、@Resource(name="myMovieFinder")
public class Teacher {
	@Resource(name="student")
	private Student student;
	
	public void setStudent(Student student) {
		this.student = student;
	}

現在試一下,看看效果。

@Autowired

@Autowired 註解可以用於“傳統的”setter 方法,如下例:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

這個註解也可以用於以屬性爲參數/多個參數的方法

public class MovieRecommender {

    private MovieCatalog movieCatalog;
    
    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

@Autowired註解甚至可以用於構造器與字段:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;
    
    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

也可以一種提供來自ApplicationContext的特殊類型的所有 beans,註解字段或者方法,例如:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

這同樣適用於集合類型:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;
    
    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

甚至是 Maps 也可以這樣註解,只要這個 Map 的 key 類型爲 String。這個 Map 的 values 應該是已知的類型,並且 keys 應該包含符合 bean 的命名:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;
    
    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

在缺省情況下,當出現0個候選的 beans時自動連接將失敗;缺省行爲把連接方法,構造器,字段假設爲required 的依賴。這樣的行爲如下所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required=false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

注意

雖然當 一個類只有一個連接構造器時它將被標記爲 required, 但是還是可以標記多個構造器的。在這種情況下,每一個構造器都有可能被認爲是連接構造器, Spring 將會把依賴關係能夠滿足的構造器認爲是greediest 的構造器。

@Autowired也能用於總所周知的“可解決的依賴”:BeanFactory接口,ApplicationContext接口,ResourceLoader接口,ApplicationEventPublisher接口,還有MessageSource接口。這些接口(還有它們的擴展,例如ConfigurableApplicationContext或者ResourcePatternResolver)將可以自動解決依賴,沒有任何特殊必須的其它步驟需要。

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

基於註解的自動連接微調

因爲通過類型的自動連接可能會有多個候選,因此經常需要在選擇過程中加以控制。一種方法去完成這個控制就是使用@Qualifier註解。在最簡單的情況下,您能夠通過命名方式去實現這個自動連接:

public class MovieRecommender {

    @Autowired
    @Qualifier("mainCatalog")
    private MovieCatalog movieCatalog;

    // ...
}

@Qualifier註解也能夠被指定爲構造器的參數或者方法的參數:

public class MovieRecommender {

    private MovieCatalog movieCatalog;
    
    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("mainCatalog") MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

@Resource

Spring 也提供了使用 JSR-250 bean 屬性支持的注射方式。這是一種在 Java EE 5 與 Java 6 中普遍使用的方式(例如,在 JSF 1.2 中映射 beans 或者 JAX-WS 2.0 端點),對於Spring 託管的對象 Spring 可以以這種方式支持映射。

@Resource有一個‘name’屬性,缺省時,Spring 將這個值解釋爲要注射的 bean 的名字。換句話說,如果遵循by-name的語法,如下例:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果沒有顯式地給出名字,缺省的名字將繼承於字段名或者 setter 方法名:如果是字段名,它將簡化或者等價於字段名;如果是 setter 方法名,它將等價於 bean 屬性名。下面這個例子使用名字 "movieFinder" 注射到它的 setter 方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

繼承Bean配置:

•問題:

    在Spring IoC容器中配置Bean時,你可能擁有超過一個共享某些公用配置的Bean,比如屬性和<bean>元素中的屬性。你常常必須爲多個Bean重複這些配置

•解決方案:
只作爲模板而不能檢索,必須將abstract設置爲true,要求spring不實例化這個bean。
並不是所有在父<bean>元素中定義的屬性都將被繼承,例如,autowire和dependency-check屬性不會從父bean中繼承

親,我還是感覺挺麻煩怎麼辦?還要配置好多XML


從Classpath中掃描組件:

•問題:
爲了便於Spring IoC容器對組件的管理,你需要在Bean配置中逐個聲明他們。
但是,如果Spring能夠自動地檢測你的組件而不需要手工配置,將大大節省你的工作量。
•解決方案:
Spring提供了一個強大的功能--組件掃描
•<context:component-scan base-package= 
第一步在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" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">
	<context:component-scan base-package="com.partner4java.spring.ioc.annotation" />
</beans>         
第二步:給自動掃描的文件加上註解
@Component\@Repository\@Service\@Controller:持久層、服務層和表現層。
•還要其他各種:@Scope、@Value
package com.partner4java.spring.ioc.annotation;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Baby {
	@Value("kiss")
	private String name;
	@Value("2")
	private int age;

	public void setName(String name) {
		this.name = name;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}

	@Override
	public String toString() {
		return "Baby [name=" + name + ", age=" + age + "]";
	}

}

package com.partner4java.spring.ioc.annotation;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;

@Component
public class Mother {
	@Resource(name = "baby")
	private Baby baby;

	public void setBaby(Baby baby) {
		this.baby = baby;
	}

	@Override
	public String toString() {
		return "Mother [baby=" + baby + "]";
	}

}
就可以用了:

package com.partner4java.spring.ioc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AnnotationDemo {

	public static void main(String[] args) {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/annotation.xml");
		System.out.println(applicationContext.getBean("baby"));
		System.out.println(applicationContext.getBean("mother"));
//		後臺打印:
//		Baby [name=kiss, age=2]
//	    Mother [baby=Baby [name=kiss, age=2]]
	}

}

註解詳解:

@Component和更多典型化註解

從Spring 2.0開始,引入了@Repository註解, 用它來標記充當儲存庫(又稱 Data Access Object或DAO)角色或典型的類。

Spring 2.5引入了更多典型化註解(stereotype annotations): @Component@Service@Controller@Component是所有受Spring管理組件的通用形式; 而@Repository@Service@Controller則是@Component的細化, 用來表示更具體的用例(例如,分別對應了持久化層、服務層和表現層)。也就是說, 你能用@Component來註解你的組件類, 但如果用@Repository@Service@Controller來註解它們,你的類也許能更好地被工具處理,或與切面進行關聯。 例如,這些典型化註解可以成爲理想的切入點目標。當然,在Spring Framework以後的版本中,@Repository@Service@Controller也許還能攜帶更多語義。如此一來,如果你正在考慮服務層中是該用@Component還是@Service, 那@Service顯然是更好的選擇。同樣的,就像前面說的那樣,@Repository已經能在持久化層中進行異常轉換時被作爲標記使用了。

自動檢測組件

Spring可以自動檢測“被典型化”(stereotyped)的類,在ApplicationContext 中註冊相應的BeanDefinition。例如,下面的這兩個類就滿足這種自動檢測的要求:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

要檢測這些類並註冊相應的bean,需要在XML中包含以下元素,其中'basePackage'是兩個類的公共父包 (或者可以用逗號分隔的列表來分別指定包含各個類的包)。

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">
               
     <context:component-scan base-package="org.example"/>
     
</beans>

此外,在使用組件掃描元素時,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor會隱式地被包括進來。 也就是說,連個組件都會被自動檢測織入 - 所有這一切都不需要在XML中提供任何bean配置元數據。

注意

通過加入值爲'false'的annotation-config屬性可以禁止註冊這些後置處理器。

使用過濾器自定義掃描

默認情況下,用@Component @Repository@Service @Controller (或本身使用了@Component註解的自定義註解) 註解的類是唯一會被檢測到的候選組件。但是可以很方便地通過自定義過濾器來改變並擴展這一行爲。 可以用'component-scan'的include-filterexclude-filter子元素來進行添加。 每個過濾器元素都要求有'type'和'expression'屬性。 下面給出了四個已有的可選過濾器。

表 3.7. 過濾器類型

過濾器類型 表達式範例
annotation

org.example.SomeAnnotation

assignable

org.example.SomeClass

regex

org\.example\.Default.*

aspectj

org.example..*Service+


下面這個XML配置會忽略所有的@Repository註解並用“stub”儲存庫代替。

<beans ...>

     <context:component-scan base-package="org.example">
        <context:include-filter type="regex" expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
     </context:component-scan>

</beans>

注意

你也可以用<component-scan/>元素的use-default-filters="false" 屬性來禁用默認的過濾器。這會關閉對使用了@Component@Repository@Service@Controller的類的自動檢測。


自動檢測組件的命名

當一個組件在某個掃描過程中被自動檢測到時,會根據那個掃描器的BeanNameGenerator 策略生成它的bean名稱。默認情況下,任何包含name值的Spring“典型”註解 (@Component@Repository@Service@Controller) 會把那個名字提供給相關的bean定義。如果這個註解不包含name值或是其他檢測到的組件 (比如被自定義過濾器發現的),默認bean名稱生成器會返回小寫開頭的非限定(non-qualified)類名。 例如,如果發現了下面這兩個組件,它們的名字會是'myMovieLister'和'movieFinderImpl':

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

注意

如果你不想使用默認bean命名策略,可以提供一個自定義的命名策略。首先實現 BeanNameGenerator 接口,確認包含了一個默認的無參數構造方法。然後在配置掃描器時提供一個全限定(fully-qualified)類名:

<beans ...>
               
     <context:component-scan base-package="org.example"
                             name-generator="org.example.MyNameGenerator" />

</beans>

作爲一條常規,當其他組件可能會顯式地引用一個組件時可以考慮用註解來指定名稱。 另一方面,當容器負責織入時,自動生成的名稱就足夠了。


爲自動檢測的組件提供一個作用域

通常受Spring管理的組件,默認或者最常用的作用域是“singleton”。然而,有時也會需要其他的作用域。 因此Spring 2.5還引入了一個新的@Scope註解。只要在註解中提供作用域的名稱就行了, 比方說:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

各種特色方式的Bean創建 (因爲有時候,我們的bean並不是 普通的形態):

使用Spring的FactoryBean創建bean:

•問題:
你可能希望用Spring的工廠Bean在Spring IoC容器中創建Bean。
工廠Bean(Factory Bean)是作爲創建IoC容器中其他Bean的工廠的一個FactoryBean。概念上,工廠Bean與工廠方法非常類似,但是他是Bean構造期間可以Spring IoC容器識別爲Spring專用Bean。
•解決方案:
工廠Bean的要求是實現FactoryBean接口。爲了方便,提供了抽象模板類AbstractFactoryBean供你擴展。
工廠Bean主要用於實現框架機制。如:
·在JNDI中查找對象(例如一個數據源)時,你可以使用JndiObjectFactoryBean。
·使用經典Spring AOP爲一個Bean創建代理時,可以使用ProxyFactoryBean。
·在IoC容器中創建一個Hibernate會話工廠時,可以使用LocalSessionFactoryBean。
•工作原理:
儘管你很少有必要編寫自定義的工廠Bean,但是會發現通過一個實例來理解其內部機制很有幫助。
通過擴展AbstractFactoryBean類,你的工廠bean能夠重載createInstance()方法以創建目標Bean實例。
此外,你必須getObjectType()方法中返回目標Bean的類型,是自動裝配(Auto-wiring)功能正常工作。
名稱之前添加&,可以得到工廠Bean的實例。
package com.partner4java.spring.ioc.factorybean;

import org.springframework.beans.factory.config.AbstractFactoryBean;

import com.partner4java.spring.ioc.required.User;

public class UserFactoryBean extends AbstractFactoryBean<User> {
	private String username;
	private String password;

	public UserFactoryBean(String username, String password) {
		super();
		this.username = username;
		this.password = password;
	}

	@Override
	public Class<?> getObjectType() {
		return User.class.getClass();
	}

	@Override
	protected User createInstance() throws Exception {
		return new User(username, password);
	}

}

	<bean id="userFactoryBean" class="com.partner4java.spring.ioc.factorybean.UserFactoryBean">
		<constructor-arg name="password" value="123" />
		<constructor-arg name="username" value="hello" />
	</bean>

		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/factorybean.xml");
		System.out.println(applicationContext.getBean("userFactoryBean"));
		System.out.println(applicationContext.getBean("&userFactoryBean"));

使用FactoryBean接口:

•當你無法使用new操作符來新建類對象,但又想將他們作爲Spring的bean來使用時,FactoryBean是一個完美的解決方案。
•FactoryBean接口聲明瞭3個方法:getObject、getObjectType和isSingleton。
•getObject:方法獲取由FactoryBean創建的對象。這是一個真正被傳給其他使用FactoryBean作爲協作者的bean的對象。
•getObjectType: 告訴Spring你的FactoryBean將返回什麼類型的對象。如果事先不知道返回什麼類型,將返回null。
•isSingleton:告訴Spring,正在被Spring管理的是否爲單例實例。
package com.partner4java.spring.ioc.factorybean;

import java.security.MessageDigest;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;

public class MessageDigestFactoryBean implements FactoryBean<MessageDigest>,
		InitializingBean {
	private MessageDigest messageDigest;
	private String algorithm;

	public void setAlgorithm(String algorithm) {
		this.algorithm = algorithm;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.messageDigest = MessageDigest.getInstance(algorithm);
	}

	@Override
	public MessageDigest getObject() throws Exception {
		return (MessageDigest) messageDigest.clone();
	}

	@Override
	public Class<?> getObjectType() {
		return messageDigest.getClass();
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

}

	<bean id="messageDigest" class="com.partner4java.spring.ioc.factorybean.MessageDigestFactoryBean">
		<property name="algorithm" value="MD5"/>
	</bean>

調用靜態工廠方法創建Bean:

•問題:  你打算調用一個靜態工廠方法在Spring IoC容器中創建一個Bean,靜態工廠方法的目的是在靜態方法中封裝對象創建過程。 
•解決方案:  Spring支持調用一個靜態工廠方法創建Bean,這個方法應該在factory-method屬性中指定。  
•工作原理:  factory-method 
public class ProductCreator {  
    public static Product createProduct(int productId){  
        if(1 == productId){  
            return new Product("xiaomei", 16);  
        }else if(2 == productId){  
            return new Product("xiaolang", 15);  
        }  
        throw new IllegalArgumentException("Unknown product");  
    }  
}  
  
<bean id="productCreator1" class="com.partner4java.spring.factorymethod.ProductCreator"  
    factory-method="createProduct">  
    <constructor-arg value="1" />  
</bean>  
  
<bean id="productCreator2" class="com.partner4java.spring.factorymethod.ProductCreator"  
    factory-method="createProduct">  
    <constructor-arg value="2" />  
</bean>  
  
@Test  
public void testFactoryMethod(){  
    System.out.println(applicationContext.getBean("productCreator1"));  
    System.out.println(applicationContext.getBean("productCreator2"));  
}  

調用一個實例工廠方法創建Bean:
•問題:  你打算調用一個實例工廠方法在Spring IoC容器中創建一個Bean,目的是在另一個對象實例的一個方法中封裝對象創建過程。  請求對象的客戶可以簡單地調用這個方法,不需要了解創建的細節。
•解決方案:  Spring支持調用實例工廠方法創建Bean。Bean實例在factory-bean屬性中指定,而工廠方法應該在factory-method屬性中指定。 
•工作原理:  factory-bean
package com.partner4java.spring.factorybean;  
  
import java.util.Map;  
  
import com.partner4java.spring.factorymethod.Product;  
  
public class ProductCreator {  
    private Map<String, Product> products;  
  
    public void setProducts(Map<String, Product> products) {  
        this.products = products;  
    }  
      
    public Product createProduct(String productId){  
        Product product = products.get(productId);  
        if(product != null){  
            return product;  
        }  
        throw new IllegalArgumentException("Unknown product");  
    }  
}  
  
<bean id="productCreator" class="com.partner4java.spring.factorybean.ProductCreator">  
    <property name="products">  
        <map>  
            <entry key="gaofumei">  
                <bean class="com.partner4java.spring.factorymethod.Product">  
                    <constructor-arg value="gaofumei" name="name"/>  
                    <constructor-arg value="100" name="price"/>  
                </bean>  
            </entry>  
            <entry key="xiaoneinv">  
                <bean class="com.partner4java.spring.factorymethod.Product">  
                    <constructor-arg value="xiaoneinv" name="name"/>  
                    <constructor-arg value="200" name="price"/>  
                </bean>  
            </entry>  
        </map>  
    </property>  
</bean>  
  
<bean id="gaofumei" factory-bean="productCreator" factory-method="createProduct">  
    <constructor-arg value="gaofumei"/>  
</bean>  
  
<bean id="xiaoneinv" factory-bean="productCreator" factory-method="createProduct">  
    <constructor-arg value="xiaoneinv"/>  
</bean>  
  
@Test  
public void testFactoryMethod(){  
    System.out.println(applicationContext.getBean("gaofumei"));  
    System.out.println(applicationContext.getBean("xiaoneinv"));  

從靜態字段中聲明bean:
•問題:  你打算從一個靜態字段中聲明Spring IoC容器中的一個Bean。在Java中,常量值往往聲明爲靜態字段。 
•解決方案:  爲了從靜態字段中聲明Bean,你可以使用內建的工廠Bean FieldRetrievingFactoryBean,或者Spring 2.X中的<util:contant>標記。
工作原理:  
public class ProductConstant {  
    public static Product gaofumei = new Product("gaofumei", 100);  
    public static Product xiaoneinv = new Product("xiaoneinv", 200);  
}  
  
<util:constant id="gaofumei"  
    static-field="com.partner4java.spring.constant.ProductConstant.gaofumei" />  
  
<util:constant id="xiaoneinv"  
    static-field="com.partner4java.spring.constant.ProductConstant.xiaoneinv" />  
  
@Test  
public void testFactoryMethod(){  
    System.out.println(applicationContext.getBean("gaofumei"));  
    System.out.println(applicationContext.getBean("xiaoneinv"));  
}  

從對象屬性中聲明bean:
•問題: 你打算從一個對象屬性或者嵌套的屬性(也就是屬性路徑)中聲明Spring IoC容器中的一個Bean。
•解決方案:爲了從一種對象屬性或者屬性路徑中聲明Bean,可以使用內建的工廠Bean PropertyPathFactoryBean或者Spring2.X中的<util:property-path>標記。
工作原理:  
public class ProductProperty {  
    private Product gaofumei;  
    private Product xiaonennv;  
      
    public Product getGaofumei() {  
        return gaofumei;  
    }  
    public void setGaofumei(Product gaofumei) {  
        this.gaofumei = gaofumei;  
    }  
    public Product getXiaonennv() {  
        return xiaonennv;  
    }  
    public void setXiaonennv(Product xiaonennv) {  
        this.xiaonennv = xiaonennv;  
    }  
      
}  
  
<bean id="productProperty" class="com.partner4java.spring.property.ProductProperty">  
    <property name="gaofumei">  
        <bean class="com.partner4java.spring.factorymethod.Product">  
            <constructor-arg name="name" value="gaofumei"/>  
            <constructor-arg name="price" value="100.1"/>  
        </bean>  
    </property>  
    <property name="xiaonennv">  
        <bean class="com.partner4java.spring.factorymethod.Product">  
            <constructor-arg name="name" value="xiaonennv"/>  
            <constructor-arg name="price" value="200.2"/>  
        </bean>  
    </property>  
</bean>  
  
<util:property-path id="gaofumei" path="productProperty.gaofumei"/>  
<util:property-path id="xiaonennv" path="productProperty.xiaonennv"/>  

好吧,親,感覺如何?好玩麼?


下面,我們來看下IoC的高級形態(其實也不是很高級)


Spring對應用程序可移植性的影響:

•不要憑空杜撰對可移植性的需求。在許多情況下,終端用戶並不關心你的應用程序是否能在3個不同的控制反轉容器上運行,而只要求他能穩定運行。

使Bean感知容器:

•問題:  一個精心設計的組件應該沒有對容器的直接依賴。  但是,有時候Bean有必要了解容器的資源。  
•解決方案:  Spring將通過一些接口定義的設置方法將對應資源注入到你的Bean中。  
•Spring中的常見感知接口  
•感知接口                目標資源  
•BeanNameAware      IoC容器中配置的實例的Bean名稱  
•BeanFactoryAware    當前的Bean工廠,通過它你可以調用容器的服務。  
•ApplicationContextAware*    當前應用上下文,通過他你可以調用容器的服務。  
•MessageSourceAware  消息資源,通過他可以解析文本消息。  
•ApplicationEventPublisherAware  應用事件發佈者,通過他你可以發佈應用事件。  
•ResourceLoaderAware 資源裝載器,通過他可以加載外部資源。
工作原理:  
public class HelloBeanNameAware implements BeanNameAware {  
    private String name;  
      
    @Override  
    public void setBeanName(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public String toString() {  
        return "HelloBeanNameAware [name=" + name + "]";  
    }  
}  
  
public class HelloBeanFactoryAware implements BeanFactoryAware {  
    private BeanFactory beanFactory;  
      
    @Override  
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {  
        this.beanFactory = beanFactory;  
    }  
  
    @Override  
    public String toString() {  
        return "HelloBeanFactoryAware [beanFactory=" + beanFactory.getClass().getSimpleName() + "]";  
    }  
  
}  
  
public class HelloApplicationContextAware implements ApplicationContextAware {  
    private ApplicationContext applicationContext;  
      
    @Override  
    public void setApplicationContext(ApplicationContext applicationContext)  
            throws BeansException {  
        this.applicationContext = applicationContext;  
    }  
  
    @Override  
    public String toString() {  
        return "HelloApplicationContextAware [applicationContext="  
                + applicationContext.getClass().getSimpleName() + "]";  
    }  
      
}  
  
  
<bean id="helloBeanNameAware" class="com.partner4java.spring.aware.HelloBeanNameAware" />  
  
<bean id="helloBeanFactoryAware" class="com.partner4java.spring.aware.HelloBeanFactoryAware" />  
  
<bean id="helloApplicationContextAware" class="com.partner4java.spring.aware.HelloApplicationContextAware" />  
  
@Test  
public void testAware() throws InterruptedException{  
    System.out.println(applicationContext.getBean("helloBeanNameAware"));  
    System.out.println(applicationContext.getBean("helloBeanFactoryAware"));  
    System.out.println(applicationContext.getBean("helloApplicationContextAware"));  
    // 後臺打印:  
    // HelloBeanNameAware [name=helloBeanNameAware]  
    // HelloBeanFactoryAware [beanFactory=DefaultListableBeanFactory]  
    // HelloApplicationContextAware  
    // [applicationContext=ClassPathXmlApplicationContext]  
}     

管理bean的生命週期:

•兩個生命週期時間與bean關係尤爲重要:
•postinitialization(初始化後)和predestruction(銷燬前)。
•注意:單例和非單例的bean加載方式不同,所以,初始化執行時間也會不同,lazy-init="true"還可以控制單例的加載機制
方式一:XML

•1、自己寫個初始化和銷燬的方法
package com.partner4java.spring.ioc.life;

public class SimpleBean {
	public void init(){
		System.out.println("SimpleBean init");
	}
	
	public void des(){
		System.out.println("SimpleBean des");
	}
}

•2、告訴XML
	<bean id="simpleBean" class="com.partner4java.spring.ioc.life.SimpleBean"
		init-method="init" destroy-method="des" lazy-init="true"/>

方式二:實現接口

實現InitializingBean接口:

•Spring中InitializingBean接口允許你在bean的代碼中這樣定義:你希望bean能接收到Spring已經完成對其配置的通知。就像使用初始化方法時一樣,你可以藉機檢查bean的配置以確保其有效,若要必要,還可以給出默認值。

決議順序:

•Spring首先調用InitializingBean.afterPropertiesSet()方法,然後再調用初始化方法。(建議如果存在順序,都寫入到afterPropertiesSet中調用。)

afterPropertiesSet方法和類的繼承:

•例子抽象父類實現了接口,但是把afterPropertiesSet定義成了final,不可被子類覆蓋,也就是去實現一些通用的初始化,然後再調用了自己定義的initSimple()初始化方法。

實現DisposableBean接口

•DisposableBean接口提供了destory方法

public class Work implements InitializingBean,DisposableBean {  
  
    @Override  
    public void destroy() throws Exception {  
        System.out.println("離職");  
    }  
      
    public void vork(){  
        System.out.println("working");  
    }  
  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        System.out.println("入職");  
    }  
  
}  
<bean id="work" class="com.partner4java.spring.initdes.Work"  
        scope="prototype" />  
  

創建Bean後處理器:

(具體可參照:http://blog.csdn.net/partner4java/article/details/6973782)

問題:  
你希望在Spring IoC容器中註冊自己的插件,在構造期間處理Bean實例。  
  
解決方案:  
Bean後處理器允許在初始化回調方法前後進行附加的Bean處理。  
Bean後處理器的主要特性是逐個處理IoC容器中的所有Bean實例,而不是單個Bean實例。  
一般,Bean後處理器用於檢查Bean屬性有效性,或者根據特殊條件修改Bean屬性。  
  
Bean後處理器的基本要求是實現BeanPostProcessor接口。  
你可以實現postProcessBeforeInitialization()和postProcessAfterInitialization()方法,在初始化回調方法前後處理所有Bean。  
然後,Spring將在調用初始化回調方法前後向這兩個方法傳遞每個Bean實例。  
步驟如下:  
1、構造程序或者工廠方法創建Bean實例。  
2、爲Bean屬性設置值和Bean引用。  
3、調用感知接口中定義的設置方法。  
4、將Bean實例傳遞給每個Bean前置處理器中的postProcessBeforeInitialization方法。  
5、調用初始化回調方法。  
6、講Bean實例傳遞給每個Bean後處理器中的postProcessAfterInitialization方法。  
7、Bean準備就緒,可以使用。  
8、容器關閉時,調用析構回調方法。  
使用Bean工廠爲IoC容器時,Bean後處理器只能編程註冊,更準確的講是通過addBeanPostProcessor()方法註冊。  
但是,如果你使用一個應用上下文,註冊將很簡單,只要在Bean配置文件中聲明處理器實例,他就會自動註冊。  
  
工作原理:  

public class LogBeanPostProcessor implements BeanPostProcessor {  
  
    @Override  
    public Object postProcessBeforeInitialization(Object bean, String beanName)  
            throws BeansException {  
        System.out.println(beanName + " say hello world!");  
        return bean;  
    }  
  
    @Override  
    public Object postProcessAfterInitialization(Object bean, String beanName)  
            throws BeansException {  
        System.out.println(beanName + " say good buy!");  
        return bean;  
    }  
  
}  
  
<!-- 要在應用上線文中註冊一個Bean後處理器,只要在Bean配置文件中聲明他的一個實例就可以了。 應用上下文能夠自動檢測誰實現了BeanPostProcessor接口,並且註冊他一處理容器中的所有其他Bean實例 -->  
<bean class="com.partner4java.spring.postprocess.LogBeanPostProcessor" />  
  
<bean id="helloResourceLoader" class="com.partner4java.spring.resource.HelloResourceLoader"  
    init-method="showResource" />  
          
如果以配置文件的格式設置init-method,對BeanPostProcesser的執行沒有什麼威脅,BeanPostProcesser還是會先執行。  
但是如果,以@PreDestroy和@PostConstruct的形式,BeanPostProcesser講不能正常工作,因爲BeanPostProcesser的默認優先級低於CommonAnnotationBeanPostProcesser。  
不過可以同時實現PriorityOrdered接口來指定執行順序。        

Spring BeanFactoryPostProcessor類 (“排隊”“後”控制修改beanfactory管理的信息--如一些配置信息)

http://blog.csdn.net/partner4java/article/details/6969417

(比較晚了,明早還有事,後面可能整理的有點亂,改天再修改下)


《partner4java 講述Spring入門》之第一步:Spring概述與Spring IoC
http://blog.csdn.net/partner4java/article/details/8194747

《partner4java 講述Spring入門》之第二步:Spring AOP
http://blog.csdn.net/partner4java/article/details/8239721


發佈了183 篇原創文章 · 獲贊 20 · 訪問量 80萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章