摘要
本章內(nèi)容:
1.Spring的bean容器
2.介紹Spring的核心模塊
3.更為強(qiáng)大的Spring生態(tài)系統(tǒng)
4.Spring的新功能
1.1 簡(jiǎn)化Java開(kāi)發(fā)
Spring是為了解決企業(yè)級(jí)應(yīng)用開(kāi)發(fā)的復(fù)雜性而創(chuàng)建的租冠,使用 Spring 可以讓簡(jiǎn)單的 JavaBean 實(shí)現(xiàn)之前只有 EJB 才能完成的事情。但 Spring 不僅僅局限于服務(wù)器端開(kāi)發(fā),任何 Java 應(yīng)用都能在簡(jiǎn)單性、可測(cè)試性和松耦合等方面從 Spring 中獲益。
為了降低Java開(kāi)發(fā)的復(fù)雜性休涤,Spring采取了以下4種關(guān)鍵策略:
1.基于POJO的輕量級(jí)和最小侵入性編程他炊;
2.通過(guò)依賴注入和面向接口實(shí)現(xiàn)松耦合搭幻;
3.基于切面和慣例進(jìn)行聲明式編程循狰;
4.通過(guò)切面和模板減少樣板式代碼。
Spring竭力避免因自身的API而弄亂你的應(yīng)用代碼券勺。Spring不會(huì)強(qiáng)迫你實(shí)現(xiàn)Spring規(guī)范的接口或繼承Spring規(guī)范的類绪钥,相反,在基于Spring構(gòu)建的應(yīng)用中关炼,它的類通常沒(méi)有任何痕跡表明你使用了Spring程腹。最壞的場(chǎng)景是,一個(gè)類或許會(huì)使用Spring注解儒拂,但它依舊是POJO寸潦。
程序清單1.1Spring不會(huì)在HelloWorldBean上有任何不合理的要求
package com.habuma.spring;
public class HelloWorldBean{
public String sayHello(){
return "Hello World";
}
}
1.1.2 依賴注入
任何一個(gè)有實(shí)際意義的應(yīng)用(肯定比Hello World示例更復(fù)雜)都會(huì)由兩個(gè)或者更多的類組成,這些類相互之間進(jìn)行協(xié)作來(lái)完成特定的業(yè)務(wù)邏
輯社痛。按照傳統(tǒng)的做法见转,每個(gè)對(duì)象負(fù)責(zé)管理與自己相互協(xié)作的對(duì)象(即它所依賴的對(duì)象)的引用,這將會(huì)導(dǎo)致高度耦合和難以測(cè)試的代碼蒜哀。
通過(guò)DI斩箫,對(duì)象的依賴關(guān)系將由系統(tǒng)中負(fù)責(zé)協(xié)調(diào)各對(duì)象的第三方組件在創(chuàng)建對(duì)象的時(shí)候進(jìn)行設(shè)定。對(duì)象無(wú)需自行創(chuàng)建或管理它們的依賴關(guān)系撵儿,依賴關(guān)系將被自動(dòng)注入到需要它們的對(duì)象當(dāng)中去乘客。
看下面這個(gè)例子:
package com.springination.knights;
public class BraveKnight implements Knight{
private Quest quest;
public BraveKnight(Quest quest){ //Quest 被注入進(jìn)來(lái)
this.quest = quest;
}
public void embarkOnQuest(){
quest.embark();
}
}
BraveKnight沒(méi)有自行創(chuàng)建探險(xiǎn)任務(wù),而是在構(gòu)造的時(shí)候把探險(xiǎn)任務(wù)作為構(gòu)造器參數(shù)傳入淀歇。這是依賴注入的方式之一易核,即構(gòu)造器注入。
為了測(cè)試BraveKnight浪默,需要注入一個(gè)mock Quest:
package com.springination.knights;
import static org.mockito.Mockito.*;
import org.junit.Test;
public class BraveKnightTest{
@Test
public void knightShouldEmbarkOnQuest(){
Quest mockQuest = mock(Quest.class);//創(chuàng)建 mock Quest
BraveKnight knight = new BraveKnight(mockQuest); //注入 mock Quest
knight.embarkOnQuest();
verify(mockQuest,times(1)).embark();
}
}
你可以使用mock框架Mockito去創(chuàng)建一個(gè)Quest接口的mock實(shí)現(xiàn)牡直。通過(guò)這個(gè)mock對(duì)象,就可以創(chuàng)建一個(gè)新的BraveKnight實(shí)例浴鸿,并通過(guò)構(gòu)造器注入這個(gè)mock Quest井氢。
創(chuàng)建應(yīng)用組件之間協(xié)作的行為通常稱為裝配。Spring有多種裝配bean的方式岳链,采用XML是很常見(jiàn)的一種裝配方式花竞。
使用Spring將SlayDragonQuest注入到BraveKnight中:
<?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="knight" class="com.springframework.knight.BraveKnight">
<constructor-arg reg="quest"/>
</bean>
<bean id="quest" class="com.springination.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}"/>
</bean>
</beans>
Spring還支持使用Java來(lái)描述配置:
package com.springination.knights.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.springinaction.Knights.BraveKnight;
import com.springinaction.Knights.Knight;
import com.springinaction.Knights.Quest;
import com.springinaction.Knights.SlayDragonQuest;
public class KnightConfig {
@Configuration
public class KnightConfig{
@Bean
public Knight knight() {
return new BraveKnight(quest());
}
@Bean
public Quest quest() {
return new SlayDragonQuest(System.out);
}
}
}
Spring通過(guò)應(yīng)用上下文(Application Context)裝載bean的定義并把它們組裝起來(lái)。Spring應(yīng)用上下文全權(quán)負(fù)責(zé)對(duì)象的創(chuàng)建和組裝。Spring自帶
了多種應(yīng)用上下文的實(shí)現(xiàn)约急,它們之間主要的區(qū)別僅僅在于如何加載配置零远。
KnightMain.java加載包含Knight的Spring上下文:
package com.springinaction.knights;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class KnightMain{
public static void main(String[] args) throws Exception{
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring/knights.xml");
Knight knight = context.getBean(Knight.class);
knight.embarkOnQuest();
context.close();
}
}
1.1.3 應(yīng)用切面
DI能夠讓相互協(xié)作的軟件組件保持松散耦合,而面向切面編程(AOP)允許你把遍布應(yīng)用各處的功能分離出來(lái)形成可重用的組件厌蔽。
先來(lái)看下面這張圖:
AOP能夠使這些服務(wù)模塊化牵辣,并以聲明的方式將它們應(yīng)用到它們需要影響的組件中去。所造成的結(jié)果就是這些組件會(huì)具有更高的內(nèi)聚性并且會(huì)更加關(guān)注自身的業(yè)務(wù)奴饮,完全不需要了解涉及系統(tǒng)服務(wù)所帶來(lái)復(fù)雜性纬向。總之戴卜,AOP能夠確保POJO的簡(jiǎn)單性逾条。
AOP 應(yīng)用:
下面程序展示了,吟游詩(shī)人吟唱騎士的英勇事跡:
package com.springinaction.knights;
import java.io.PrintStream;
public class Minstrel{
private PrintStream stream;
public Minstrel(PrintStream stream) {
this.stream = stream;
}
public void singBeforeQuest() {
stream.println("Fa la la, the knight is so brave!");
}
public void singAfterQuest() {
stream.println("Tee hee hee, the brave knight" + "did embark on a quest!");
}
}
package com.springinaction.knights;
public class BraveKnight implements Knight{
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuest() throws QuestException{
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
這樣應(yīng)該能達(dá)到效果投剥,在騎士行動(dòng)前后师脂,詩(shī)人歌頌騎士。但是詩(shī)人應(yīng)該是獨(dú)立的個(gè)體江锨,但是這個(gè)程序復(fù)雜化了吃警,這個(gè)騎士居然去管理詩(shī)人,其實(shí)跟詩(shī)人應(yīng)該是沒(méi)有關(guān)系的啄育。
所以下面要將詩(shī)人聲明為一個(gè)切面:
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www/springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springination.knights.BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="com.springination.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}"/>
</bean>
<bean id="minstrel" class="com.springination.knights.Minstrel"> //聲明 Minstrel bean
<constructor-arg value="#{T(System).out}"/>
</bean>
<aop:config>
<aop:aspect ref="minstrel">
<aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/> //定義切點(diǎn)
<aop:before pointcut-ref="embark" method="singBeforeQuest"/>
<aop:after pointcut-ref="embark" method="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>
使用模板消除樣板式代碼
許多Java API酌心,例如JDBC,會(huì)涉及編寫大量的樣板式代碼灸撰。
public Employee getEmployeeById(long id){
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try{
conn = dataSource.getConnection();
stmt = conn.prepareStatement("select id, firstname,lastname,salary from" +
"employee where id =?");
stmt.setLong(1,id);
rs = stmt.executeQuery();
Employee employee = null;
if(rs.next()){
employee = new Employee();
employee.setId(rs.getLong("id"));
employee.setFirstName(rs.getString("firstname"));
employee.setLastName(rs.getString("lastname"));
employee.setSalary(rs.getBigDecimal("salary"));
}
return employee;
}catch (SQLException e){
}finally {
if(rs != null){
try{
stmt.close();
}catch(SQLException e){}
}
if(conn != null){
try{
conn.close();
}catch(SQLException e){}
}
}
return null;
}
這段代碼很繁瑣谒府,就是一個(gè)查詢數(shù)據(jù)庫(kù)的功能要寫很多重復(fù)的代碼,很多重復(fù)的異常處理浮毯。
使用Spring的JdbcTemplate(利用了 Java 5特性的JdbcTemplate實(shí)現(xiàn))重寫的getEmployeeById()方法僅僅關(guān)注于獲取員工數(shù)據(jù)的核心邏輯完疫,而不需要迎合JDBC API的需求。
public Employee getEmployeeById(long id){
return jdbcTemplate.queryForObject("select id, firstname, lastname, salary" +
"from employee where id=?",
new RowMapper<Employee>(){
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException{
Employee employee = new Employee();
employee.setId(rs.getLong("id"));
employee.setFirstName(rs.getString("firstname"));
employee.setLastName(rs.getString("lastname"));
employee.setSalary(rs.getBigDecimal("salary"));
return employee;
}
},
id);
}
1.2 容納你的Bean
在基于Spring的應(yīng)用中债蓝,你的應(yīng)用對(duì)象生存于Spring容器中壳鹤。Spring容器負(fù)責(zé)創(chuàng)建對(duì)象,裝配它們饰迹,配置它們并管理它們的整個(gè)生命周期芳誓,從生存到死亡(在這里,可能就是new到finalize())啊鸭。
Spring自帶了多個(gè)容器實(shí)現(xiàn)锹淌,可以歸為兩種不同的類型。bean工廠(由org.springframework. beans.factory.eanFactory接口定義)是最簡(jiǎn)單的容器赠制,提供基本的DI支持赂摆。應(yīng)用上下文(由org.springframework.context.ApplicationContext接口定義)基于BeanFactory構(gòu)建,并提供應(yīng)用框架級(jí)別的服務(wù),例如從屬性文件解析文本信息以及發(fā)布應(yīng)用事件給感興趣的事件監(jiān)聽(tīng)者烟号。
Spring自帶了多種類型的應(yīng)用上下文绊谭。下面羅列的幾個(gè)是你最有可能
遇到的。
1.AnnotationConfigApplicationContext:從一個(gè)或多個(gè)基于Java的配置類中加載Spring應(yīng)用上下文汪拥。
2.AnnotationConfigWebApplicationContext:從一個(gè)或多個(gè)基于Java的配置類中加載Spring Web應(yīng)用上下文达传。
3.ClassPathXmlApplicationContext:從類路徑下的一個(gè)或多個(gè)XML配置文件中加載上下文定義,把應(yīng)用上下文的定義文件作為類資源迫筑。
4.FileSystemXmlapplicationcontext:從文件系統(tǒng)下的一個(gè)或多個(gè)XML配置文件中加載上下文定義宪赶。
5.XmlWebApplicationContext:從Web應(yīng)用下的一個(gè)或多個(gè)XML配置文件中加載上下文定義。
bean的生命周期
在傳統(tǒng)的Java應(yīng)用中脯燃,bean的生命周期很簡(jiǎn)單逊朽。使用Java關(guān)鍵字new進(jìn)行bean實(shí)例化,然后該bean就可以使用了曲伊。一旦該bean不再被使用,則由Java自動(dòng)進(jìn)行垃圾回收追他。
相比之下坟募,Spring容器中的bean的生命周期就顯得相對(duì)復(fù)雜多了。
在bean準(zhǔn)備就緒之前邑狸,bean工廠執(zhí)行了若干啟動(dòng)步驟:
1.Spring對(duì)bean進(jìn)行實(shí)例化懈糯;
2.Spring將值和bean的引用注入到bean對(duì)應(yīng)的屬性中;
3.如果bean實(shí)現(xiàn)了BeanNameAware接口单雾,Spring將bean的ID傳遞給setBean-Name()方法赚哗;
4.如果bean實(shí)現(xiàn)了BeanFactoryAware接口,Spring將調(diào)用setBeanFactory()方法硅堆,將BeanFactory容器實(shí)例傳入屿储;
5.如果bean實(shí)現(xiàn)了ApplicationContextAware接口,Spring將調(diào)用setApplicationContext()方法渐逃,將bean所在的應(yīng)用上下文的引用傳入進(jìn)來(lái)够掠;
6.如果bean實(shí)現(xiàn)了BeanPostProcessor接口,Spring將調(diào)用它們的post-ProcessBeforeInitialization()方法茄菊;
7.如果bean實(shí)現(xiàn)了InitializingBean接口疯潭,Spring將調(diào)用它們的after-PropertiesSet()方法。類似地面殖,如果bean使用initmethod聲明了初始化方法竖哩,該方法也會(huì)被調(diào)用;
8.如果bean實(shí)現(xiàn)了BeanPostProcessor接口脊僚,Spring將調(diào)用它們的post-ProcessAfterInitialization()方法相叁;
9.此時(shí),bean已經(jīng)準(zhǔn)備就緒,可以被應(yīng)用程序使用了钝荡,它們將一直駐留在應(yīng)用上下文中街立,直到該應(yīng)用上下文被銷毀;
10.如果bean實(shí)現(xiàn)了DisposableBean接口埠通,Spring將調(diào)用它的destroy()接口方法赎离。同樣,如果bean使用destroy-method聲明了銷毀方法端辱,該方法也會(huì)被調(diào)用梁剔。
Spring模塊
總結(jié)
Spring致力于簡(jiǎn)化企業(yè)級(jí)Java開(kāi)發(fā),促進(jìn)代碼的松散耦合舞蔽。成功的關(guān)鍵在于依賴注入和AOP荣病。