1. 程式人生 > >Spring學習6(5):FactoryBean及使用註解配置

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個介面方法:

  1. bollean isSingleton():確定由工廠建立的Bean是singleton還是prototype
  2. T object():返回工廠建立的Bean,如果是singleton Bean則該例項會放到Spring容器中的單例項快取池中。
  3. 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身份的標註:

  1. @Repository:Dao實現類的標註
  2. @Service:Service實現類的標註
  3. @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可以有如下種類:

  1. annotation:如com.smart.XxxAnnotation,是對所有標註了XxxAnnotation的類,該型別採用目標類是否標註了某個註解來進行過濾。
  2. assignable:如com.smart.XxxService是針對所有繼承或擴充套件XxxService的類,該型別採用目標是否繼承或擴充套件了某個特定類進行過濾。
  3. aspectj:如com.smart..*Service+,針對所有類名以Service結束的類及繼承或擴充套件它們的類。
  4. regex:如com/.smart/.anno/..*,針對所有com.smart.anno類包下的類,是採用正則表示式對類名進行過濾。
  5. 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();
		
	}
}