自動化裝配的確有很大的便利性,可是卻並不能適用在全部的應用場景,好比須要裝配的組件類不是由本身的應用程序維護,而是引用了第三方的類庫,這個時候自動裝配便沒法實現,Spring對此也提供了相應的解決方案,那就是經過顯示的裝配機制——Java配置和XML配置的方式來實現bean的裝配。spring
咱們仍是藉助上篇博文中的老司機開車的示例來說解。Car接口中有開車的drive方法,該接口有兩個實現——QQCar和BenzCarapp
package spring.impl; import spring.facade.Car; public class QQCar implements Car { @Override public void drive() { System.out.println("開QQ車"); } }
既然是經過Java代碼來裝配bean,那就是否是咱們上一篇講的經過組件掃描的方式來發現應用程序中的bean的自動裝配機制了,而是須要咱們本身經過配置類來聲明咱們的bean。咱們先經過@Configuration註解來建立一個Spring的配置類,該類中包含了bean的建立細節——ide
import org.springframework.context.annotation.Configuration; import spring.facade.Car; import spring.impl.QQCar; /** * @Configuration 代表該類是Spring的一個配置類,該類中會包含應用上下文建立bean的具體細節 * @Bean 告訴Spring該方法會返回一個要註冊成爲應用上下文中的bean的對象 */ @Configuration public class CarConfig { @Bean public Car laoSiJi() { return new QQCar(); } }
以上類中建立的bean實例默認狀況下和方法名是同樣的,咱們也能夠經過@Bean註解的name屬性自定義ID,例如 @Bean(name = "chenbenbuyi") ,那麼在獲取bean的時候根據你本身定義的ID獲取便可。接着咱們測試——測試
package spring.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import spring.config.CarConfig; import spring.facade.Car; public class CarTest { @Test public void carTest() { ApplicationContext context = new AnnotationConfigApplicationContext(CarConfig.class); //根據ID從容器容獲取bean Car car = (Car) context.getBean("chenbenbuyi"); car.drive(); } }
以上測試可以成功輸出,這就代表咱們可以獲取到QQCar的實例對象的,而這也是最簡單的基於Java配置類來裝配bean的示例了。可是你可能會說,明明是咱們本身建立的Car的實例,怎麼就成了Spring爲咱們建立的呢?好吧,咱們把@Bean註解拿開,測試固然是沒法經過,會拋NoSuchBeanDefinitionException異常。這裏,你可能須要好好理解控制反轉的思想了:由於如今對於bean建立的控制權咱們是交給了Spring容器的,若是沒有@Bean註解,方法就只是一個普通方法,方法體返回的實例對象就不會註冊到應用上下文(容器)中,也就說,Spring不會爲咱們管理該方法返回的實例對象,當咱們在測試類中向容器伸手要對象的時候,天然就找不到。this
上述示例過於簡單,如今,咱們要更進一步,給簡單的對象添加依賴,來完成稍微複雜一點的業務邏輯。車是須要老司機來開的,因而咱們同上篇同樣定義一個Man類,Man的工做就是開車——
spa
package spring.impl; import spring.facade.Car; public class Man { private Car car;public Man(Car car) { this.car = car; } public void work() { car.drive(); } }
Car的對象實例是經過構造器注入,而Car的實例對象在配置類中經過方法laoSiJi()返回,因此咱們在配置類中能夠直接調用laoSiJi方法獲取bean注入到Man的實例對象——code
package spring.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import spring.facade.Car; import spring.impl.BenzCar; import spring.impl.Man; @Configuration public class CarConfig { @Bean public Car laoSiJi() { return new BenzCar(); } @Bean public Man work() { return new Man(laoSiJi()); } }
測試類中經過上下文對象的getBean("work")方法就能夠獲取到Man的實例對象,從而完成對老司機開車的測試。或許,你會以爲,work方法是經過調用laoSiJi方法才獲取的Car的實例的,實際上並不是如此。由於有了@Bean註解,Spring會攔截全部對該註解方法的調用,直接返回該方法建立的bean,也即容器中的管理的bean。也就是說,laoSiJi方法返回的bean交給了Spring容器管理後,當其餘地方須要實例對象的時候,是直接從容器中獲取的第一次調用方法產生的實例對象,而不會重複的調用laoSiJi方法。咱們能夠以下測試——component
package spring.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import spring.facade.Car; import spring.impl.BenzCar; import spring.impl.Man; @Configuration public class CarConfig { @Bean public Car laoSiJi() { System.out.println("方法調用"); return new BenzCar(); } @Bean public Man work() { return new Man(laoSiJi()); } @Bean public Man work2() { return new Man(laoSiJi()); } }
如上測試你會發現,雖然我定義了兩個方法來獲取Man實例,可是控制檯只輸出了一次調用打印,即證實方法只在最初返回bean的時候被調用了一次,然後的實例獲取都是直接從容器中獲取的。這也就是默認狀況下Spring返回的實例都是單例的緣由:一旦容器中註冊了實例對象,應用程序須要的時候,就直接給予,不用重複建立。固然,不少狀況下咱們不會如上面的方式去引入依賴的bean,而可能會經過參數注入的方式,這樣你就能夠很靈活的使用不一樣的裝配機制來知足對象之間的依賴關係,好比下面這種自動裝配的方式給Man的實例注入依賴的Car對象——xml
package spring.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import spring.facade.Car; import spring.impl.Man; @Configuration @ComponentScan("spring.impl") public class CarConfig { @Bean public Man work(Car car) { return new Man(car); } }
固然,若是你喜歡去簡就繁,也能夠經過XML配置文件配置依賴的bean。下面再來看看XML的方式如何裝配bean。對象
使用XML配置文件的方式裝配bean,首要的就是要建立一個基於Spring配置規範的XML文件,該配置文件以<beans>爲根元素(至關於Java配置的@Configuration註解),包含一個或多個<bean>元素(至關於配置類中@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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--經過類的全限定名來聲明要建立的bean--> <bean class="spring.impl.BenzCar"></bean> </beans>
而後,從基於XML的配置文件中加載上下文定義,咱們就能根據ID獲取到對應的bean了——
package spring.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import spring.facade.Car; public class CarTest { @Test public void carTest() { ApplicationContext context = new ClassPathXmlApplicationContext("resource/applicationContext.xml"); //XML的方式若是沒有明確給定ID,默認bean的ID會根據類的全限定名來命名,以#加計數序號的方式命名。 Car car = (Car)context.getBean("spring.impl.BenzCar#0"); car.drive(); } }
固然,示例中使用自動化的命名ID看起來逼格滿滿,但其實並不實用,若是須要引用bean的實例就有點操蛋了,實際應用中固然仍是要藉助<bean>的id屬性來自定義命名。
給<bean>元素設置id屬性,在構建另外的對象實例的時候,就能夠很方便的引用,譬如上面基於Java的配置中的構造器注入,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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="spring.impl.BenzCar"></bean> <bean id="man" class="spring.impl.Man"> <!--經過Man的構造器注入Car的實例對象--> <constructor-arg ref="car"></constructor-arg> </bean> </beans>
而有時候咱們並不必定都是將對象的引用裝配到依賴對象中,也能夠簡單的注入字面值——
package spring.impl; import spring.facade.Car; public class Man { private Car car; private String str;
public Man(String str ,Car car) { this.car = car; this.str = str; } public void work() { System.out.println(str); car.drive(); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="spring.impl.BenzCar"></bean> <bean id="man" class="spring.impl.Man"> <!--分別注入字面值和對象的應用--> <constructor-arg value="陳本布衣"></constructor-arg> <constructor-arg ref="car"></constructor-arg> </bean> </beans>
接着,咱們繼續對已有代碼作些改動,將注入的參數改成Car的List集合——
public Man(List<Car> cars) { this.cars = cars; }
那麼配置文件就能夠這樣配置——
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="benzCar" class="spring.impl.BenzCar"></bean> <bean id="qqCar" class="spring.impl.QQCar"></bean> <bean id="man" class="spring.impl.Man"> <!--經過<list>子元素實現List集合對象的裝配--> <constructor-arg> <list> <ref bean="benzCar"/> <ref bean="qqCar"/> </list> </constructor-arg> </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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="benzCar" class="spring.impl.BenzCar"></bean> <bean id="qqCar" class="spring.impl.QQCar"></bean> <bean id="man" class="spring.impl.Man"> <!--經過<list>子元素實現List集合字面值的裝配--> <constructor-arg> <list> <value>這裏直接填寫字面值</value> <value>陳本布衣</value> </list> </constructor-arg> </bean> </beans>
咱們能夠採用一樣的方式裝配Set集合,只是Set集合會忽略掉重複的值,並且順序也不保證。此處不作演示。
構造器注入是一種強依賴注入,而不少時候咱們並不傾向於寫那種依賴性太強的代碼,而屬性的Setter方法注入做爲一種可選性依賴,在實際的開發中是應用得很是多的。上面Man類若是要經過屬性注入的方式注入Car的實例,就該是這樣子——
package spring.impl; import spring.facade.Car; public class Man { private Car car; public void setCar(Car car) { this.car = car; } public void work() { car.drive(); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="benzCar" class="spring.impl.BenzCar"></bean> <bean id="qqCar" class="spring.impl.QQCar"></bean> <bean id="man" class="spring.impl.Man"> <!--經過屬性注入的方式注入Car的實例--> <property name="car" ref="benzCar"></property> </bean> </beans>
以上示例中,XML配置文件中屬性注入的屬性名必需要和Java類中Setter方法對應的屬性名一致。而對於字面量的注入,和上面構造器的方式相似,只不過使用的元素名換成了<property>而已,下面僅作展現——
<bean id="man" class="spring.impl.Man"> <property name="str" value="字面量的注入"></property> <property name="list"> <list> <value>集合的字面量注入1</value> <value>集合的字面量注入2</value> </list> </property> </bean>
<bean id="benzCar" class="spring.impl.BenzCar"></bean> <bean id="qqCar" class="spring.impl.QQCar"></bean> <bean id="man" class="spring.impl.Man"> <!--屬性注入的方式注入集合--> <property name="cars"> <list> <ref bean="qqCar"></ref> <ref bean="benzCar"></ref> </list> </property> </bean>
在同一個應用程序中,Spring常見的這三種裝配方式咱們可能都會用到,而對於不一樣的裝配方式,他們之間如何實現相互引用從而整合到一塊兒的呢?咱們先看看Java配置類的引用問題。試想若是Java配置類中的bean數量過多,咱們可能會考慮拆分。在本文的示例中,Man類實例的建立必須經過構造器注入Car的實例,若是把兩個實例的產生分紅兩個配置類,那麼在依賴注入的配置類中能夠經過@Import註解引入被依賴的配置類——
package spring.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import spring.facade.Car; import spring.impl.Man; @Configuration @Import(CarConfig.class) //經過@Import註解引入產生Car實例的配置類 public class ManConfig { @Bean public Man work(Car car) { return new Man(car); } }
可是若是Car的實例不是經過Java類配置的,而是經過XML方式配置的方式配置,咱們只需經過@ImportResource註解將配置bean的XML文件引入便可,只不過這個時候要保證XML中被依賴的bean的id要和Java配置類中的形參保持一致——
package spring.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import spring.facade.Car; import spring.impl.Man; @Configuration @ImportResource("classpath:resource/applicationContext.xml") public class ManConfig { @Bean public Man work(Car car) { return new Man(car); } }
而若是bean是採用XML進行裝配,若是須要裝配的bean過多,咱們固然仍是會根據業務拆分紅不一樣的配置文件,而後使用<improt>元素進行不一樣XML配置文件之間的引入,形如: <import resource="classpath:xxx.xml" /> ;而若是要在XML中引入Java配置,只需將Java配置類當成普通的bean在XML中進行聲明便可,可是在測試的時候要注意開啓組件掃描,由於加載XML配置的上下文對象只會加載XML配置文件中的bean定義,沒法讓基於Java配置類產生bean的裝配機制自動生效——
<?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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--開啓組件掃描,在測試的時候配置類才能向容器中註冊類中聲明的bean--> <context:component-scan base-package="spring"/> <!--XML中引入Java配置類:將配置類聲明爲bean--> <bean class="spring.config.CarConfig"></bean> <bean id="man" class="spring.impl.Man"> <constructor-arg ref="laoSiJi"></constructor-arg> </bean> </beans>
最後說一點,不論是Java配置仍是XML配置,有個一般的作法就是建立一個比全部配置都更高層次的根配置類/文件,該配置不聲明任何的bean,只用來將多個配置組合在一塊兒,從而讓配置更易於維護和擴展。好了,以上即是兩種bean的裝配方式的簡單講解,若有紕漏,歡迎指正,不勝感激。