本篇將介紹Spring框架中創建bean的其他方式,包括基於註解的方法、工廠方法(靜態工廠、實例工廠)、利用FactoryBean創建實例。
一,基於註解的方式配置Bean
Spring能夠從classpath下自動掃描、偵測和實例化具有特定註解的組件,特定組件包括:@Component:基本註解,標識了一個受Spring管理的組件;@Respository:建議標識持久層組件;@Service:建議標識服務層組件;@Controller:建議標識表現層組件。對於掃描到的組件,Spring有默認的命名策略:使用非限定類名,第一個字母小寫,如:User對應user,也可以在註解中通過value屬性標識組件的名稱。 下面先簡單演示註解的用法。
實例類:
package cn.pb.anno;
import org.springframework.stereotype.Component;
@Respository
public class Car {
private String brand;
private double price;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
接口:
package cn.pb.anno;
public interface AnnotationTest {
public void method();
}
接口實現類:
package cn.pb.anno;
import org.springframework.stereotype.Component;
@Component
public class annotationTestImpl implements AnnotationTest {
@Override
public void method() {
System.out.println("test...");
}
}
xml配置:
<context:component-scan base-package="cn.pb.anno"></context:component-scan>
測試類:
package cn.pb.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.pb.anno.AnnotationTest;
import cn.pb.anno.Car;
public class ConfigurationTest {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("ConfigurationTest.xml");
Car car= (Car) ctx.getBean("car");
AnnotationTest ant=(AnnotationTest) ctx.getBean("annotationTestImpl");
System.out.println(car);
ant.method();
}
}
輸出結果:cn.pb.anno.Car@73a54cb6
test...
表明實例化成功。
xml配置中的base-package屬性指定一個需要掃描的基類包,Spring容器將會掃描這個基類包裏以及子包中的所有類,當需要掃描多個包時可以用逗號隔開。如果僅希望掃描特定的類而非基包下所有的類,可以使用resource-pattern屬性過濾特定的類,例如:<context:component-scan base-package="cn.pb.anno" resource-pattern="test/*.class"/>
表示只掃描cn.pb.anno的子包test包下的所有類。component-scan還有兩個子節點include-filter和exclude-filter,前者表示要包含的目標類(注意並不是意味着排除其他類,只是要把目標類包含在內),後者表示要排除在外的目標類。如上的配置文件如果改爲:
<context:component-scan base-package="cn.pb.anno" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
與use-default-filters=”false”配合使用,表示只實例化包中的註解爲@Respository的類,所以註解爲@Component的annotationTestImpl是不能被實例化的。而將如上xml配置改爲:
<context:component-scan base-package="cn.pb.anno">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
表示排除註解爲@Repository的類,所以此時Car是不能被實例化的。兩個節點中的type屬性值還可以是type=”assignable”,expression的值是某個接口, 表示包含或者排除的類型是指定的接口及接口的實現類。component-scan下可以擁有若干個include-filter和exclude-filter。
考慮複雜的情況,當一個類中有對另一個類的引用時,例如在User類中有對上述Car類的引用,代碼如下:
package cn.pb.anno;
import org.springframework.stereotype.Repository;
@Repository
public class User {
private String name;
private int age;
private Car car;
//對應的setter,getter
...
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
此時若直接實例化User對象,將得到結果:User [name=null, age=0, car=null]
,這表明Car類型的屬性沒有被實例化。因此需要在Car屬性前添加@Autowired註解,再實例化將得到結果:User [name=null, age=0, car=Car [brand=null, price=0.0]]
,表明car屬性已被實例化。@Autowired註解自動轉配具有兼容類型的單個Bean,同時,構造器、普通字段(即使是非public)、一切具有參數的方法都可以應用。要注意,默認情況下所有使用@Autowired註解的屬性都需要被設置,當Spring找不到匹配的Bean裝配屬性時,會拋出異常,例如將上面例子中Car的@Respository註解去掉,實例化User時就會出現異常。若允許其不被設置,可以設置@Autowired註解的required屬性值爲false,即@Autowired(required=false)。
當IOC容器裏存在多個類型兼容的Bean時,例如繼承自同一個類的多個Bean或者實現同一個接口的多個Bean,可以在@Qualifier註解裏提供Bean的名稱,Spring就會實例化對應的Bean。@Autowired註解也可以應用在數組類型的屬性上,此時Spring將會把所有匹配的Bean進行自動裝配@Autowired註解也可以應用在集合屬性上,此時Spring讀取該幾個的類型信息,然後自動裝配所有與之兼容的Bean。@Autowired註解用在java.util.Map上時,若該Map的鍵值爲String,那麼Spring將自動裝配與之Map值類型兼容的Bean,此時Bean的名稱作爲鍵值。
二,工廠方法
首先創建一個需要被實例化的類Car:
package cn.pb.bean.factory;
public class Car {
private String brand;
private double price;
public Car(String brand, double price) {
super();
this.brand = brand;
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", price=" + price + "]";
}
}
①靜態工廠方法
直接調用某一個類的靜態方法就可以返回Bean的實例。
創建靜態工廠類:
package cn.pb.bean.factory;
import java.util.HashMap;
import java.util.Map;
public class StaticFactory {
private static Map<String, Car> cars=new HashMap<String, Car>();
static{
cars.put("audi", new Car("audi", 300000));
cars.put("dazhong", new Car("dazhong", 200000));
}
public static Car getBean(String brand){
return cars.get(brand);
}
}
配置xml文件:
<bean id="car" class="cn.pb.bean.factory.StaticFactory" factory-method="getBean">
<constructor-arg value="audi"></constructor-arg>
</bean>
class屬性指向靜態工廠方法的全類名,factory-method屬性執行靜態工廠方法的名字,如果工廠方法需要傳入參數,則使用constructor-arg來配置參數。
測試類:
package cn.pb.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.pb.bean.factory.Car;
public class ConfigurationTest {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("ConfigurationTest.xml");
Car car= (Car) ctx.getBean("car");
System.out.println(car);
}
}
執行結果:
Car [brand=audi, price=300000.0]
②實例工廠方法
需要先創建工廠本身,再調用工廠的實例方法來返回bean的實例。
創建工廠類:
package cn.pb.bean.factory;
import java.util.HashMap;
import java.util.Map;
public class InstanceFactory {
private Map<String, Car> cars=null;
public InstanceFactory(){
cars=new HashMap<String, Car>();
cars.put("audi", new Car("audi", 300000));
cars.put("dazhong", new Car("dazhong", 200000));
}
public Car getBean(String brand){
return cars.get(brand);
}
}
配置xml文件:
<!-- 配置工廠實例 -->
<bean id="beanFactory" class="cn.pb.bean.factory.InstanceFactory"></bean>
<!-- 通過實例工廠方法來配置bean -->
<bean id="car1" factory-bean="beanFactory" factory-method="getBean">
<constructor-arg value="audi"></constructor-arg>
</bean>
測試結果:
Car [brand=audi, price=300000.0]
這兩種方法事實上就是在工廠中創建好Bean的實例,通過方法的調用來對外提供Bean的對象,上面的例子都是根據傳入的字符串來找到實例對象返回給Spring容器,這兩種方法最大的區別就是靜態工廠方法不需要創建工廠類本身的實例,而實例工廠方法需要配置工廠實例。
三,使用FactoryBean創建實例
FactoryBean是有Spring提供的一個接口,一般的bean 直接用xml配置即可,如果一個bean的創建過程中涉及到很多其他的bean 和複雜的邏輯,用xml配置比較困難,這時可以考慮用FactoryBean。一下簡單演示如何使用FactoryBean。
定義實現FactoryBean接口的類:
package cn.pb.bean.factory;
import org.springframework.beans.factory.FactoryBean;
public class CarFactoryBean implements FactoryBean<Car> {
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
//返回的bean對象
@Override
public Car getObject() throws Exception {
// TODO Auto-generated method stub
return new Car(brand, 500000);
}
//返回的bean類型
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Car.class;
}
//是否是單實例的
@Override
public boolean isSingleton() {
return true;
}
}
配置xml文件:
<bean id="car2" class="cn.pb.bean.factory.CarFactoryBean">
<property name="brand" value="BMW"></property>
</bean>
測試結果:
Car [brand=BMW, price=500000.0]
Spring提供了多種多樣的創建實例的方式,對於指定全類名的方式創建Bean的方法而言,其思路比較清晰,方便快速查找Bean對應的實體類,但當需要創建的Bean過多時,會使xml文件顯得雜亂。而對於使用註解的方式創建Bean,其方法顯得簡單了許多,但使用者必須清楚爲哪些類添加了註解,即哪些類被加載到了Spring容器中,這樣才能在使用時得心應手。通過指定全類名的方式和基於註解的方式創建Bean的方法是較爲常用的創建Bean的方式,個人認爲,掌握一到兩種方法,應用時採用統一的一種方法來創建Bean會大大減少工作量,也會大大提高代碼的可讀性。