Spring通过反射机制利用
的class属性来指定实现类的方法在Bean的实例化过程较为复杂时会增加编码繁琐度。故此Spring提供了一个org.springframework.beans.factory.FactoryBean
工厂类接口,用户可以通过实现该工厂类接口定制实例化Bean的逻辑。
在spring3.0以后,FactoryBean开始支持泛型,即是接口声明改为FactoryBean
的形式。该接口中共定义了3个接口方法:
bollean isSingleton()
:确定由工厂创建的Bean是singleton还是prototypeT object()
:返回工厂创建的Bean,如果是singleton Bean则该实例会放到Spring容器中的单实例缓存池中。Class>getObjectType()
:返回FactoryBean创建Bean的类型。注意如果配置的实现类是factoryBean的时候,使用getBean()方法获得的Bean是FactoryBean.getObject()方法返回的对象。
比如对于car的配置,我们不想利用P:
或者是
这些方式来配置属性,而直接用逗号分割的方法来配置的话,就可以通过一个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命名空间:
<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">
<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: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可以有如下种类:
com.smart.XxxAnnotation
,是对所有标注了XxxAnnotation的类,该类型采用目标类是否标注了某个注解来进行过滤。com.smart.XxxService
是针对所有继承或扩展XxxService的类,该类型采用目标是否继承或扩展了某个特定类进行过滤。com.smart..*Service+
,针对所有类名以Service结束的类及继承或扩展它们的类。com/.smart/.anno/..*
,针对所有com.smart.anno类包下的类,是采用正则表达式对类名进行过滤。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">
使用注解可以更方便的完成依赖注入,如我们在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标注的变量中。
如果容器中没有一个和标注变量类型匹配的Bean,那么Spring回报出NoSuchBeanDefinitionException
异常。如果希望spring没有找到匹配的Bean也不抛出异常可以使用如下方式:
public class LogonService{
@Autowired(required=false)
private LogDao logDao;
如果容器中有一个以上匹配的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默认的作用范围是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();
}
}