《Spring入門經典》這本書不管對於初學者或者有經驗的工程師仍是很值一看的,最近花了點時間回顧了Spring的內容,在此順帶記錄一下,本篇主要與spring IOC相關 ,這篇博文適合初學者也適合spring有過開發經驗的工程師,前者可用於全面瞭解Spring IOC的知識點,後者且過目進而查漏補缺,ok~,如下是主要內容:java
在咱們的平常開發中,建立對象的操做隨處可見以致於對其十分熟悉的同時又感受十分繁瑣,每次須要對象都須要親手將其new出來,甚至某些狀況下因爲壞編程習慣還會形成對象沒法被回收,這是至關糟糕的。但更爲嚴重的是,咱們一直倡導的鬆耦合,少入侵原則,這種狀況下變得一無可取。因而前輩們開始謀求改變這種編程陋習,考慮如何使用編碼更加解耦合,由此而來的解決方案是面向接口的編程,因而便有了以下寫法:mysql
/** * Created by zejian on 2017/1/15. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ public class BookServiceImpl { //class private BookDaoImpl bookDaoImpl; public void oldCode(){ //原來的作法 bookDaoImpl=new bookDaoImpl(); bookDaoImpl.getAllCategories(); } } //=================new==================== public class BookServiceImpl { //interface private BookDao bookDao; public void newCode(){ //變爲面向接口編程 bookDao=new bookDaoImpl(); bookDao.getAllCategories(); } }
BookServiceImpl類中由原來直接與BookDaoImp打交互變爲BookDao,即便BookDao最終實現依然是BookDaoImp,這樣的作的好處是顯而易見的,全部調用都經過接口bookDao來完成,而接口的真正的實現者和最終的執行者就是bookDaoImpl,當替換bookDaoImpl類,也只需修改bookDao指向新的實現類。web
雖然上述的代碼在很大程度上下降了代碼的耦合度,可是代碼依舊存在入侵性和必定程度的耦合性,好比在修改bookDao的實現類時,仍然需求修改BookServiceImpl的內部代碼,當依賴的類多起來時,查找和修改的過程也會顯得至關糟糕,所以咱們仍須要尋找一種方式,它能夠令開發者在無需觸及BookServiceImpl內容代碼的狀況下實現修改bookDao的實現類,以便達到最低的耦合度和最少入侵的目的。實際上存在一種稱爲反射的編程技術能夠協助解決上述問題,反射是一種根據給出的完整類名(字符串方式)來動態地生成對象,這種編程方式可讓對象在生成時才決定究竟是哪種對象,所以能夠這樣假設,在某個配置文件,該文件已寫好bookDaoImpl類的徹底限定名稱,經過讀取該文件而獲取到bookDao的真正實現類徹底限定名稱,而後經過反射技術在運行時動態生成該類,最終賦值給bookDao接口,也就解決了剛纔的存在問題,這裏爲簡單演示,使用properties文件做爲配置文件,className.properties以下:spring
bookDao.name=com.zejian.spring.dao.BookDaoImpl
獲取該配置文件信息動態爲bookDao生成實現類:sql
public class BookServiceImpl implements BookService { //讀取配置文件的工具類 PropertiesUtil propertiesUtil=new PropertiesUtil("conf/className.properties"); private BookDao bookDao; public void DaymicObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException { //獲取徹底限定名稱 String className=propertiesUtil.get("bookDao.name"); //經過反射 Class c=Class.forName(className); //動態生成實例對象 bookDao= (BookDao) c.newInstance(); } }
的確如咱們所願生成了bookDao的實例,這樣作的好處是在替換bookDao實現類的狀況只需修改配置文件的內容而無需觸及BookServiceImpl的內部代碼,從而把代碼修改的過程轉到配置文件中,至關於BookServiceImpl及其內部的bookDao經過配置文件與bookDao的實現類進行關聯,這樣BookServiceImpl與bookDao的實現類間也就實現瞭解耦合,固然BookServiceImpl類中存在着BookDao對象是沒法避免的,畢竟這是協同工做的基礎,咱們只能最大程度去解耦合。編程
瞭解了上述的問題再來理解IOC就顯得簡單多了。Spring IOC 也是一個java對象,在某些特定的時間被建立後,能夠進行對其餘對象的控制,包括初始化、建立、銷燬等。簡單地理解,在上述過程當中,咱們經過配置文件配置了BookDaoImpl實現類的徹底限定名稱,而後利用反射在運行時爲BookDao建立實際實現類,包括BookServiceImpl的建立,Spring的IOC容器都會幫咱們完成,而咱們惟一要作的就是把須要建立的類和其餘類依賴的類以配置文件的方式告訴IOC容器須要建立那些類和注入哪些類便可。Spring經過這種控制反轉(IoC)的設計模式促進了鬆耦合,這種方式使一個對象依賴其它對象時會經過被動的方式傳送進來(如BookServiceImpl被建立時,其依賴的BookDao的實現類也會同時被注入BookServiceImpl中),而不是經過手動建立這些類。咱們能夠把IoC模式看作是工廠模式的昇華,能夠把IoC看做是一個大工廠,只不過這個大工廠裏要生成的對象都是在配置文件(XML)中給出定義的,而後利用Java的反射技術,根據XML中給出的類名生成相應的對象。從某種程度上來講,IoC至關於把在工廠方法裏經過硬編碼建立對象的代碼,改變爲由XML文件來定義,也就是把工廠和對象生成這二者獨立分隔開來,目的就是提升靈活性和可維護性,更是達到最低的耦合度,所以咱們要明白所謂爲的IOC就將對象的建立權,交由Spring完成,今後解放手動建立對象的過程,同時讓類與類間的關係到達最低耦合度。設計模式
理解了Spring IOC模式(容器)後,咱們來看一個簡單入門實例。使用Spring的IOC功能,必須先引入Spring的核心依賴包(使用Maven做爲構建工具):數組
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency>
而後建立Dao層(AccountDao):瀏覽器
public interface AccountDao { void addAccount(); }
實現類(AccountDaoImpl):spring-mvc
public class AccountDaoImpl implements AccountDao{ @Override public void addAccount() { System.out.println("addAccount...."); } }
再建立Service,AccountService
/** * Created by zejian on 2017/1/15. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ public interface AccountService { void doSomething(); }
實現類:
/** * Created by zejian on 2017/1/15. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ public class AccountServiceImpl implements AccountService { /** * 須要注入的對象 */ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void doSomething() { System.out.println("AccountServiceImpl#doSomething......"); accountDao.addAccount(); } }
上面咱們建立了Dao層和Service層的接口類及其實現類,其中Service層的操做依賴於Dao層,下面經過Spring的IOC容器幫助咱們建立並注入這些類。IOC使用的是XML配置文件,代碼以下:
<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 "> <!-- 聲明accountDao對象,交給spring建立 --> <bean name="accountDao" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- 聲明accountService對象,交給spring建立 --> <bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl"> <!-- 注入accountDao對象,須要set方法--> <property name="accountDao" ref="accountDao"/> </bean> </beans>
從xml文件中,咱們須要聲明一個beans的頂級標籤,同時須要引入核心命名空間,Spring的功能在使用時都須要聲明相對應的命名空間,上述的命名空間是最基本的。而後經過bean子標籤聲明那些須要IOC容器幫助咱們建立的類,其中name是指明IOC建立後該對象的名稱(固然也可使用id替換name,這個後面會講到),class則是告訴IOC這個類的徹底限定名稱,IOC就會經過這組信息利用反射技術幫助咱們建立對應的類對象,以下:
<!-- 聲明accountDao對象,交給spring建立 --> <bean name="accountDao" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
接着咱們還看到以下聲明,accountService聲明中多出了一個property的標籤,這個標籤指向了咱們剛纔建立的accountDao對象,它的做用是把accountDao對象傳遞給accountService實現類中的accountDao屬性,該屬性必須擁有set方法才能注入成功,咱們把這種往類accountService對象中注入其餘對象(accountDao)的操做稱爲依賴注入,這個後面會分析到,其中的name必須與AccountService實現類中變量名稱相同,到此咱們就完成對須要建立的對象聲明。接着看看如何使用它們。
<!-- 聲明accountService對象,交給spring建立 --> <bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl"> <!-- 注入accountDao對象,須要set方法--> <property name="accountDao" ref="accountDao"/> </bean> public class AccountServiceImpl implements AccountService { /** * 須要注入的對象 */ private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } }
使用這些類須要利用Spring提供的核心類,ApplicationContext,經過該類去加載已聲明好的配置文件,而後即可以獲取到咱們須要的類了。
@Test public void testByXml() throws Exception { //加載配置文件 ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml"); // AccountService accountService=applicationContext.getBean("accountService",AccountService.class); //屢次獲取並不會建立多個accountService對象,由於spring默認建立是單實例的做用域 AccountService accountService= (AccountService) applicationContext.getBean("accountService"); accountService.doSomething(); }
經過這個簡單的案例相信你們已大概理解了Spring IOC的主要是幹什麼的了吧,接下來就詳細分析它,上述的運行結果:
有了大概瞭解後,先分析一下前面的案例,咱們採用xml配置文件的方式對bean進行聲明和管理,每個bean標籤都表明着須要被建立的對象並經過property標籤能夠爲該類注入其餘依賴對象(關於依賴後面會分析,這裏簡單理解爲將對象實例傳入到某個bean實例便可),經過這種方式Spring容器就能夠成功知道咱們須要建立那些bean實例,以下:
<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 "> <!-- 聲明accountDao對象,交給spring建立 --> <bean name="accountDao" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- 聲明accountService對象,交給spring建立 --> <bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl"> <!-- 注入accountDao對象,須要set方法--> <property name="accountDao" ref="accountDao"/> </bean> </beans>
而後經過ClassPathXmlApplicationContext去加載spring的配置文件,接着獲取想要的實例bean並調用相應方法執行。對於ClassPathXmlApplicationContext默認加載classpath路徑下的文件,只需指明對應文件的classpath路徑便可。若是存在多個配置文件,則只需分別傳遞便可,ClassPathXmlApplicationContext是一個能夠接收可變參數的構造函數。實際上ClassPathXmlApplicationContext還有一個孿生兄弟FileSystemXmlApplicationContext,它默認爲項目工做路徑 即項目的根目錄 ,至於使用哪一個,博主以爲沒多大的差異。
//默認查找classpath路徑下的文件 ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml"); //多文件,也可傳遞數組 ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml","spring/spring-ioc2.xml",.....); //默認爲項目工做路徑 即項目的根目錄 FileSystemXmlApplicationContext applicationContext= new FileSystemXmlApplicationContext("/src/main/resources/spring/spring-ioc.xml"); //也能夠讀取classpath下的文件 FileSystemXmlApplicationContext applicationContext=new FileSystemXmlApplicationContext("classpath:spring/spring-ioc.xml"); //使用前綴file 表示的是文件的絕對路徑 ApplicationContext applicationContext = new FileSystemXmlApplicationContext("file:D:/app.spring.xml"); //多文件與ClassPathXmlApplicationContext相同
ok~,瞭解完如何加載配置文件後,下面將採用註解的方式來達到上述xml的配置的效果:
package com.zejian.spring.springIoc.conf; import com.zejian.spring.springIoc.dao.AccountDao; import com.zejian.spring.springIoc.dao.impl.AccountDaoImpl; import com.zejian.spring.springIoc.service.AccountService; import com.zejian.spring.springIoc.service.impl.AccountServiceImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Created by zejian on 2017/1/15. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ @Configuration public class BeanConfiguration { @Bean public AccountDao accountDao(){ return new AccountDaoImpl(); } @Bean public AccountService accountService(){ AccountServiceImpl bean=new AccountServiceImpl(); //注入dao bean.setAccountDao(accountDao()); return bean; } }
上述代碼中使用了@Configuration註解標明BeanConfiguration類,使得BeanConfiguration類替代了xml文件,也就是說註解@Configuration等價於<beans>標籤。在該類中,每一個使用註解@Bean的公共方法對應着一個<bean>標籤的定義,即@Bean等價於<bean>標籤。這種基於java的註解配置方式是在spring3.0中引入的,此時使用到註解,所以必須確保spring-context包已引入。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency>
那怎麼使用呢?事實上跟xml很類似,不過這是使用到AnnotationConfigApplicationContext來加載java的配置文件,運行結果跟xml同樣。
@Test public void testByConfigurationAnnotation() throws Exception { AnnotationConfigApplicationContext config=new AnnotationConfigApplicationContext(BeanConfiguration.class); //名稱必須BeanConfiguration中工程方法名稱一致 AccountService accountService= (AccountService) config.getBean("accountService"); accountService.doSomething(); }
這裏咱們須要明白的是,在大部分狀況下更傾向於使用xml來配置Bean的相關信息,這樣會更加方便咱們對代碼進行管理,所以後面的分析都會基於xml的配置,除了前面經過在xml中使用<bean>標籤爲每一個類聲明實例外,Spring容器還爲咱們提供了基於註解的聲明方式,這點放在後面分析,接着再來解Spring提供的依賴注入功能。
所謂的依賴注入,實際上是當一個bean實例引用到了另一個bean實例時spring容器幫助咱們建立依賴bean實例並注入(傳遞)到另外一個bean中,如上述案例中的AccountService依賴於AccountDao,Spring容器會在建立AccountService的實現類和AccountDao的實現類後,把AccountDao的實現類注入AccountService實例中,下面分別介紹setter注入和構造函數注入。
Setter注入顧名思義,被注入的屬性須要有set方法, Setter注入支持簡單類型和引用類型,Setter注入時在bean實例建立完成後執行的。直接觀察前面的案例,對象注入使用<property>的ref屬性。
<!-- 聲明accountDao對象並交給spring建立 --> <bean name="accountDao" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- 聲明accountService對象,交給spring建立 --> <bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl"> <!-- 經過setter注入accountDao對象,對象注入使用ref--> <property name="accountDao" ref="accountDao"/> </bean>
除了上述的對象注入同時也能夠注入簡單值和map、set、list、數組,簡單值注入使用<property>的value屬性:
public class Account { private String name; private String pwd; private List<String> citys; private Set<String> friends; private Map<Integer,String> books; public void setName(String name) { this.name = name; } public void setPwd(String pwd) { this.pwd = pwd; } public void setCitys(List<String> citys) { this.citys = citys; } public void setFriends(Set<String> friends) { this.friends = friends; } public void setBooks(Map<Integer, String> books) { this.books = books; } }
注入代碼以下:
<!-- setter經過property 注入屬性值,普通類型使用value --> <bean id="account" scope="prototype" class="com.zejian.spring.springIoc.pojo.Account" > <property name="name" value="I am SpringIOC1" /> <property name="pwd" value="123" /> <!-- 注入map --> <property name="books"> <map> <entry key="10" value="CoreJava"> </entry> <entry key="11" value="JavaWeb"> </entry> <entry key="12" value="SSH2"> </entry> </map> </property> <!-- 注入set --> <property name="friends"> <set> <value>張龍</value> <value>老王</value> <value>王五</value> </set> </property> <!-- 注入list --> <property name="citys"> <list> <value>北京</value> <value>上海</value> <value>深圳</value> <value>廣州</value> </list> </property> </bean>
構造注入也就是經過構造方法注入依賴,構造函數的參數通常狀況下就是依賴項,spring容器會根據bean中指定的構造函數參數來決定調用那個構造函數,一樣看一個案例:
/** * Created by zejian on 2017/1/15. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ public class AccountServiceImpl implements AccountService{ /** * 須要注入的對象Dao層對象 */ private AccountDao accountDao; /** * 構造注入 * @param accountDao */ public AccountServiceImpl(AccountDao accountDao){ this.accountDao=accountDao; } //........ }
xml配置以下:
<bean name="accountDao" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- 經過構造注入依賴 --> <bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl"> <!-- 構造方法方式注入accountDao對象,--> <constructor-arg ref="accountDao"/> </bean>
固然跟setter注入同樣,構造注入也可傳入簡單值類型和集合類型,這個比較簡單,不囉嗦。須要注意的是,當一個bean定義中有多個<constructor-arg>標籤時,它們的放置順序並不重要,由於Spring容器會經過傳入的依賴參數與類中的構造函數的參數進行比較,嘗試找到合適的構造函數。惋惜的是,在某些狀況下可能會出現問題,以下的User類,帶有兩個構造函數,參數類型和個數都是同樣的,只是順序不一樣,這在class的定義中是容許的,但對於Spring容器來講倒是一種災難。
public class User { private String name; private int age; //第一個構造函數 public User(String name , int age){ this.name=name; this.age=age; } //第二個構造函數 public User(int age,String name){ this.name=name; this.age=age; } }
配置信息:
<bean id="user" class="com.zejian.spring.springIoc.pojo.User" > <constructor-arg type="java.lang.String" value="Jack"/> <constructor-arg type="int" value="26"/> </bean>
當程序運行時,Spring容器會嘗試查找適合的User構造函數進而建立User對象,因爲<constructor-arg>的注入順序並不重要,從而致使不知該使用兩種構造函數中的那種,這時user實例將建立失敗,Spring容器也將啓動失敗。幸運地是,Spring早已爲咱們預測到這種狀況,所以只要給Spring容器一點點提示,它便能成功找到適合的構造函數從而建立user實例,在<constructor-arg>標籤中存在一個index的屬性,經過index屬性能夠告訴spring容器傳遞的依賴參數的順序,下面的配置將會令Spring容器成功找到第一個構造函數並調用建立user實例。
<bean id="user" class="com.zejian.spring.springIoc.pojo.User" > <constructor-arg index="0" value="Jack"/> <constructor-arg index="1" value="26"/> </bean>
在平常的開發中,setter注入和構造注入常常會混合使用,這並不用感受到詫異,後面咱們還會分析到註解裝配,它在開發中將更爲經常使用。
除了上述的現象,在構造函數注入還有一個沒法解決的循環依賴的問題,以下有兩個bean,A和B,這兩個bean經過構造函數互爲依賴,這種狀況下Spring容器將沒法實例化這兩個bean。
public class A{ private B b; public A(B b){ this.b=b; } } public class B{ private A a; public B(A a){ this.a=a; } }
<bean id="a" class="com.zejian.springioc.pojo.A"> <constructor-arg ref="b" /> </bean> <bean id="b" class="com.zejian.springioc.pojo.B"> <constructor-arg ref="a" /> </bean>
這是因爲A被建立時,但願B被注入到自身,然而,此時B還有沒有被建立,並且B也依賴於A,這樣將致使Spring容器左右爲難,沒法知足兩方需求,最後腦殼奔潰,拋出異常。解決這種困境的方式是使用Setter依賴,但仍是會形成一些沒必要要的困擾,所以,強烈不建議在配置文件中使用循環依賴。
除了上述手動注入的狀況,Spring還很是智能地爲咱們提供自動向Bean注入依賴的功能,這個過程通常被稱爲自動裝配(autowiring)。博主認爲這是一個很是酷的功能,當注入的bean特別多時,它將極大地節省編寫注入程序的時間,所以在開發中,很是常見。Spring的自動裝配有三種模式:byTpye(根據類型),byName(根據名稱)、constructor(根據構造函數)。
在byTpye模式中,Spring容器會基於反射查看bean定義的類,而後找到與依賴類型相同的bean注入到另外的bean中,這個過程須要藉助setter注入來完成,所以必須存在set方法,不然注入失敗。
//dao層 public class UserDaoImpl implements UserDao{ //....... @Override public void done(){ System.out.println("UserDaoImpl.invoke......"); } } //service層 public class UserServiceImpl implements UserService { //須要注入的依賴 private UserDao userDao; /** * set方法 * @param userDao */ public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void done(){ userDao.done(); } }
基於xml的配置以下,經過使用<bean>的autowire屬性啓動名稱爲userService的自動裝配功能
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!-- byType 根據類型自動裝配userDao--> <bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
測試代碼:
@Test public void test3() { ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc2.xml"); UserService userService= (UserService) applicationContext.getBean("userService"); userService.done(); }
運行截圖,顯然是能夠生效的:
事實上byType模式可能存一種注入失敗的狀況,因爲是基於類型的注入,所以當xml文件中存在多個相同類型名稱不一樣的實例Bean時,Spring容器依賴注入仍然會失敗,由於存在多種適合的選項,Spring容器沒法知道該注入那種,此時咱們須要爲Spring容器提供幫助,指定注入那個Bean實例。能夠經過<bean>標籤的autowire-candidate設置爲false來過濾那些不須要注入的實例Bean
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!-- autowire-candidate="false" 過濾該類型 --> <bean id="userDao2" autowire-candidate="false" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!-- byType 根據類型自動裝配userDao--> <bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
除了上述的解決方案外,還可採用byName模式的自動裝配,此時Spring只會嘗試將屬性名與bean名稱進行匹配,若是找到則注入依賴bean。
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!-- byName 根據名稱自動裝配,找到UserServiceImpl名爲 userDao屬性並注入--> <bean id="userService" autowire="byName" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
須要瞭解的是若是Spring容器中沒有找到能夠注入的實例bean時,將不會向依賴屬性值注入任何bean,這時依賴bean的屬性可能爲null,所以咱們須要當心處理這種狀況,避免沒必要要的奔潰。對於constructor模式,在該模式下Spring容器一樣會嘗試找到那些類型與構造函數相同匹配的bean而後注入。
public class UserServiceImpl implements UserService { private UserDao userDao; //constructor模式 public UserServiceImpl(UserDao userDao){ this.userDao=userDao; } // /** // * set方法 // * @param userDao // */ // public void setUserDao(UserDao userDao) { // this.userDao = userDao; // } @Override public void done(){ userDao.done(); } }
基於xml配置:
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!-- constructor自動裝配userDao--> <bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
在實際測試中發現當出現多個bean實例時,若是含有名稱與類中聲明同樣時仍然能正確找到對應類,如userDao、userDao2同時出現,而userService實現類中存在同名屬性userDao,此時無需過濾也能正確注入:
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!--沒有過濾userDao2--> <bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
運行程序:
當只存在userDao2時,仍然能正常注入該類,以下形式
<!--只存在userDao2,仍然能正確注入--> <bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
可是當不存在userDao,只存在userDao2和userDao3時就不能注入了此時必須使用autowire-candidate=」false」 過濾該類型了。
<!--只存在userDao2,userDao3 沒法成功注入--> <bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <bean id="userDao3" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <bean id="userService" autowire="constructor" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
所以得出以下結論:在constructor模式下,存在單個實例則優先按類型進行參數匹配(不管名稱是否匹配),當存在多個類型相同實例時,按名稱優先匹配,若是沒有找到對應名稱,則注入失敗,此時可使用autowire-candidate=」false」 過濾來解決。
經過上述的分析,咱們已對自動裝配有所熟悉,可是在bean實例過多的情景下,手動設置自動注入屬性仍是不太完美,好在Spring 2.5 中引入了 @Autowired 註釋,它能夠對類成員變量、方法及構造函數進行標註,完成自動裝配的工做。 經過 @Autowired的使用標註到成員變量時不須要有set方法,請注意@Autowired 默認按類型匹配的,先看示例用註解演示前面注入userDao實例的三種方式。固然使用註解前必須先註冊註解驅動,這樣註解才能被正確識別
<!-- 使用註解時必須啓動註解驅動 --> <context:annotation-config />
public class UserServiceImpl implements UserService { //標註成員變量 @Autowired private UserDao userDao; //標註構造方法 @Autowired public UserServiceImpl(UserDao userDao){ this.userDao=userDao; } //標註set方法 @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void done(){ userDao.done(); } }
顯然上述代碼咱們經過3種方式注入userDao實例,xml配置文件只需聲明bean的實例便可,在實際開發中,咱們只需選擇其中一種進行注入操做便可,建議使用成員變量注入,這樣能夠省略set方法和構造方法,至關簡潔。
public class UserServiceImpl implements UserService { //標註成員變量 @Autowired private UserDao userDao; }
在@Autowired中還傳遞了一個required=false的屬性,false指明當userDao實例存在就注入不存就忽略,若是爲true,就必須注入,若userDao實例不存在,就拋出異常。因爲默認狀況下@Autowired是按類型匹配的(byType),若是須要按名稱(byName)匹配的話,可使用@Qualifier註解與@Autowired結合,請注意必須在xml配置中啓動註解驅動:
public class UserServiceImpl implements UserService { //標註成員變量 @Autowired @Qualifier("userDao1") private UserDao userDao; }
使用byName模式,xml配置以下:
<!-- 根據@Qualifier("userDao1")自動識別 --> <bean id="userDao1" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <bean id="userService" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
與@Autowried具有相同功效的還有@Resource,默認按 byName模式 自動注入,由J2EE提供,需導入Package: javax.annotation.Resource,能夠標註在成員變量和set方法上,但沒法標註構造函數。@Resource有兩個中重要的屬性:name和type。Spring容器對於@Resource註解的name屬性解析爲bean的名字,type屬性則解析爲bean的類型。所以使用name屬性,則按byName模式的自動注入策略,若是使用type屬性則按 byType模式自動注入策略。假若既不指定name也不指定type屬性,Spring容器將經過反射技術默認按byName模式注入。
//@Autowired標註成員變量 @Autowired @Qualifier("userDao") private UserDao userDao; //上述代碼等價於@Resource @Resource(name=「userDao」) private UserDao userDao;//用於成員變量 //也能夠用於set方法標註 @Resource(name=「userDao」) public void setUserDao(UserDao userDao) { this.userDao= userDao; }
關於@Autowired和@Resource都分析完了,但這裏存在一個問題,上述兩種自動裝配的依賴注入並不適合簡單值類型,如int、boolean、long、String以及Enum等,對於這些類型,Spring容器也提供了@Value注入的方式,這是很是具有人性化的,能夠解決不少硬編碼問題。@Value接收一個String的值,該值指定了將要被注入到內置的java類型屬性值,放心,沒必要關係類型轉換,大多數狀況下Spring容器都會自動處理好的。通常狀況下@Value會與properties文件結合使用,也分兩種狀況一種是SpEL(有點相似於jsp的EL),另一種是佔位符方式,看一個簡單例子jdbc.properties文件以下:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&allowMultiQueries=true jdbc.username=root jdbc.password=root
利用註解@Value獲取jdbc.url和jdbc.username的值,實現以下:
/** * Created by zejian on 2017/1/18. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ public class UserServiceImpl implements UserService { //標註成員變量 @Autowired @Qualifier("userDao") private UserDao userDao; //佔位符方式 @Value("${jdbc.url}") private String url; //SpEL表達方式,其中表明xml配置文件中的id值configProperties @Value("#{configProperties['jdbc.username']}") private String userName; @Override public void done(){ System.out.println("url:"+url); System.out.println("username:"+userName); userDao.done(); } }
基於xml的配置以下:
<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: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"> <!--基於佔位符方式 配置單個properties --> <!--<context:property-placeholder location="conf/jdbc.properties"/>--> <!--基於佔位符方式 配置多個properties --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer"> <property name="location" value="conf/jdbc.properties"/> </bean> <!--基於SpEL表達式 配置多個properties id值爲configProperties 提供java代碼中使用 --> <bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:/conf/jdbc.properties</value> </list> </property> </bean> <!--基於SpEL表達式 配置單個properties --> <!--<util:properties id="configProperties" location="classpath:conf/jdbc.properties"/>--> <!--註解驅動 --> <context:annotation-config/> <bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl"/> <bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl"/> </beans>
運行程序:
每個交給Spring IOC(後面統稱Spring容器)容器建立的對象必須被分配至少一個名稱,若是開發者沒有提供,Spring容器將會爲其分配一個內部名稱,經過Bean的名稱,咱們能夠在其餘類中查找該類並使用它,如前面的案例,也是經過Bean名稱獲取到實際對象並執行對應的操做。在基於xml的配置信息中,可使用id屬性來爲一個Bean分配名稱,在同一個xml配置文件中,id必須是惟一的,但不一樣的xml能夠相同,固然還可使用name來爲Bean分配名稱,name屬性能夠分配多個名稱,此時可以使用空格、逗號、分號來分離給定Bean分配多個名稱,而id屬性則沒法這樣使用。
<!-- name屬性配置多個名稱 --> <bean name="accountDao,accountDao2" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- id屬性配置惟一名稱並且不能與name相同--> <bean id="accountDaoId" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
在name屬性中聲明瞭兩個名稱,除了第一個名稱外,其餘的名稱都被稱爲別名(aliase)。除了在Bean中定義名稱外,還可利用<alias>標籤向Bean賦予別名:
<bean id="accountDaoId" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- name屬性指明要給那個Bean賦予別名,alias則指明賦予的別名--> <alias name="accountDaoId" alias="accountDao3" />
顯然若是咱們想要配置的Bean對象已存在,而且但願向一些Bean賦予特別的名稱,此時別名就至關有用了。上述的Bean對象聲明使用都在xml內聲明手動聲明的方式,一旦Bean對象多起來,管理Bean可能會發生繁瑣的狀況,爲了Spring提供了基於Java註解的配置方式,下面分別使用org.springframework.stereotype.Service(@Service)和org.springframework.stereotype.Repository(@Repository)聲明AccountServiceImpl和AccountDaoImpl類,使用@Autowired註解注入accountDao(須要在xml聲明註解驅動)
//@Component 相同效果 @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; }
//@Component 相同效果 @Repository public class AccountDaoImpl implements AccountDao{ //...... }
有了註解聲明,咱們就不須要在xml中聲明以上兩個Bean,但須要明確告訴Spring註解的Bean在那些包下,所以須要添加包掃描機制,此時須要啓用Spring的context命名空間:
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 聲明包掃描 --> <context:component-scan base-package="com.zejian.spring.springIoc" /> </beans>
以上的聲明方式與以前在xml聲明bean的效果相同。這裏咱們須要明白可使用@Component註解達到與@Service和@Repository的效果,@Component與@Service的含義並沒有差別,只不過@Service更能讓咱們明白該類爲業務類罷了。至於@Repository在表示數據訪問層含義的同時還可以啓用與Spring數據訪問相關鏈的其餘功能(這個在Spring jdbc相關內容時再詳談,此時咱們只需明白@Repository與@Component等效便可),同時還可給@Component、@Service和@Repository輸入一個String值的名稱,若是沒有提供名稱,那麼默認狀況下就是一個簡單的類名(第一個字符小寫)變成Bean名稱。
@Service("accountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; } @Repository("accountDao") public class AccountDaoImpl implements AccountDao{ //...... }
所以到這咱們也就知道了,Spring的框架中提供了與@Component註解等效的三個註解,@Repository 用於對DAO實現類進行標註,@Service 用於對Service實現類進行標註,@Controller 用於對Controller實現類進行標註(web層控制器),同時也瞭解了Spring 容器經過xml的bean標籤配置和java註解兩種方式聲明的Bean對象,咱們能夠單獨使用其中一種也能夠兩種混合使用,取決於各自的需求。
在平常開發中建立對象的最經常使用的就是經過類的構造方法,事實上Spring容器也是正常狀況下也是經過構造方法建立bean的。
package com.zejian.spring.springIoc.pojo; /** * Created by zejian on 2017/1/16. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] * pojo對象 */ public class Account { private String name; private String pwd; /** * 默認構造 */ public Account(){ } /** * 帶參數的構造 * @param name * @param pwd */ public Account(String name, String pwd) { this.name = name; this.pwd = pwd; } public void setName(String name) { this.name = name; } public void setPwd(String pwd) { this.pwd = pwd; } public String getName() { return name; } public String getPwd() { return pwd; } }
在xml中配置
<!-- 默認構造建立,並經過property 注入屬性值 --> <bean id="account" class="com.zejian.spring.springIoc.pojo.Account" > <property name="name" value="Jack" /> <property name="pwd" value="123" /> </bean> <!-- 帶參構造建立,並經過constructor-arg注入屬性值 --> <bean id="account2" class="com.zejian.spring.springIoc.pojo.Account" > <constructor-arg name="name" value="Jack" /> <constructor-arg name="pwd" value="1234" /> </bean>
除了構造建立外還存在另外兩種比較冷門的建立的方式即靜態方法構造和實例工廠方法構造,下面咱們簡單看一個例子便可:
/** * Created by zejian on 2017/1/16. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ public class PojoFactory { /** * 靜態工廠建立 * @return */ public static Account createAccount(){ Account account=new Account(); account.setName("Jack"); account.setPwd("1234"); return account; } /** * 經過工廠實例建立 * @return */ public Account createAccount2(){ Account account=new Account(); account.setName("Jack"); account.setPwd("1234"); return account; } }
xml配置以下:
<!-- 靜態工廠建立,調用靜態方法createAccount --> <bean id="account3" class="com.zejian.spring.springIoc.conf.PojoFactory" factory-method="createAccount"/> <!-- 工廠實例建立,先建立工廠類在調用方法createAccount2 --> <bean id="factory" class="com.zejian.spring.springIoc.conf.PojoFactory" /> <bean id="account4" factory-bean="factory" factory-method="createAccount2"/>
後面兩種方式簡單瞭解便可,通常不多使用。經常使用的仍是構造建立,開發中使用構造實例化bean便可。
Bean的重寫機制並無那麼神祕,主要是當不一樣的xml文件中出現同名id屬性的bean時讀取的優先級問題,一樣簡單看個例子就明白了。定義兩個spring的配置文件並同時加載它們:spring-ioc.xml
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 默認構造建立,並經過property 注入屬性值 --> <bean id="account" class="com.zejian.spring.springIoc.pojo.Account" > <property name="name" value="I am SpringIOC1" /> <property name="pwd" value="123" /> </bean> </beans>
spring-ioc2.xml
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 默認構造建立,並經過property 注入屬性值 --> <bean id="account" class="com.zejian.spring.springIoc.pojo.Account" > <property name="name" value="I am SpringIOC2" /> <property name="pwd" value="123" /> </bean> </beans>
獲取bean並調用
@Test public void test1() { ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml","spring/spring-ioc2.xml"); Account account= (Account) applicationContext.getBean("account"); System.out.println("調用結果:"+account.getName()); }
執行結果:
顯然在不一樣的xml配置文件中使用相同id命名,並聲明相同類型的bean對象時,spring容器會默認加載最後添加的spring-ioc2.xml中account而忽略spring-ioc.xml中的account,也就是說Bean的重寫機制原則是當聲明的bean的名稱同樣時,後者會覆蓋前者。咱們還須要明確的一點時,在web應用開發過程當中,通常都會將配置進行分層管理,而後經過一個主springApplication.xml來聚合它,在這樣的狀況下分層的配置文件屬於springApplication.xml的子文件,在這樣的關係遇到上述的狀況通常都子文件的優先級高,所以會加載子文件的bean。如在spring-ioc.xml主文件導入子文件spring-ioc2.xml:
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 默認構造建立,並經過property 注入屬性值 --> <bean id="account" class="com.zejian.spring.springIoc.pojo.Account" > <property name="name" value="I am SpringIOC1" /> <property name="pwd" value="123" /> </bean> <!-- 導入的子文件 --> <import resource="spring-ioc2.xml" /> </beans>
運行代碼:
@Test
public void test1() { ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml"); Account account= (Account) applicationContext.getBean("account"); System.out.println("調用結果:"+account.getName()); }
上述代碼會優先加載spring-ioc2.xml中的account而忽略spring-ioc.xml中的account,效果與前面的代碼相同。
關於分層管理開發通常按以下方式(這樣的好處是脈絡清晰,方便管理):
spring-web.xml文件:web層相關bean聲明
spring-service.xml文件:service層相關bean聲明
spring-dao.xml文件:dao層相關bean聲明
spring-tx.xml文件:事務相關bean和規則聲明
spring-security.xml文件:安全相關聲明
spring-application.xml 文件:匯聚文件或總bean聲明。
所謂Bean的做用域是指spring容器建立Bean後的生存週期即由建立到銷燬的整個過程。以前咱們所建立的全部Bean其做用域都是Singleton,這是Spring默認的,在這樣的做用域下,每個Bean的實例只會被建立一次,並且Spring容器在整個應用程序生存期中均可以使用該實例。所以以前的代碼中spring容器建立Bean後,經過代碼獲取的bean,不管多少次,都是同一個Bean的實例。咱們可以使用<bean>標籤的scope屬性來指定一個Bean的做用域,以下:
<!-- 默認狀況下無需聲明Singleton --> <bean name="accountDao" scope="singleton" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
除了Singleton外還有另一種比較經常使用的做用域,prototype,它表明每次獲取Bean實例時都會新建立一個實例對象,相似new操做符。咱們來簡單測試一下:
<!-- 做用域:prototype --> <bean name="accountDao" scope="prototype" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
測試代碼:
@Test
public void test2() {
ApplicationContext applicationContext=new
ClassPathXmlApplicationContext("spring/spring-ioc.xml"); AccountDao accountDao1 = (AccountDao) applicationContext.getBean("accountDao"); AccountDao accountDao2 = (AccountDao) applicationContext.getBean("accountDao"); System.out.println("accountDao1地址:"+accountDao1.toString()); System.out.println("accountDao2地址:"+accountDao2.toString()); }
執行結果:
顯然是兩個不一樣的實例對象。固然咱們也可經過註解來聲明做用域:
@Scope("prototype") public class AccountDaoImpl { //...... }
這裏還須要說明一種特殊的情景,當一個做用域爲Singleton的Bean依賴於一個做用域爲prototype的Bean時以下:
<!-- 做用域prototype--> <bean name="accountDao" scope="prototype" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- 做用域Singleton --> <bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl"> <!-- 注入做用域爲prototype的accountDao對象,須要set方法 --> <property name="accountDao" ref="accountDao"/> </bean>
在這樣的狀況下但願的是每次getBean(「accountService」)處理的都是一個新的accountDao實例對象,可是因爲accountService的依賴是在Bean被建立時注入的,並且accountService是一個Singleton,整個生存週期中只會建立一次,所以它所依賴的accountDao實例對象也只會被注入一次,此後不會再注入任何新的accountDao實例對象。爲了解決這種困境,只能放棄使用依賴注入的功能,使用代碼實現,以下:經過實現ApplicationContextAware接口,重寫setApplicationContext,這樣的話Spring容器在建立AccountServiceImpl實例時會自動注入ApplicationContext對象,此時即可以經過ApplicationContext獲取accountDao實例了,這樣能夠保證每次獲取的accountDao實例都是新的(這裏的代碼只是演示該過程,實際開發中通常不會要求accountDao每次都是新實例,由於accountDao無需記錄狀態信息,即無狀態bean,通常默認singleton便可)
<bean name="accountDao" scope="prototype" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- accountDao經過代碼注入 --> <bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl" />
代碼注入示範:
/** * Created by zejian on 2017/1/15. * Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創] */ public class AccountServiceImpl implements AccountService , ApplicationContextAware { private ApplicationContext applicationContext; @Override public void doSomething() { System.out.println("AccountServiceImpl#doSomething......"); System.out.println("getAccountDao....."+ getAccountDao().toString()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext=applicationContext; } private AccountDao getAccountDao(){ return applicationContext.getBean(AccountDao.class); } }
測試代碼:
//加載配置文件 ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring/spring-ioc.xml"); //測試獲取不一樣實例的AccountDao AccountService accountService= (AccountService) applicationContext.getBean("accountService"); accountService.doSomething(); AccountService accountService1= (AccountService) applicationContext.getBean("accountService"); accountService1.doSomething();
運行結果:
顯然這樣的方式,每次獲取的daoAccount都不同,也就解決上述的問題,另外的一種狀況是當一個做用域爲propotype的Bean依賴於一個Singleton做用域的Bean時,解決方案跟上述是相同的。請注意,當一個Bean被設置爲prototype 後Spring就不會對一個bean的整個生命週期負責,容器在初始化、配置、裝飾或者是裝配完一個prototype實例後,將它交給客戶端,隨後就對該prototype實例漠不關心了。所以咱們須要慎用它,通常狀況下,對有狀態的bean應該使用prototype做用域,而對無狀態的bean則應該使用singleton做用域。所謂有狀態就是該bean有保存信息的能力,不能共享,不然會形成線程安全問題,而無狀態則不保存信息,是線程安全的,能夠共享,spring中大部分bean都是Singleton,整個生命週期過程只會存在一個。
在spring2.5中專門針對Web應該程序引進了request和session這兩種做用域。關於request做用域,對於每次HTTP請求到達應用程序,Spring容器會建立一個全新的Request做用域的bean實例,且該bean實例僅在當前HTTP request內有效,整個請求過程也只會使用相同的bean實例,所以咱們能夠根據須要放心的更改所建實例的內部狀態,而其餘請求HTTP請求則建立新bean的實例互不干擾,當處理請求結束,request做用域的bean實例將被銷燬,如在接收參數時可能須要一個bean實例來裝載一些參數,顯然每次請求參數幾乎不會相同,所以但願bean實例每次都是足夠新的並且只在request做用域範圍內有效。關於Session可能你也已猜到,每當建立一個新的HTTP Session時就會建立一個Session做用域的Bean,並該實例bean伴隨着會話的存在而存在。下面看一個測試程序:
@Component @Scope(value = "singleton") public class SingletonBean { //...... } @Component @Scope(value = "prototype" , proxyMode = ScopedProxyMode.TARGET_CLASS) public class PrototypeBean { //...... } @Component @Scope(value = "request" , proxyMode = ScopedProxyMode.TARGET_CLASS) public class RequestBean { //...... } @Component @Scope(value = "session" , proxyMode = ScopedProxyMode.TARGET_CLASS) public class SessionBean { //........ }
上述代碼,分別建立4個不一樣做用域的Bean,並使用註解的方式開發,@Component表名它們是組件類,須要Spring容器幫忙建立,@Scope註明做用域,value指明是哪一種做用域,除了SingletonBean外,其它Bean還使用proxyMode指明哪一種代理模式建立,這裏沒有接口,所以使用CGLib代理生成(後面會分析爲何這麼作)。接着須要在xml註明掃描包告訴Spring容器它們在哪裏(4個類都聲明在com.zejian.spring.dto包下)。
<!-- 包掃描 --> <context:component-scan base-package="com.zejian.spring.dto" />
使用SpringMVC建立Web訪問層(若是不清楚springmvc,能夠理解爲web訪問層即將相似Servlet):
@Controller public class BookController { @Autowired private RequestBean requestBean; @Autowired private SessionBean sessionBean; @Autowired private PrototypeBean prototypeBean; @Autowired private SingletonBean singletonBean; @RequestMapping(value = "/test") public void test() { print(); } public void print() { System.out.println("first time singleton is :" + singletonBean); System.out.println("second time singleton is :" + singletonBean); System.out.println("first time prototype is :" + prototypeBean); System.out.println("second time prototype is :" + prototypeBean); System.out.println("first time requestBean is :" + requestBean); System.out.println("second time requestBean is :" + requestBean); System.out.println("first time sessionBean is :" + sessionBean); System.out.println("second time sessionBean is :" + sessionBean); System.out.println("==========================================="); }
如今啓動tomcat服務器使用Chrome瀏覽器進行連續兩次訪問結果以下:
first time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720 second time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720 first time prototypeBean is :com.zejian.spring.dto.PrototypeBean@1ed53cde second time prototypeBean is :com.zejian.spring.dto.PrototypeBean@35c052be first time requestBean is :com.zejian.spring.dto.RequestBean@15b9dfe1 second time requestBean is :com.zejian.spring.dto.RequestBean@15b9dfe1 first time sessionBean is :com.zejian.spring.dto.SessionBean@5b355dae second time sessionBean is :com.zejian.spring.dto.SessionBean@5b355dae =========================================== first time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720 second time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720 first time prototypeBean is :com.zejian.spring.dto.PrototypeBean@7775fd09 second time prototypeBean is :com.zejian.spring.dto.PrototypeBean@79b20d97 first time requestBean is :com.zejian.spring.dto.RequestBean@7d8d9679 second time requestBean is :com.zejian.spring.dto.RequestBean@7d8d9679 first time sessionBean is :com.zejian.spring.dto.SessionBean@5b355dae second time sessionBean is :com.zejian.spring.dto.SessionBean@5b355dae ===========================================
顯然singletonBean永遠只有一個實例,而PrototypeBean則每次被獲取都會建立新的實例,對應RequestBean,在同一次Http請求過程當中是同一個實例,當請求結束,RequestBean也隨着銷燬,在新的Http請求則會生成新的RequestBean實例,對於SessionBean,因爲是在同一個瀏覽器中訪問屬於同一次會話,所以SessionBean實例都是同一個實例對象。如今使用另一個瀏覽器啓動訪問,觀察SessionBean是否變化。
first time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720 second time singletonBean is :com.zejian.spring.dto.SingletonBean@2ebdd720 first time prototypeBean is :com.zejian.spring.dto.PrototypeBean@5a85c6a7 second time prototypeBean is :com.zejian.spring.dto.PrototypeBean@54423387 first time requestBean is :com.zejian.spring.dto.RequestBean@507dadd7 second time requestBean is :com.zejian.spring.dto.RequestBean@507dadd7 first time sessionBean is :com.zejian.spring.dto.SessionBean@157f39bc second time sessionBean is :com.zejian.spring.dto.SessionBean@157f39bc ===========================================
顯然SessionBean已改變,也就說明不一樣的會話中SessionBean實例是不一樣的。但咱們仍是很詫異,爲何須要在其餘3種做用域上設置代理模式?事實上這個問題的本質與前面Singleton做用域的bean依賴於Prototype做用域的bean原理是相同,Prototype前面已分析過了(使用註解時也必須聲明代理模式),這裏咱們主要分析request和session做用域,因爲Spring容器只會在建立bean實例時幫助咱們注入該實例bean所依賴的其餘bean實例,並且只會注入一次,這並非request、session做用域所但願看到的,畢竟它們都須要在不一樣的場景下注入新的實例對象而不是惟一不變的實例對象。爲了解決這種困境,必須放棄直接在xml中注入bean實例,改用java代碼方式(實現ApplicationContextAware接口)或者註解的方式(@Autowired)注入。示例中選擇了後者,並在bean的聲明中聲明瞭動態代理模式(關於動態代理自行查閱相關資料),幸運地是,Spring容器足夠聰明,以致於spring容器經過代理的方式生成新的代理實例bean,以此來知足建立新實例的需求。在程序運行過程當中,當一個方法調用到達該代理對象時,Spring容器便嘗試在當前的請求(Request)或者會話(Session)獲取目標對象Bean(真正的實例Bean)。若是已存在則使用該Bean,不然,代理方法將建立新實例bean處理請求或者會話,請注意,這裏指的的是一次Http請求或者一次會話的過程。若是但願request和session做用域經過xml配置文件方式聲明時必須在<bean>標籤中放置<aop:scoped-proxy>做爲子標籤,該做用於註解聲明代理模式效果相同(須要引用aop命名空間,關於aop不清楚的,這裏可簡單理解爲聲明代理便可)。請注意,通過測試這種聲明代理的方式不適合prototype做用域,該做用域生效的方式目前測試中只有基於註解方式和前面基於實現ApplicationContextAware接口兩種方式。
<?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:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > <bean id="requestBean" scope="request" class="com.zejian.spring.dto.RequestBean" > <!-- 聲明aop代理 --> <aop:scoped-proxy /> </bean> <bean id="sessionBean" scope="request" class="com.zejian.spring.dto.SessionBean" > <!-- 聲明aop代理 --> <aop:scoped-proxy /> </bean> </beans>
最後還有一點須要明確的是,若是web層適用的是SpringMVC處理web請求,則不須要作任何事情就可使Request和Session做用域生效。但假若適用其餘web層框架的實現,務必須要在web.xml中聲明以下監聽器,以便使Request和Session做用域正常工做:
<web-app> <listener> <listener-class> org.springframework.web.context.request.RequestContextListener <listener-class> </listener> </web-app>
這種做用域相似於Session做用域,至關於全局變量,相似Servlet的Application,適用基於portlet的web應用程序,請注意,portlet在這指的是分佈式開發,而不是portlet語言開發。
在某些狀況下,咱們可能但願把bean的建立延遲到使用階段,以避免消耗沒必要要的內存,Spring也很是自願地支持了延遲bean的初始化。所以能夠在配置文件中定義bean的延遲加載,這樣Spring容器將會延遲bean的建立直到真正須要時才建立。一般狀況下,從一個已建立的bean引用另一個bean,或者顯示查找一個bean時會觸發bean的建立即便配置了延遲屬性,所以若是Spring容器在啓動時建立了那些設爲延長加載的bean實例,沒必要驚訝,可能那些延遲初始化的bean可能被注入到一個非延遲建立且做用域爲singleton的bean。在xml文件中使用bean的lazy-init屬性能夠配置改bean是否延遲加載,若是須要配置整個xml文件的bean都延遲加載則使用defualt-lazy-init屬性,請注意lazy-init屬性會覆蓋defualt-lazy-init屬性。
<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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd " default-lazy-init="true"> <!--default-lazy-init="true" xml中所有bean延遲加載 --> <!-- lazy-init="false" 表示非延長加載--> <bean name="accountDao" lazy-init="false" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/> <!-- 聲明accountService對象,交給spring建立 --> <bean name="accountService" class="com.zejian.spring.springIoc.service.impl.AccountServiceImpl"> <!-- 注入accountDao對象,須要set方法--> <property name="accountDao" ref="accountDao"/> </bean> </beans>
請務必明確一點,默認狀況下Spring容器在啓動階段就會建立bean,這個過程被稱爲預先bean初始化,這樣是有好處的,可儘量早發現配置錯誤,如配置文件的出現錯別字或者某些bean尚未被定義卻被注入等。固然如存在大量bean須要初始化,這可能引發spring容器啓動緩慢,一些特定的bean可能只是某些場合須要而不必在spring容器啓動階段就建立,這樣的bean多是Mybatis的SessionFactory或者Hibernate SessionFactory等,延遲加載它們會讓Spring容器啓動更輕鬆些,從而也減小不必的內存消耗。
前面咱們使用@Autowired、@Resource、@Value等自動裝配註解時用<context:annotation-config/>進行註解驅動註冊,從而使註解生效。實際上這樣<context:annotation-config/>一條配置,它的做用是式地向 Spring 容器註冊AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor 以及RequiredAnnotationBeanPostProcessor 這 4 個BeanPostProcessor。註冊這4個 BeanPostProcessor後Spring容器就可以識別相應的註解了,固然它們也是也可單獨配置的。
假如想使用@ Resource 、@ PostConstruct、@ PreDestroy等註解就能夠單獨聲明CommonAnnotationBeanPostProcessor
若是想使用@PersistenceContext註解,聲明PersistenceAnnotationBeanPostProcessor的Bean便可。
若是想使用 @Required的註解,就必須聲明RequiredAnnotationBeanPostProcessor的Bean。
通常來講,這些註解是隨處可見的,若是老是須要一條一條配置天然就很是繁瑣了,因而spring容器很是智能地爲咱們提供<context:annotation-config/>的簡化配置方式,自動聲明。
對於<context:component-scan/>,前面在使用@Service、@Component、@Controller 、@Repository等註解時,須要在xml配置文件聲明包掃描驅動<context:component-scan/>,它的做用是Spring容器在啓動時會啓動註解驅動去掃描對應包下的bean對象並將建立它們的實例,這樣咱們就沒法一個個地進行bean配置聲明瞭,極大簡化了編程代碼。請注意,當spring的xml配置文件出了<context:component-scan/> 後,<context:annotation-config/>就能夠退休了,由於<context:component-scan/>已包含了<context:annotation-config/>的功能了。在大部分狀況下,都會直接使用<context:component-scan/>進行註解驅動註冊和包掃描功能。
IOC:控制反轉:將對象的建立權,由Spring管理. DI(依賴注入):在Spring建立對象的過程當中,把對象依賴的屬性注入到類中。 它們兩就這樣,其餘什麼毛線都沒有了。