本篇将介绍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会大大减少工作量,也会大大提高代码的可读性。