通过单元测试理解spring容器以及dubbo+zookeeper单元测试异常处理
一、先说一个结论:单元测试与主项目的spring容器是隔离的,也就是说,单元测试无法访问主项目spring容器,需要自己加载spring容器。
接下来是代码实例,WEB主项目出于运行状态,单元测试中可能会看到如下这样的代码:
代码一:当前类加载式
public class TestSpring {
@Test
public void testSpring(){
LoginService loginService = this.getBean("loginService");
}
//以下为容器实例声明及初始化、销毁
private ClassPathXmlApplicationContext context;
@Before
public void before(){
//加载spring容器
context = new ClassPathXmlApplicationContext("spring-context.xml");
}
@After
public void after(){
context.destroy();
}
//从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
public <T> T getBean(String name) {
return (T) context.getBean(name);
}
//从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
public <T> T getBean(Class<T> requiredType) {
return context.getBean(requiredType);
}
}
代码二:继承加载式
/**
* @Description: 登录单元测试类
*/
public class LoginTest extends SpringJunitSupport{
@Autowired
private LoginService loginService;
@Test
public void testLogin(){
loginService.login();
}
//让单元测试运行于spring环境,保证拥有spring框架相关支持
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring容器
@ContextConfiguration("classpath:/spring-context.xml")
public class SpringJunitSupport {
}
代码三:动态添加spring配置文件式
/**
* @Description: 登录单元测试类
*/
public class LoginTest{
//使用@Before注解方式加载spring容器配置文件,就不能通过自动装配的方式注入bean,因为自动装配注解执行要早于@Before
//@Autowired
private LoginService loginService;
private TestSpringContextSupport springContextSupport = null;
@Before
public void setUp() throws Exception {
springContextSupport = new TestSpringContextSupport();
//初始化spring容器时,再动态添加spring bean配置文件
springContextSupport.init(new String[] { "classpath:/support-quartz.xml" });
loginService = springContextSupport.getBean("loginService");
}
@Test
public void testLogin(){
loginService.login();
}
}
public class TestSpringContextSupport {
//通过静态语句块初始化一个静态变量,用于存放spring容器配置文件
public static List<String> contextList = new ArrayList<String>();
static {
contextList.add("classpath:/spring-context.xml");
}
private ApplicationContext context;
//定义初始化方法,动态添加spring配置文件到静态配置文件集合
public void init(String[] contextFile) {
List<String> list = new ArrayList<String>();
list.addAll(contextList);
for (int i = 0; contextFile != null && i < contextFile.length; i++) {
list.add(contextFile[i]);
}
String[] x = new String[list.size()];
list.toArray(x);
//加载spring容器
context = new ClassPathXmlApplicationContext(x);
}
//从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
public <T> T getBean(String name) {
return (T) context.getBean(name);
}
//从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
public <T> T getBean(Class<T> requiredType) {
return context.getBean(requiredType);
}
}
如上面三种方式,有一个共同点,就是在单元测试方法执行前,都大费周章的去加载了spring容器。
web主项目出于运行状态,单元测试为什么还要单独加载spring容器,因为web主项目的spring容器对单元测试是隔离的,通过如下手段验证:
验证1:
把单元测试所有加载spring容器的代码去掉,保证主项目出于运行状态,通过@Autowired注解(@Autowired注解可以装配spring内部bean),获取spring应用上下文的bean,然后再通过其获取业务bean,代码如下:
/**
* @Description: 登录单元测试类
*/
public class LoginTest{
//自动装配spring应用上下文bean
@Autowired
private ApplicationContext context;
@Test
public void testLogin(){
//通过应用上下文bean,获取业务bean
LoginService loginService = (LoginService)context.getBean("loginService");
loginService.login();
}
}
结果一定是空指针异常,context对象为null。
验证二,把web主项目停掉,单元测试使用上面第二种继承的方式加载spring容器,其它同上,代码如下:
/**
* @Description: 登录单元测试类,继承SpringJunitSupport加载spring容器
*/
public class LoginTest extends SpringJunitSupport{
//自动装配spring应用上下文bean
@Autowired
private ApplicationContext context;
@Test
public void testLogin(){
//通过应用上下文bean,获取业务bean
LoginService loginService = (LoginService)context.getBean("loginService");
loginService.login();
}
}
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring容器
@ContextConfiguration("classpath:/spring-context.xml")
public class SpringJunitSupport {
}
结果一切正常,如此就验证了单元测试与主项目的spring容器是隔离的,单元测试必须自己加载spring容器。
上面一直在说加载spring容器,其实就是加载配置文件,把配置文件里面的bean加载到spring容器中,上面的验证也一直通过在spring容器中搜索bean对象进行的,理解并应用这一点是非常重要的。
最后的彩蛋,理解是因为项目中有困惑,探究之后才能领悟透彻,比如一个实例:
1、主项目运行,提供服务接口,采用的方式为dubbo+zookeeper方式;
2、单元测试,调用提供者提供的服务,采用继承式加载spring配置文件;
3、抛出异常:地址已经被绑定使用(Address already in use: bind)
java.lang.IllegalStateException: Failed to load ApplicationContext
......
Caused by: com.alibaba.dubbo.rpc.RpcException: Fail to start server(url: dubbo://127.0.0.1:20880/......
......
Caused by: com.alibaba.dubbo.remoting.RemotingException: Failed to bind NettyServer on /127.0.0.1:20880, cause: Failed to bind to: /0.0.0.0:20880
......
Caused by: org.jboss.netty.channel.ChannelException: Failed to bind to: /0.0.0.0:20880
......
Caused by: java.net.BindException: Address already in use: bind
......
4、异常原因:因为采用的是dubbo+zookeeper方式,主项目spring提供者注册了127.0.0.1:20880,单元测试加载spring配置文件想要注册0.0.0.0:20880地址,但是20880已经被主容器占用,所以单元测试无法正常加载。
5、解决办法:将主容器停掉,单独使用单元测试,即作为服务端又作为客户端
6、再次抛出异常:
DEBUG [2016-08-18 18:30:26,603] - ZkClient.java () - Closing ZkClient...
INFO [2016-08-18 18:30:26,603] - ZkEventThread.java () - Terminate ZkClient event thread.
DEBUG [2016-08-18 18:30:26,603] - ZkConnection.java () - Closing ZooKeeper connected to 119.254.166.167:2181
DEBUG [2016-08-18 18:30:26,603] - ZooKeeper.java () - Closing session: 0x15678a538f900ef
DEBUG [2016-08-18 18:30:26,603] - ClientCnxn.java () - Closing client for session: 0x15678a538f900ef
......
DEBUG [2016-08-18 18:30:26,808] - ZkClient.java () - Closing ZkClient...done
INFO [2016-08-18 18:30:26,810] - DubboProtocol.java () - [DUBBO] Close dubbo server: /127.0.0.1:20880, dubbo version: 2.5.4, current host: 127.0.0.1
INFO [2016-08-18 18:30:26,812] - AbstractServer.java () - [DUBBO] Close NettyServer bind /0.0.0.0:20880, export /127.0.0.1:20880, dubbo version: 2.5.4, current host: 127.0.0.1
INFO [2016-08-18 18:30:26,812] - ClientCnxn.java () - EventThread shut down for session: 0x15678a538f900ef
ERROR [2016-08-18 18:30:26,813] - FailbackRegistry.java () - [DUBBO] Failed to uregister dubbo://127.0.0.1:20880/......
com.alibaba.dubbo.rpc.RpcException: Failed to unregister dubbo://127.0.0.1:20880/业务方法......
at com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doUnregister(ZookeeperRegistry.java:108)
at com.alibaba.dubbo.registry.support.FailbackRegistry.unregister(FailbackRegistry.java:160)
at com.alibaba.dubbo.registry.integration.RegistryProtocol$1.unexport(RegistryProtocol.java:130)
at com.alibaba.dubbo.config.ServiceConfig.unexport(ServiceConfig.java:270)
at com.alibaba.dubbo.config.spring.ServiceBean.destroy(ServiceBean.java:255)
at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:258)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:538)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:514)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:831)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:483)
at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:923)
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:897)
at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:811)
Caused by: java.lang.NullPointerException
at org.I0Itec.zkclient.ZkClient$8.call(ZkClient.java:720)
at org.I0Itec.zkclient.ZkClient.retryUntilConnected(ZkClient.java:675)
at org.I0Itec.zkclient.ZkClient.delete(ZkClient.java:716)
at com.alibaba.dubbo.remoting.zookeeper.zkclient.ZkclientZookeeperClient.delete(ZkclientZookeeperClient.java:57)
at com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry.doUnregister(ZookeeperRegistry.java:106)
... 12 more
上面需要注意的关键信息可以归结为:zookeeper客户端关闭(Closing ZkClient…)、dubbo服务关闭(Close dubbo server….)、注销dubbo的某个方法失败(Failed to uregister dubbo://127.0.0.1:20880/…)
虽然没细探究到底怎么回事,但是感觉应该是单元测试同时加载服务端和客户端(即加载spring配置文件),当测试方法执行完毕需要关闭服务的时候,由于先后顺序问题引发的异常。
7、再次解决办法:提供者由主容器启动,至于单元测试,就到了上面最后的彩蛋那句话了,单元测试作为客户端,只需要拿到服务端提供者的bean对象,就可以完成对提供者服务端的调用。
那么这个对象从哪里来,dubbo+zookeeper方式会在客户端配置订阅服务的配置文件,这个里面有提供者对应的bean,所以单元测试只需要加载客户端订阅配置文件即可,代码如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:/spring/test-dubbo-consumer.xml" })
public class CodeRuleRPCImplDubboTest {
@Autowired
private UserRPC UserRPC;
@Test
public void testGetUserByCode() {
bizCode = userRPC.getUserByCode("001");
}
}
客户端订阅者配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:task="http://www.springframework.org/schema/task" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
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
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 消费方应用名称信息,这个相当于起一个名字,我们dubbo管理页面比较清晰是哪个应用暴露出来的 -->
<dubbo:application name="SPRING_DUBBO_CONSUMER"></dubbo:application>
<!-- 使用zookeeper注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"
check="true"></dubbo:registry>
<!-- 要引用的用户管理服务 -->
<dubbo:reference interface="com.test.rpc.UserRPC"
id="userRPC"></dubbo:reference>
</beans>
至此以上问题解决,同时深入理解spring容器,单元测试,spring-bean之间的关系,同时也对dubbo和zookeeper加深了了解。
通过单元测试理解spring容器以及dubbo+zookeeper单元测试异常处理的更多相关文章
- 理解Spring 容器、BeanFactory 以及 ApplicationContext
一.spring 容器理解 spring 容器可以理解为生产对象(Object)的地方,在这里容器不只是帮助我们创建对象那么简单,它负责了对象的整个生命周期-创建.装配.销毁.而这里对象的创建管理的控 ...
- Spring boot + mybatis + dubbo + zookeeper + mysql + mybatis-generator 一个小demo
代码的链接地址:https://gitee.com/frostGG/springbooo_dubbo_demo.git 1.项目的目录经构: 介绍: 这一个项目,用的是阿里的dubbo,和zookee ...
- 资深程序员总结:彻底理解Spring容器和应用上下文
点关注,不迷路:持续更新Java架构相关技术及资讯热文!!! 有了Spring之后,通过依赖注入的方式,我们的业务代码不用自己管理关联对象的生命周期.业务代码只需要按照业务本身的流程,走啊走啊,走到哪 ...
- 转: 彻底理解 Spring 容器和应用上下文
本文由 简悦 SimpRead 转码, 原文地址 https://mp.weixin.qq.com/s/o11jVTJRsBi998WlgpfrOw 有了 Spring 之后,通过依赖注入的方式,我们 ...
- 深入理解 spring 容器,源码分析加载过程
Spring框架提供了构建Web应用程序的全功能MVC模块,叫Spring MVC,通过Spring Core+Spring MVC即可搭建一套稳定的Java Web项目.本文通过Spring MVC ...
- 【Spring】Junit加载Spring容器作单元测试
如果我们需要对我们的Service方法作单元测试,恰好又是用Spring作为IOC容器的,我们可以这么配置Junit加载Spring容器,方便做单元测试. > 基本的搭建 (1)引入所需的包 & ...
- Junit加载Spring容器作单元测试
阅读目录 > 基本的搭建 > 常见的用法 如果我们需要对我们的Service方法作单元测试,恰好又是用Spring作为IOC容器的,我们可以这么配置Junit加载Spring容器,方便做单 ...
- Spring容器、BeanFactory和ApplicationContext,及3种装配Bean的方式
目录 一. spring容器理解 二. BeanFactory和ApplicationContext之间的关系 三. BeanFactory详情介绍 四.ApplicationContext介绍 五. ...
- 从Spring容器的角度理解Dubbo扩展点的加载时机
对于Dubbo提供的扩展点,主程序执行的过程中并没有显示调用加载的过程,无论是自激活的Filter还是自适应的ThreadPool.那么这样的扩展点在程序运行的哪个节点调用的呢?跟踪之前性能监控扩展点 ...
随机推荐
- guess number
crossin的前面几章基本和LPTHW内容重合,因此我直接做了他前面的一个综合练习. 猜数游戏, 即系统随机记录一个数,根据用户猜的记录,如果正确则告知,且退出游戏,如不正确,则提示答案与用户输入的 ...
- 遗传算法,实数编码的交叉操作之SBX(模拟二进制交叉)
本文主要介绍遗传算法(实数编码)的交叉操作中的SBX,模拟二进制交叉. 首先,给出个人用python2.7实现的代码,具体模块已上传到: https://github.com/guojun007/sb ...
- Sqoop使用手册
转载请注明出处:http://www.cnblogs.com/xiaodf/ 1 Sqoop概述 2 版本说明 3 驱动安装 3.1 MySQL 4 基本用法 4.1 导入 4.1.1 保护密码 4. ...
- js用户修改密码功能模块
;(function(){ var ajaxSub = false, showError = function(msg){ if(msg){ $('#er_txt').html(msg).show() ...
- js-PC版监听键盘大小写事件
//获取键盘按键事件,可以使用keyup. //问题:获取到键盘的按下Caps lock键时,不能知道当前状态是大写.还是小写状态. //解决: 设置一个全局判断大小写状态的 标志:isCapital ...
- 汇编语言进阶和Makefile进阶---第二天
摘要: 原创博文:转载请标明出处:http://www.cnblogs.com/zxouxuewei 首先加载启动代码: ; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序装载地 ...
- window.onload用法详解:
网页中的javaScript脚本代码往往需要在文档加载完成后才能够去执行,否则可能导致无法获取对象的情况,为了避免这种情况的发生,可以使用以下两种方式: 一.将脚本代码放在网页的底端,这样在运行脚本代 ...
- HTML学习-日常问题-1
最近入了Html的坑 所以开始了学习前端的漫长道路 所以打算将日常遇到的小问题记录下来 1.关于img自适应的问题 <div class="main"> <div ...
- virtual file system (VFS)
http://www.ibm.com/developerworks/library/l-virtual-filesystem-switch/ http://www.ibm.com/developerw ...
- CentOS源 Ubuntu 源 OpenSUSE-----持续汇总
CentOS 基础(常用)的源:http://dl.fedoraproject.org/pub/epel/epel-release-latest-5.noarch.rpmhttp://dl.fedor ...