Author:Willam2004
引言:
FactoryBean:我们在使用Spring过程中一般都是使用基本的的配置,在Spring配置中,还有一种特殊的FactoryBean,这种bean,可以动态的帮我们创建我们需要的bean,如: ProxyFactoryBean,通用的用于获得AOP代理的工厂bean。可以方便帮我们配置AOP的拦截类.
factorybean关键的是接口 org.springframework.beans.factory.FactoryBean,它有两个重要的方法:
Object getObject() throws Exception;
Class getObjectType();
getObject是要实际返回的bean对象
getObjectType是返回Bean对象对应的ObjectType.
原理可以参考:http://dengyin2000.iteye.com/blog/47443
JMock:http://www.jmock.org/
JMock是我们单元测试中经常用到的一个Mock框架.
问题:
单元测试的一个重要难点就是外部接口的依赖,为了保证单元测试的持续有效,我们对外部接口的都要进行Mock.原始的mock,是在单元测试中,直接将接口手动实现一个mock类,在单元测试中再进行注入.但这种缺点是
,如果接口有变更,如新增方法(比较频繁),原有的实现类就需要重新编写.对于依赖二方库,因为更新的延迟,还会导致单元测试报错,需要重新做版本进行发布,耗时耗力.使用jmock就可以避免这个问题,因为你不需要手动再实现这个类,只需要对你关注的方法进行断言就可以了.如代码:
我们需要测试类:
public class RealService {
private MorganService morganService;
/**
* @param morganService the morganService to set
*/
public void setMorganService(MorganService morganService) {
this.morganService = morganService;
}
public void doExecute(String memberId) {
String s = morganService.findMemberById(memberId);
System.out.println(s);
}
}
其中MorganService服务就是我们依赖的接口类,采用jmock的测试如:
public class RealServiceTest extends TestCase {
private RealService service;
/**
* Test method for {@link com.alibaba.RealService#doExecute(java.lang.String)}.
*/
public void testDoExecute() {
Mockery context = new Mockery();
//Mockery是jmock的context,它负责mock对象的创建和mock检查.
final MorganService morganService = context.mock(MorganService.class);
context.checking(new Expectations() {
{
//一次调用morganService的findMemberById方法,返回值为字符串good
oneOf(morganService).findMemberById("test120");
will(returnValue("good"));
}
});
service.setMorganService(morganService);
service.doExecute("test120");
context.assertIsSatisfied(); //检查mock对象有没有正确调用.
}
}
但是目前我们的单元测试,一般是扩展Spring的单元测试进行Bean自动注入,不需要我们手动进行set方法注入,而上面Jmock的使用,需要我们将测试servcie手动调用set方法才能将Mock对象进行注入.
解决方案:
关键问题:我们不能通过配置的方式将mock对象注入到测试的服务类.FactoryBean可以帮我们动态生成我们需要的Bean进行注入.而不用关心到底是什么样的类型.
步骤一:新建MockFactoryBean
public class MockFactoryBean implements FactoryBean {
private String interfaceName;
private boolean expectation = true;
private IExpectaion iexpectation;
/**
* @return the iexpectation
*/
public IExpectaion getIexpectation() {
return iexpectation;
}
/**
* @param iexpectation the iexpectation to set
*/
public void setIexpectation(IExpectaion iexpectation) {
this.iexpectation = iexpectation;
}
/**
* @return the expectation
*/
public boolean isExpectation() {
return expectation;
}
/**
* @param expectation the expectation to set
*/
public void setExpectation(boolean expectation) {
this.expectation = expectation;
}
/**
* @return the interfaceName
*/
public String getInterfaceName() {
return interfaceName;
}
/**
* @param interfaceName the interfaceName to set
*/
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
@SuppressWarnings("unchecked")
@Override
public Object getObject() throws Exception {
Mockery context = new Mockery();
final Object o = context.mock(getObjectType());
if (this.isExpectation()) {
context.checking(new Expectations() {
{
//因为Expectations是动态的,所以我将此方法抽出来接口,方便以后的扩展
iexpectation.expectaion(o, this);
}
});
}
return o;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
@SuppressWarnings("unchecked")
@Override
public Class getObjectType() {
// TODO Auto-generated method stub
try {
return Class.forName(interfaceName);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
throw new BeanInstantiationException(this.getClass(), "Can't find the class for use the "
+ getInterfaceName());
}
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
@Override
public boolean isSingleton() {
return true;
}
}
上面的IExpectaion是一个定义的接口,主要是用来方便自定义断言,它采用泛型的方式进行处理:
public interface IExpectaion {
/**
* @param o
* @param mockFactoryBean
*/
void expectaion(T o, Expectations expectations);
}
这样我们看下我们的applicationContext的配置:
com.alibaba.MorganService
开发人员针对每个外部接口,只需要再实现一个IExpectaion的接口类,进行相应断言就可以进行开发,而不需要再实现全部的接口方法,也避免了接口的方法变更的导致的单元测试错误.
上述方案的改进点:
1.MockFactory其实获取Object的class,可以不需要通过直接的InterfaceName来填写,直接通过iexpectation属性中的泛型参数进行获得.
其他方案
除了以上的方案,还可以通过JTester框架的Mocked注解方式进行处理.这种方式更方便,如:
@SpringApplicationContext("applicationContext.xml")
public class RealServiceJTester extends JTester {
@Mocked
@MockedBean
private MorganService morganService;
@SpringBeanByName
private RealService realService;
@Test
public void testRealService() {
new Expectations() {
{
// some expectation
}
};
realService.doExecute("test100");
}
}
分享到:
相关推荐
FactoryBean:我们在使用Spring过程中一般都是使用基本的的配置,在Spring配置中,还有一种特殊的FactoryBean,这种bean,可以动态的帮我们创建我们需要的bean,如: ProxyFactoryBean,通用的用于获得AOP代理的工厂bean。...
这篇代码主要适用于我的博客,用来帮助理解Spring配置文件+FactoryBean的实例化过程。注意,这里是FactoryBean,而不是BeanFactory,下载资源要看清楚哈。
SSH笔记-通过实现FactoryBean接口来创建 Bean的demo,恩
通过FactoryBean在获取目标类的时间我们将增强的代理类给返回,使得我们调用方法的时间使用的代理类来增强,如果需要继续使用未代理的对象,则直接@Autowired 注入使用.
spring源码解析之FactoryBean相关测试代码demo,文章中也有相关代码
com-spring-ioc-demo:源码主要是学习Spring IOC的原理,以及对Bean的注册及控制,主要运用以下类对...通过FactoryBean结合InvocationHandler关于动态代理invoke()方法的理解5.BeanNameAware 7.BeanFactoryPostPro
通过@Bean 和spring的factoryBean注入的bean. 以及对应@service注解注入的类 通过@Autowired 注入对象的时间是现根据类型在根据beanName获取的案例集合
使用ImportBeanDefinitionRegistrar、JDK代理、FactoryBean模拟mybatis原理
spring-3 演示使用动态代理模式实现面向切面编程 使用注解方式进行AOP编程及使用配置xml方式进行AOP编程 spring-4 演示了spring与JDBCTemplate配合使用 spring-5 演示了声明式事务及使用xml配置文件处理事务
68-BeanFactory与FactoryBean1
主要介绍了spring中的FactoryBean代码示例,涉及FactoryBean的实现等相关内容,具有一定参考价值,需要的朋友可以了解下。
Spring中的FactoryBean.rar
本篇文章主要介绍了Spring中的FactoryBean,如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean
主要介绍了简单了解Spring中BeanFactory与FactoryBean的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
Spring和MyBatis的整合是Java...Spring使用FactoryBean来创建特定类型的bean,例如MyBatis的Mapper接口的动态代理实例。BeanDefinitionRegistry则用于注册这些动态生成的bean,确保它们可以被Spring容器识别和管理。
spring 自定义 FactoryBean 样例工程,可以通过这个工程通过实现FactoryBean来扩展Spring容器
主要介绍了spring中FactoryBean中的getObject()方法实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
3.7.3. 使用FactoryBean定制实例化逻辑 3.8. The ApplicationContext 3.8.1. BeanFactory 还是 ApplicationContext? 3.8.2. 利用MessageSource实现国际化 3.8.3. 事件 3.8.4. 底层资源的访问 3.8.5. ...
3.7.3. 使用FactoryBean定制实例化逻辑 3.8. The ApplicationContext 3.8.1. BeanFactory 还是 ApplicationContext? 3.8.2. 利用MessageSource实现国际化 3.8.3. 事件 3.8.4. 底层资源的访问 3.8.5. ...