來源:關于Spring IOC (DI-依賴注入)你需要知道的一切
作者:zejian
Dao層(AccountDao):
public interface AccountDao {
void addAccout();
}
實現類(AccountDaoImpl):
public class AccountDaoImpl implements AccountDao{
@Override
public void addAccount() {
System.out.println("addAccount....");
}
}
再創建Service,AccountService
public interface AccountService {
void doSomething();
}
實現類:
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
@Override
public void doSomething() {
// TODO Auto-generated method stub
System.out.println("AccountServiceImpl#doSomething......");
accountDao.addAccout();
}
public void setAccountDao(AccountDao accountDao){
this.accountDao = accountDao;
}
}
由于Spring的IOC控制反轉類似于java工廠模式的升華,相當于將所有對象都交給xml配置文件管理,另類的工廠方法。現在建立了Dao層和service層的接口和實現類,其中service層的操作依賴于Dao層。我們現在建立xml配置文件:
<!-- 聲明accountDao對象,交給spring創建 -->
<bean name="accountDao" class="com.yangjing.javaTest.SpringIocTest.AccountDaoImpl"/>
<!-- 聲明accountService對象,交給spring創建 -->
<bean name="accountService" class="com.yangjing.javaTest.SpringIocTest.AccountServiceImpl">
<!-- 注入accountDao對象,需要set方法-->
<property name="accountDao" ref="accountDao"/>
</bean>
accountService聲明中多出了一個property的標簽,這個標簽指向了我們剛才創建的accountDao對象,它的作用是把accountDao對象傳遞給accountService實現類中的accountDao屬性,該屬性必須擁有set方法才能注入成功,我們把這種往類accountService對象中注入其他對象(accountDao)的操作稱為依賴注入
<!-- 聲明accountService對象,交給spring創建 -->
<bean name="accountService" class="com.yangjing.javaTest.SpringIocTest.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,通過這個類加載聲明好的配置文件,通過getBean獲取我們所需要的類。
@Test
public void testAccountByXml() throws Exception{
ApplicationContext context = new ClassPathXmlApplicationContext("service-test.xml");
AccountService accountService = (AccountService)context.getBean("accountService");
accountService.doSomething();
}
通過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相同
使用注解方式達到上述效果:
@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;
}
}
使用AnnotationConfigApplicationContext來加載配置文件:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfiguration.class);
AccountService accountService= (AccountService) context.getBean("accountService");
accountService.doSomething();
我并沒有測試通過,網上搜索問題,缺少jar包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>1.5.3</version>
</dependency>
還是報錯,就先沒管這種方式了。
Spring依賴注入
所謂的依賴注入,其實是當一個bean實例引用到了另外一個bean實例時spring容器幫助我們創建依賴bean實例并注入(傳遞)到另一個bean中,如上述案例中的AccountService依賴于AccountDao,Spring容器會在創建AccountService的實現類和AccountDao的實現類后,把AccountDao的實現類注入AccountService實例中。
基于注解的自動裝配(@Autowired&@Resource&@Value)
基于@Autowired注解的自動裝配
我們已對自動裝配有所熟悉,但是在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.yangjing.javaTest.SpringIocTest.UserDaoImpl" />
<bean id="userDao2" class="com.yangjing.javaTest.SpringIocTest.UserDaoImpl" />
<bean id="userService" class="com.yangjing.javaTest.SpringIocTest.UserServiceImpl" />
基于@Resource注解的自動裝配
與@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;
}
#######基于@Value注解的自動裝配以及properties文件讀取
關于@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>
IOC容器管理 bean
#######Bean的命名以及實例化方法
Bean的命名
每一個交給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對象,我們可以單獨使用其中一種也可以兩種混合使用,取決于各自的需求。
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的重寫機制
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的作用域
Singleton作用域
所謂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"/>
prototype作用域
除了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,整個生命周期過程只會存在一個。
request與session作用域
在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>
globalSession作用域
這種作用域類似于Session作用域,相當于全局變量,類似Servlet的Application,適用基于portlet的web應用程序,請注意,portlet在這指的是分布式開發,而不是portlet語言開發。
Bean的延長加載
在某些情況下,我們可能希望把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容器啟動更輕松些,從而也減少沒必要的內存消耗。
<context:component-scan/>與<context:annotation-config/>
前面我們使用@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 與依賴注入的區別
IOC:控制反轉:將對象的創建權,由Spring管理。
DI(依賴注入):在Spring創建對象的過程中,把對象依賴的屬性注入到類中。
它們兩就這樣,其他什么毛線都沒有了。