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();
		
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值