Spring學習6(5):FactoryBean及使用註解配置
Spring學習6(5)
FactoryBean
Spring通過反射機制利用<bean>
的class屬性來指定實現類的方法在Bean的例項化過程較為複雜時會增加編碼繁瑣度。故此Spring提供了一個org.springframework.beans.factory.FactoryBean
工廠類介面,使用者可以通過實現該工廠類介面定製例項化Bean的邏輯。
在spring3.0以後,FactoryBean開始支援泛型,即是介面宣告改為FactoryBean<T>
的形式。該介面中共定義了3個介面方法:
bollean isSingleton()
:確定由工廠建立的Bean是singleton還是prototypeT object()
:返回工廠建立的Bean,如果是singleton Bean則該例項會放到Spring容器中的單例項快取池中。Class<?>getObjectType()
:返回FactoryBean建立Bean的型別。
注意如果配置的實現類是factoryBean的時候,使用getBean()方法獲得的Bean是FactoryBean.getObject()方法返回的物件。
例子
比如對於car的配置,我們不想利用P:
或者是<property>
這些方式來配置屬性,而直接用逗號分割的方法來配置的話,就可以通過一個FactoryBean來達到目的。
我們在com.smart.fb中建立一個CarFactoryBean.java檔案並最好將Car.java檔案也放入同一資料夾,而後在CarFactoryBean.java中寫入如下程式碼:
package com.smart.fb;
import org.springframework.beans.factory.FactoryBean;
public class CarFactoryBean implements FactoryBean<Car>{
private String carInfo;
//接收逗號分隔的屬性設定資訊
public String getCarInfo() {
return this.carInfo;
}
//例項化Car Bean
public Car getObject() throws Exception{
Car car = new Car();
String[] infos = carInfo.split(",");
car.setBrand(infos[0]);
car.setMaxSpeed(Integer.parseInt(infos[1]));
car.setPrice(Double.parseDouble(infos[2]));
return car;
}
public Class<Car> getObjectType(){
return Car.class;
}
public boolean isSingleton() {
return false;
}
}
這之後就可以在配置檔案中寫入如下的配置資訊:
<bean id="car1" class="com.smart.fb.CarFactoryBean" p:carInfo="HongQi,200,20000.2"/>
基於註解的配置
使用註解
spring容器啟動的三大要件分別是Bean定義資訊,Bean實現及Spring本身。基於XML的配置是將Bean定義資訊和Bean的實現類分開;基於基於註解的配置是將Bean定義資訊通過在Bean實現類上標註註解出來。 如下面使用註解定義一個DAO的Bean:UserDao.java:
package com.smart.anno;
import org.springframework.stereotype.Component;
//通過Repository定義一個DAO的Bean
@Component("userDao")
public class UserDao{
}
這裡使用@Component
註解進行標註,它可以被Spring容器識別,自動將POJO轉換為容器管理的Bean。
除了@Component
,Spring還有3個功能基本和它一樣但是為了清晰Bean身份的標註:
@Repository
:Dao實現類的標註@Service
:Service實現類的標註@Controller
:Controller實現類的標註
掃描註解
那麼spring容器如何知道那些類有註解呢,於是spring提供了一個context名稱空間,通過掃描類包來應用註解,其需要在beans中宣告context名稱空間:
<?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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 掃描包以應用註解的Bean -->
<context:component-scan base-package="com.smart.anno"/>
</beans>
通過context名稱空間的component-scan的base-package屬性指定一個需要掃描的基類包,Spring掃描基類包並獲取其中Bean的定義資訊。 如果我們進希望掃描特定類而非基類包中的所有類,可以使用resource-pattern來過濾如下:
<context:component-scan base-package="com.smart" resource-pattern="anno/*.class">
這裡將基類包設為com.smart,預設的resource-pattern為“**/*.class”,這個程式碼就只會去掃描anno子包中的類。
但是上述的resource-pattern並不能過濾特定的類,如類包中實現了XxxService介面的類或標註了某個特定註解的類。我們可以通過context:include-filter>
(表示要包含的目標類)和<context:exclude-filter>
(表示要排除的目標類)實現篩選:
<context:component-scan base-package="com.smart">
<context:include-filter type="regex" expression="com/.smart/.anno.*"/>
<context:exclude-filter type="aspectj" expression="com.smart..*Controller+"/>
</context:component-scan>
這裡的type可以有如下種類:
- annotation:如
com.smart.XxxAnnotation
,是對所有標註了XxxAnnotation的類,該型別採用目標類是否標註了某個註解來進行過濾。 - assignable:如
com.smart.XxxService
是針對所有繼承或擴充套件XxxService的類,該型別採用目標是否繼承或擴充套件了某個特定類進行過濾。 - aspectj:如
com.smart..*Service+
,針對所有類名以Service結束的類及繼承或擴充套件它們的類。 - regex:如
com/.smart/.anno/..*
,針對所有com.smart.anno類包下的類,是採用正則表示式對類名進行過濾。 - custon:如
com.smart.XxxTypeFilter
,採用XxxTypeFileter程式碼方式實現過濾規則,需要實現org.springframework.core.type.TypeFilter
介面。
除此之外,其還有一個use-default-filters屬性,其預設為true,會對標註@Component,
,@Controller
,@Service
,@Reposity
進行掃描,所以如果是下列程式碼,則其include-fliter失去了作用:
<context:component-scan base-package="com.smart">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
所以必須要將use-default-filters改變:
<context:component-scan base-package="com.smart" use-default-filters="false">
自動配置Bean
使用@Autowired進行自動注入
使用註解可以更方便的完成依賴注入,如我們在Logon中注入UserDao和LogDao:
package com.smart.anno;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LogonService{
@Autowired
private LogDao logDao;
@Autowired
private UserDao userDao;
}
````@Autowired```預設按型別匹配的方式在容器中查詢匹配的Bean,當有且只有一個匹配的Bean時,會將其注入@Autowired標註的變數中。
使用@Autowired的required屬性
如果容器中沒有一個和標註變數型別匹配的Bean,那麼Spring回報出NoSuchBeanDefinitionException
異常。如果希望spring沒有找到匹配的Bean也不丟擲異常可以使用如下方式:
public class LogonService{
@Autowired(required=false)
private LogDao logDao;
使用@Qualifier指定注入Bean的名稱
如果容器中有一個以上匹配的Bean時,可以通過@Qualifier
註解限定Bean的名稱:
import org.springframework.beans.factory.annotation.Qualifier;
...
@Autowired
@Qualifier("userDao")
private UserDao userDao;
對類方法進行標註
除了對成員變數進行注入,@Autowired
還可以在類的方法上進行註解,程式碼如下:
@Service
public class LogonService{
private LogDao logDao;
private UserDao userDao;
@Autowired
public void setLogDao(LogDao logDao) {
this.logDao = logDao;
}
@Autowired
@Qualifier("userDao")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
這樣就實現了對入參的注入,可以讓其不是注入於私人屬性,更容易測試和更改。 如果一個方法擁有多個入參,則在預設情況下,將自動匹配入參,但也允許指定名稱注入,例項如下:
@Autowired
public void init(@Qualifier("userDao")UserDao userDao,LogDao logDao) {
System.out.println("lalala");
this.userDao = userDao;
this.logDao = logDao;
}
對集合類進行標註
如果對類中集合類的變數或方法入參經過@Autowired
標註,那麼spring會將容器中所有匹配的Bean都注入進來。
為了示例,我們先定義一個名為Plugin.java的介面,其中程式碼如下:
package com.smart.anno;
public interface Plugin{
}
而後定義OnePlugin和TwoPlugin實現這個介面:
package com.smart.anno;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value=1)//指定載入順序,值越小,優先被載入
public class OnePlugin implements Plugin{
}
上述是OnePlugin.java的程式碼,這裡的order就是來規定Bean的載入順序的,TwoPlugin.java中就是value=2。 最後我們來建立一個注入集合的java檔案,其中程式碼如下:
package com.smart.anno;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
@Component
public class MyComponent{
@Autowired(required=false)
private List<Plugin> plugins;
@Autowired
private Map<String,Plugin> pluginMaps;
public List<Plugin> getPlugins(){
return this.plugins;
}
}
Spring在發現物件是集合後,會將所有容器中匹配元素型別的Bean都注入進來。特別注意這裡Map中的Key是Bean的id, value是Bean。
延遲依賴注入
spring4.0後支援延遲依賴注入,在spring容器啟動時,對於在Bean上標註@Lazy的屬性就不會立即注入屬性值,直到使用的時候才注入。特別注意這裡@Lazy不光要打在Bean上,還要打在屬性Bean上如:
@Lazy
@Component("logonDao")
public class LogDao{
...
@Lazy
@Autowired
public void setLogDao(LogDao logDao) {
this.logDao = logDao;
}
...
Bean作用範圍及生命過程
通過註解方式配置的Bean預設的作用範圍是singleton,spring也提供了@Scope註解來顯示指定作用範圍。 如:
@Scope("prototype")
@Component
public class Car {
在使用xml配置方法配置時,還可以指定init-method和destroy-method方法,同樣在註解配置中,也有類似的功能:@PostConstruct
和@PreDestroy
方法。並且可以定義多個,例項程式碼如下:
package com.smart.anno;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Boss{
private Car car;
public Boss() {
System.out.println("construct");
}
@Autowired
public 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 init2");
}
@PreDestroy
private void destrory1(){
System.out.println("execute in destroy1");
}
@PreDestroy
private void destroy2() {
System.out.println("execute in destroy2");
}
}
完成後可以使用下面程式碼進行測試:
package com.smart.anno;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
public class SimpleTest{
@Test
public void test_pro_pre() {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("com/smart/anno/beans.xml");
((ClassPathXmlApplicationContext)ctx).destroy();
}
}