Bean配置信息定義了Bean的實現及依賴關係,Spring容器根據各類形式的Bean配置信息在容器內部創建Bean定義註冊表,而後根據註冊表加載、實例化Bean,並創建Bean和Bean的依賴關係,最後將這些準備就緒的Bean放到Bean緩存池中,以供外層的應用程序進行調用。java
bean配置有三種方法:web
對於基於XML的配置,Spring 2.0之後使用Schema的格式,使得不一樣類型的配置擁有了本身的命名空間,是配置文件更具擴展性。spring
①默認命名空間:它沒有空間名,用於Spring Bean的定義;express
②xsi命名空間:這個命名空間用於爲每一個文檔中命名空間指定相應的Schema樣式文件,是標準組織定義的標準命名空間;緩存
③aop命名空間:這個命名空間是Spring配置AOP的命名空間,是用戶自定義的命名空間。框架
命名空間的定義分爲兩個步驟:第一步指定命名空間的名稱;第二步指定命名空間的Schema文檔樣式文件的位置,用空格或回車換行進行分分隔。函數
在Spring容器的配置文件中定義一個簡要Bean的配置片斷以下所示:ui
通常狀況下,Spring IOC容器中的一個Bean即對應配置文件中的一個<bean>,這種鏡像對應關係應該容易理解。其中id爲這個Bean的名稱,經過容器的getBean("foo")便可獲取對應的Bean,在容器中起到定位查找的做用,是外部程序和Spring IOC容器進行交互的橋樑。class屬性指定了Bean對應的實現類。this
下面是基於XML的配置文件定義了兩個簡單的Bean:spa
<?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-3.0.xsd"> <bean id="car" name="#car1" class="com.baobaotao.simple.Car"></bean> <bean id="boss" class="com.baobaotao.simple.Boss"></bean> </beans>
咱們知道,Spring容器成功啓動的三大要件分別是:Bean定義信息、Bean實現類以及Spring自己。若是採用基於XML的配置,Bean定義信息和Bean實現類自己是分離的,而採用基於註解的配置方式時,Bean定義信息即經過在Bean實現類上標註註解實現。
下面是使用註解定義一個DAO的Bean:
package com.baobaotao.anno; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; //①經過Repository定義一個DAO的Bean @Component("userDao") public class UserDao { }
在①處,咱們使用@Component註解在UserDao類聲明處對類進行標註,它能夠被Spring容器識別,Spring容器自動將POJO轉換爲容器管理的Bean。
它和如下的XML配置是等效的:
<bean id="userDao" class="com.baobaotao.anno.UserDao"/>
除了@Component之外,Spring提供了3個功能基本和@Component等效的註解,它們分別用於對DAO、Service及Web層的Controller進行註解,因此也稱這些註解爲Bean的衍型註解:(相似於xml文件中定義Bean<bean id=" " class=" "/>
之因此要在@Component以外提供這三個特殊的註解,是爲了讓註解類自己的用途清晰化,此外Spring將賦予它們一些特殊的功能。
Spring提供了一個context的命名空間,它提供了經過掃描類包以應用註解定義Bean的方式:
<?xml version="1.0" encoding="UTF-8" ?> <!--①聲明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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" > <!--②掃描類包以應用註解定義的Bean--> <context:component-scan base-package="com.baobaotao.anno"/> <bean class="com.baobaotao.anno.LogonService"></bean> <!-- context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ --> <!-- context:component-scan base-package="com.baobaotao"> <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Dao"/> <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Service"/> <context:exclude-filter type="aspectj" expression="com.baobaotao..*Controller+"/> </context:component-scan --> </beans>
在①處聲明context命名空間,在②處便可經過context命名空間的component-scan的base-package屬性指定一個須要掃描的基類包,Spring容器將會掃描這個基類包裏的全部類,並從類的註解信息中獲取Bean的定義信息。
若是僅但願掃描特定的類而非基包下的全部類,大家可使用resource-pattern屬性過濾特定的類,以下所示:
< context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ >
這裏咱們將基類包設置爲com.baobaotao,默認狀況下resource-pattern屬性的值爲"**/*.class",即基類包裏的全部類。這裏咱們設置爲"anno/*.class",則Spring僅會掃描基包裏anno子包中的類。
在普通的POJO類中只要標註@Configuration註解,就能夠爲spring容器提供Bean定義的信息了,每一個標註了@Bean的類方法都至關於提供了一個Bean的定義信息。
package com.baobaotao.conf; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //①將一個POJO標註爲定義Bean的配置類 @Configuration public class AppConf { //②如下兩個方法定義了兩個Bean,以提供了Bean的實例化邏輯 @Bean public UserDao userDao(){ return new UserDao(); } @Bean public LogDao logDao(){ return new LogDao(); } //③定義了logonService的Bean @Bean public LogonService logonService(){ LogonService logonService = new LogonService(); //④將②和③處定義的Bean注入到LogonService Bean中 logonService.setLogDao(logDao()); logonService.setUserDao(userDao()); return logonService; } }
①處在APPConf類的定義處標註了@Configuration註解,說明這個類可用於爲Spring提供Bean的定義信息。類的方法處能夠標註@Bean註解,Bean的類型由方法返回值類型決定,名稱默認和方法名相同,也能夠經過入參顯示指定Bean名稱,如@Bean(name="userDao").直接在@Bean所標註的方法中提供Bean的實例化邏輯。
在②處userDao()和logDao()方法定義了一個UserDao和一個LogDao的Bean,它們的Bean名稱分別是userDao和logDao。在③處,又定義了一個logonService Bean,而且在④處注入②處所定義的兩個Bean。
所以,以上的配置和如下XML配置時等效的:
<bean id="userDao" class="com.baobaotao.anno.UserDao"/> <bean id="logDao" class="com.baobaotao.anno.LogDao"/> <bean id="logService" class="com.baobaotao.conf.LogonService" p:logDao-ref="logDao" p:userDao-ref="userDao"/>
基於java類的配置方式和基於XML或基於註解的配置方式相比,前者經過代碼的方式更加靈活地實現了Bean的實例化及Bean之間的裝配,但後面二者都是經過配置聲明的方式,在靈活性上要稍遜一些,可是配置上要更簡單一些。
Bean注入的方式有兩種,一種是在XML中配置,此時分別有屬性注入、構造函數注入和工廠方法注入;另外一種則是使用註解的方式注入 @Autowired,@Resource,@Required。
屬性注入即經過setXxx()方法注入Bean的屬性值或依賴對象,因爲屬性注入方式具備可選擇性和靈活性高的優勢,所以屬性注入是實際應用中最常採用的注入方式。
屬性注入要求Bean提供一個默認的構造函數,併爲須要注入的屬性提供對應的Setter方法。Spring先調用Bean的默認構造函數實例化Bean對象,而後經過反射的方式調用Setter方法注入屬性值。
package com.baobaotao.anno; import org.springframework.beans.factory.BeanNameAware; public class LogonService implements BeanNameAware{ private LogDao logDao; private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setLogDao(LogDao logDao) { this.logDao = logDao; } public LogDao getLogDao() { return logDao; } public UserDao getUserDao() { return userDao; } public void setBeanName(String beanName) { System.out.println("beanName:"+beanName); } public void initMethod1(){ System.out.println("initMethod1"); } public void initMethod2(){ System.out.println("initMethod2"); } }
bean.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" 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" default-autowire="byName" > <bean id="logDao" class="com.baobaotao.anno.LogDao"/> <bean id="userDao" class="com.baobaotao.anno.UserDao"/> <bean class="com.baobaotao.anno.LogonService"> <property name="logDao" ref="logDao"></property> <property name="userDao" ref="userDao"></property> </bean> </beans>
使用構造函數注入的前提是Bean必須提供帶參數的構造函數。例如
package com.baobaotao.anno; import org.springframework.beans.factory.BeanNameAware; public class LogonService implements BeanNameAware{ public LogonService(){} public LogonService(LogDao logDao, UserDao userDao) { this.logDao = logDao; this.userDao = userDao; } private LogDao logDao; private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void setLogDao(LogDao logDao) { this.logDao = logDao; } public LogDao getLogDao() { return logDao; } public UserDao getUserDao() { return userDao; } public void setBeanName(String beanName) { System.out.println("beanName:"+beanName); } public void initMethod1(){ System.out.println("initMethod1"); } public void initMethod2(){ System.out.println("initMethod2"); } }
bean.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" 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" default-autowire="byName"> <bean id="logDao" class="com.baobaotao.anno.LogDao"/> <bean id="userDao" class="com.baobaotao.anno.UserDao"/> <bean class="com.baobaotao.anno.LogonService"> <constructor-arg ref="logDao"></constructor-arg> <constructor-arg ref="userDao"></constructor-arg> </bean> </beans>
非靜態工廠方法:
有些工廠方法是非靜態的,即必須實例化工廠類後才能調用工廠放。
package com.baobaotao.ditype; public class CarFactory { public Car createHongQiCar(){ Car car = new Car(); car.setBrand("紅旗CA72"); return car; } public static Car createCar(){ Car car = new Car(); return car; } }
工廠類負責建立一個或多個目標類實例,工廠類方法通常以接口或抽象類變量的形式返回目標類實例,工廠類對外屏蔽了目標類的實例化步驟,調用者甚至不用知道具體的目標類是什麼。
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- 工廠方法--> <bean id="carFactory" class="com.baobaotao.ditype.CarFactory" /> <bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar"> </bean> </beans>
靜態工廠方法:
不少工廠類都是靜態的,這意味着用戶在無須建立工廠類實例的狀況下就能夠調用工廠類方法,所以,靜態工廠方法比非靜態工廠方法的調用更加方便。
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="car6" class="com.baobaotao.ditype.CarFactory" factory-method="createCar"></bean> </beans>
Spring經過@Autowired註解實現Bean的依賴注入,下面是一個例子:
package com.baobaotao.anno; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; //① 定義一個Service的Bean(不須要在XML中定義Bean) @Service public class LogonService implements BeanNameAware{ //② 分別注入LogDao及UserDao的Bean(不須要在XML中定義property屬性注入) @Autowired(required=false) private LogDao logDao; @Autowired @Qualifier("userDao") private UserDao userDao; public LogDao getLogDao() { return logDao; } public UserDao getUserDao() { return userDao; } public void setBeanName(String beanName) { System.out.println("beanName:"+beanName); } public void initMethod1(){ System.out.println("initMethod1"); } public void initMethod2(){ System.out.println("initMethod2"); } }
在①處,咱們使用@Service將LogonService標註爲一個Bean,在②處,經過@Autowired注入LogDao及UserDao的Bean。@Autowired默認按類型匹配的方式,在容器查找匹配的Bean,當有且僅有一個匹配的Bean時,Spring將其注入到@Autowired標註的變量中。
若是容器中沒有一個和標註變量類型匹配的Bean,Spring容器啓動時將報NoSuchBeanDefinitionException的異常。若是但願Spring即便找不到匹配的Bean完成注入也不用拋出異常,那麼可使用@Autowired(required=false)進行標註:
@Service public class LogonService implements BeanNameAware{ @Autowired(required=false) private LogDao logDao; ... }
默認狀況下,@Autowired的required屬性的值爲true,即要求必定要找到匹配的Bean,不然將報異常。
若是容器中有一個以上匹配的Bean時,則能夠經過@Qualifier註解限定Bean的名稱,以下所示:
@Service public class LogonService implements BeanNameAware{ @Autowired(required=false) private LogDao logDao;
//①注入名爲UserDao,類型爲UserDao的Bean @Autowired @Qualifier("userDao") private UserDao userDao; }
這裏假設容器有兩個類型爲UserDao的Bean,一個名爲userDao,另外一個名爲otherUserDao,則①處會注入名爲userDao的Bean。
@Autowired能夠對類成員變量及方法的入參進行標註,下面咱們在類的方法上使用@Autowired註解:
package com.baobaotao.anno; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class LogonService implements BeanNameAware{ private LogDao logDao; private UserDao userDao; @Autowired public void setLogDao(LogDao logDao) { this.logDao = logDao; } @Autowired @Qualifier("userDao") public void setUserDao(UserDao userDao) { System.out.println("auto inject"); this.userDao = userDao; } }
若是一個方法擁有多個入參,在默認狀況下,Spring自動選擇匹配入參類型的Bean進行注入。Spring容許對方法入參標註@Qualifier以指定注入Bean的名稱,以下所示:
@Autowired public void init(@Qualifier("userDao")UserDao userDao,LogDao logDao){ System.out.println("multi param inject"); this.userDao = userDao; this.logDao =logDao; }
在以上例子中,UserDao的入參注入名爲userDao的Bean,而LogDao的入參注入LogDao類型的Bean。
通常狀況下,在Spring容器中大部分的Bean都是單實例的,因此咱們通常都無須經過@Repository、@Service等註解的value屬性爲Bean指定名稱,也無須使用@Qualifier按名稱進行注入。
此外,Spring還支持@Resource和@Inject註解,這兩個標準註解和@Autowired註解的功能類型,都是對類變量及方法入參提供自動注入的功能。@Resource要求提供一個Bean名稱的屬性,若是屬性爲空,則自動採用標註處的變量名或方法名做爲Bean的名稱。
package com.baobaotao.anno; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import org.springframework.stereotype.Component; @Component public class Boss { private Car car; public Boss(){ System.out.println("construct..."); } // @Autowired // private void setCar(Car car){ // System.out.println("execute in setCar"); // this.car = car; // } @Resource("car") private void setCar(Car car){ System.out.println("execute in setCar"); this.car = car; } @PostConstruct private void init1(){ System.out.println("execute in init1"); } @PostConstruct private void init2(){ System.out.println("execute in init1"); } @PreDestroy private void destory1(){ System.out.println("execute in destory1"); } @PreDestroy private void destory2(){ System.out.println("execute in destory2"); } }
這時,若是@Resource未指定"car"屬性,則也能夠根據屬性方法獲得須要注入的Bean名稱。可見@Autowired默認按類型匹配注入Bean,@Resource則按名稱匹配注入Bean。而@Inject和@Autowired同樣也是按類型匹配注入的Bean的,只不過它沒有required屬性。可見不論是@Resource仍是@Inject註解,其功能都沒有@Autowired豐富,所以除非必須,大可沒必要在意這兩個註解。(相似於Xml中使用<constructor-arg ref="logDao"></constructor-arg>或者<property name="logDao" ref="logDao"></property>進行注入,若是使用了@Autowired或者Resource等,這不須要在定義Bean時使用屬性注入和構造方法注入了)
1.@Autowired注入是按照類型注入的,只要配置文件中的bean類型和須要的bean類型是一致的,這時候注入就沒問題。可是若是相同類型的bean不止一個,此時注入就會出現問題,Spring容器沒法啓動。
2.@Resourced標籤是按照bean的名字來進行注入的,若是咱們沒有在使用@Resource時指定bean的名字,同時Spring容器中又沒有該名字的bean,這時候@Resource就會退化爲@Autowired即按照類型注入,這樣就有可能違背了使用@Resource的初衷。因此建議在使用@Resource時都顯示指定一下bean的名字@Resource(name="xxx")
1.在xml配置文件中顯式指定
<!-- 爲了使用Autowired標籤,咱們必須在這裏配置一個bean的後置處理器 --> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" /> <!-- 爲了使用@Resource標籤,這裏必須配置一個後置處理器 --> <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
2.在xml配置文件中使用context:annotation-config
<context:annotation-config />
3.在xml配置文件中使用context:component-scan
<context:component-scan base-package="com.baobaotao.anno"/>
4.重寫Spring容器的Context,在自定義BeanFactory時調用AnnotationConfigUtils.registerAnnotationConfigProcessors()把這兩個註解處理器增長到容器中。
一開始使用公司框架的時候發現能夠在web層使用@Resource以及@Autowired來注入一些bean,首先這個註解是Spring提供的,本身把這部分代碼抽出來寫了小例子,發現要想使用Spring的這兩註解,必須直接或者間接的引入AutowiredAnnotationBeanPostProcesso以及CommonAnnotationBeanPostProcessor這兩個註解處理器引入到BeanDefinitions中,不然不會實現注入的,可是仔細閱讀公司框架代碼後發現沒有地方直接或間接引入這兩個註解處理器,發現一個細節,公司框架所依賴的Spring版本是2.5.6而我使用的Spring版本是2.5.5,當初的結論是高版本的Spring在容器啓動的時候,自動把這兩個註解處理器加入到BeanDefinitions中,這幾天仔細看了看Spring的源代碼,發現Spring2.5.6並無這樣作。而後子寫DEBUG了一下公司框架的源代碼,最後發現原來公司框架有一個本身的XmlWebApplicationContext,在這個context中重寫customizeBeanFactory(),在這個方法中調用了AnnotationConfigUtils.registerAnnotationConfigProcessors()方法把這兩自動註解處理器加入到BeanDefinitions中,這樣公司框架在web層就支持@Resource和@Autowired進行自動注入啦
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.alibaba.citrus.springext.support.context; import com.alibaba.citrus.springext.ResourceLoadingExtendable; import com.alibaba.citrus.springext.ResourceLoadingExtender; import com.alibaba.citrus.springext.support.context.InheritableListableBeanFactory; import com.alibaba.citrus.springext.support.resolver.XmlBeanDefinitionReaderProcessor; import java.io.IOException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourcePatternResolver; public class XmlWebApplicationContext extends org.springframework.web.context.support.XmlWebApplicationContext implements ResourceLoadingExtendable { private ResourceLoadingExtender resourceLoadingExtender; private boolean parentResolvableDependenciesAccessible = true; public XmlWebApplicationContext() { } public boolean isParentResolvableDependenciesAccessible() { return this.parentResolvableDependenciesAccessible; } public void setParentResolvableDependenciesAccessible(boolean parentResolvableDependenciesAccessible) { this.parentResolvableDependenciesAccessible = parentResolvableDependenciesAccessible; } public void setResourceLoadingExtender(ResourceLoadingExtender resourceLoadingExtender) { if(this.resourceLoadingExtender != null) { this.getApplicationListeners().remove(this.resourceLoadingExtender); } this.resourceLoadingExtender = resourceLoadingExtender; if(resourceLoadingExtender instanceof ApplicationListener) { this.addApplicationListener((ApplicationListener)resourceLoadingExtender); } } protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) { (new XmlBeanDefinitionReaderProcessor(beanDefinitionReader)).addConfigurationPointsSupport(); } protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { super.customizeBeanFactory(beanFactory);
//AnnotationConfigUtils.registerAnnotationConfigProcessors()方法把這兩自動註解處理器加入到BeanDefinitions中,這樣公司框架在web層就支持@Resource和@Autowired進行自動注入 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory, (Object)null); } protected DefaultListableBeanFactory createBeanFactory() { return (DefaultListableBeanFactory)(this.isParentResolvableDependenciesAccessible()?new InheritableListableBeanFactory(this.getInternalParentBeanFactory()):super.createBeanFactory()); } protected Resource getResourceByPath(String path) { Resource resource = null; if(this.resourceLoadingExtender != null) { resource = this.resourceLoadingExtender.getResourceByPath(path); } if(resource == null) { resource = super.getResourceByPath(path); } return resource; } protected ResourcePatternResolver getResourcePatternResolver() { final ResourcePatternResolver defaultResolver = super.getResourcePatternResolver(); return new ResourcePatternResolver() { public Resource[] getResources(String locationPattern) throws IOException { ResourcePatternResolver resolver = null; if(XmlWebApplicationContext.this.resourceLoadingExtender != null) { resolver = XmlWebApplicationContext.this.resourceLoadingExtender.getResourcePatternResolver(); } if(resolver == null) { resolver = defaultResolver; } return resolver.getResources(locationPattern); } public ClassLoader getClassLoader() { return defaultResolver.getClassLoader(); } public Resource getResource(String location) { return defaultResolver.getResource(location); } }; } }