故事要从一个异常开始,某天我在开发一个加密、解密特性,算法使用的是3DES,样例代码如下。

package org.jackie.study.powermock;

import java.io.UnsupportedEncodingException;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; /**
* @author Jackie
*
*/
public class TripleDESAlgorithm {
private static final String ALGORITHM = "DESede"; public static byte[] encrypt(String cryptKey, byte[] src) {
try {
SecretKey deskey = new SecretKeySpec(build3DesKey(cryptKey), ALGORITHM);
Cipher c1 = Cipher.getInstance(ALGORITHM);
c1.init(Cipher.ENCRYPT_MODE, deskey);
return c1.doFinal(src);
} catch (Exception e) {
e.printStackTrace();
}
return null;
} public static byte[] decrypt(String cryptKey, byte[] src) {
try {
SecretKey deskey = new SecretKeySpec(build3DesKey(cryptKey), ALGORITHM);
Cipher c1 = Cipher.getInstance(ALGORITHM);
c1.init(Cipher.DECRYPT_MODE, deskey);
return c1.doFinal(src);
} catch (Exception e) {
e.printStackTrace();
}
return null;
} static byte[] build3DesKey(String keyStr) throws UnsupportedEncodingException {
byte[] key = new byte[24];
byte[] temp = keyStr.getBytes("UTF-8");
if (key.length > temp.length) {
System.arraycopy(temp, 0, key, 0, temp.length);
} else {
System.arraycopy(temp, 0, key, 0, key.length);
} return key;
}
}

用来验证这个算法类的单元测试代码工作正常,运行结果显示上述加密、解密代码逻辑正确,没有错误,测试代码如下。

package org.jackie.study.powermock;

import org.junit.Assert;
import org.junit.Test; public class TripleDESAlgorithmTest { @Test
public void test() throws Exception {
String key = String.valueOf(System.currentTimeMillis()) + "jackie";
byte[] encrypted = TripleDESAlgorithm.encrypt(key, "helloworld".getBytes("UTF-8"));
byte[] decrypted = TripleDESAlgorithm.decrypt(key, encrypted);
Assert.assertEquals("helloworld", new String(decrypted, "UTF-8"));
}
}

但当我使用单元测试代码来验证特性代码功能是否正常时,但恼人的事情发生了,测试代码抛出了诡异的异常,如下。

java.lang.ClassCastException: com.sun.crypto.provider.DESedeCipher cannot be cast to javax.crypto.CipherSpi
at javax.crypto.Cipher.chooseProvider(Cipher.java:845)
at javax.crypto.Cipher.init(Cipher.java:1213)
at javax.crypto.Cipher.init(Cipher.java:1153)
at org.jackie.study.powermock.TripleDESAlgorithm.encrypt(TripleDESAlgorithm.java:23)
at org.jackie.study.powermock.SubscriberTest.testSayHello(SubscriberTest.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:310)

异常信息比较长,这里仅截取了一部分,但是可以说明问题。

业务代码和测试代码如下。

// 业务代码
package org.jackie.study.powermock; public class Subscriber {
private String name;
// 解密收到的加密信息
public String sayHello(String name, byte[] message) throws Exception
{
byte[] realMessage = TripleDESAlgorithm.decrypt(name, message); if (realMessage != null)
{
this.name = name;
return new String(realMessage, "UTF-8");
}
return null;
} public String getName() {
return name;
}
}
// 测试代码
package org.jackie.study.powermock; import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest
public class SubscriberTest {
@Test
public void testSayHello() throws Exception {
byte[] encryptMessage = TripleDESAlgorithm.encrypt("jackie", "hello world!".getBytes("UTF-8")); Subscriber subscriber = new Subscriber();
subscriber.sayHello("jackie", encryptMessage);
Assert.assertEquals("jackie", subscriber.getName());
}
}

异常很让人伤脑筋,因为反编译类com.sun.crypto.provider.DESedeCipher可以发现,这个算法实现类的确是javax.crypto.CipherSpi的子类,类转换失败实在是不应该。通常情况下如果出现ClassCastException,除了确实是类型不匹配外,还有一种可能即是两个类的ClassLoader不同,导致JVM认为这两个类没有关联。想到这里,这个问题基本上就有点思路了,因为PowerMock的工作原理即是使用自定义的类加载器来加载被修改过的类,从而达到打桩的目的,这里会不会是这个原因呢?

求助Google大师,发现在官网上有如下一段话。根据提示,我在测试类SubscriberTest前面加上了@PowerMockIgnore({"javax.crypto.*"}),保存之后重新运行测试代码,发现异常不再抛出,问题得到了解决。

The reason is that the XML framework tries to instantiate classes using reflection and does this from the thread context classloader (PowerMock's classloader) but then tries to assign the created object to a field not loaded by the same classloader. When this happens you need to make use of the @PowerMockIgnore annotation to tell PowerMock to defer the loading of a certain package to the system classloader. What you need to ignore is case specific but usually it's the XML framework or some packages that interact with it. E.g. @PowerMockIgnore({"org.xml.*", "javax.xml.*"}).

从上述描述中可以得到的信息是,假如待测试类中使用到了XML解析相关的包和类,那么测试类前同样需要增加@PowerMockIgnore({"org.xml.*", "javax.xml.*"}),消除类加载器引入的ClassCastException。

PowerMock注解PowerMockIgnore的使用方法的更多相关文章

  1. 《SpringMVC从入门到放肆》十一、SpringMVC注解式开发处理器方法返回值

    上两篇我们对处理器方法的参数进行了分别讲解,今天来学习处理器方法的返回值. 一.返回ModelAndView 若处理器方法处理完后,需要跳转到其它资源,且又要在跳转资源之间传递数据,此时处理器方法返回 ...

  2. SpringBoot —— AOP注解式拦截与方法规则拦截

    AspectJ是一个面向切面的框架,它扩展了Java语言.AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件. SpringBoot中AOP的使用 ...

  3. Spring Boot 实战 —— MyBatis(注解版)使用方法

    原文链接: Spring Boot 实战 -- MyBatis(注解版)使用方法 简介 MyBatis 官网 是这么介绍它自己的: MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过 ...

  4. 10.SpringMVC注解式开发-处理器方法的参数

    1.逐个参数接收 只要保证请求参数名与该请求处理方法的参数名相同即可 // 请求参数名 与该处理器中的请求方法的参数名相同 ,即可接收前台传递过来的参数 public ModelAndView met ...

  5. SpringBoot AOP注解式拦截与方法规则拦截

    AOP的本质还是动态代理对方法调用进行增强. SpringBoot 提供了方便的注解实现自定义切面Aspect. 1.使用需要了解的几个概念: 切面.@Aspect 切点.@Pointcut. 通知. ...

  6. 用PowerMock mock 由工厂方法产生的对象

    有些对象需要mock的对象是由工厂方法产生出来的,而工厂方法一般是静态方法,这时候就需要同时mock工厂方法及对象 被测方法: public class EmployeeServiceFactory ...

  7. SpringMVC---RequestMapping注解类的使用方法

    RequestMapping注解的使用 开发Controller控制器类,使用@Controller注解标注,并在配置文件中用<context:component-scan/>扫描相应的包 ...

  8. 理解JPA注解@GeneratedValue的使用方法

    https://blog.csdn.net/u012838207/article/details/80406716 一.JPA通用策略生成器 通过annotation来映射hibernate实体的,基 ...

  9. 11.SpringMVC注解式开发-处理器方法的返回值

    处理器方法的返回值 使用@Controller 注解的处理器的处理器方法,其返回值常用的有四种类型 1.ModelAndView 2.String 3.void 4.自定义类型对象 1.返回Model ...

随机推荐

  1. Cocos2d-X 2.2嵌入MFC的子窗口

    1.在cocos2dx目录下创建基于对话框的MFC工程,对话框中间放一个Picture控件 2.添加cocos2dx的相关头文件包含路径.库包含路径和依赖项,可以参考其他cocos工程设置 3.选中P ...

  2. 关于配置ST_Geometry报ORA-06522的问题

    环境 SDE版本:10./10.2/10.2.1/10.2.2 Oracle版本:11g R2 11.2.0.1 Windows版本:Windows Server 2008 R2 问题描述及原因 li ...

  3. bzoj 1200: [HNOI2005]木梳 DP

    1200: [HNOI2005]木梳 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 266  Solved: 125[Submit][Status] ...

  4. /etc/passwd 与 /etc/shadow

    /etc/passwd是用户数据库,其中的域给出了用户名.加密口令和用户的其他信息. /etc/shadow是在安装了影子(shadow)口令软件的系统上的影子口令文件.影子口令文件将/etc/pas ...

  5. ubuntu系统下创建软件桌面快捷方式

    转自ubuntu系统下创建软件桌面快捷方式 默认情况下,ubuntu会将自动安装的软件快捷方式保存在/usr/share/applications目录下,如果我们要创建桌面快捷方式,只需要右键-复制- ...

  6. 【BZOJ 2005】[Noi2010]能量采集 (容斥原理| 欧拉筛+ 分块)

    能量采集 Description 栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量.在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起. 栋栋 ...

  7. ANDROID_MARS学习笔记_S03_005_Geocoder、AsyncTask

    一.代码1.xml(1)AndroidManifest.xml <uses-permission android:name="android.permission.ACCESS_FIN ...

  8. JavaScript简介、语法

    一.JavaScript简介 1.JavaScript是个什么东西? 它是个脚本语言,需要有宿主文件,它的宿主文件是HTML文件. 2.它与Java什么关系? 没有什么直接的联系,Java是Sun公司 ...

  9. in an effort to

    What does "in an effort" to mean? I personally consider in an effort to a stock phrase1. T ...

  10. INFORMATION_SCHEMA.INNODB_TRX 详解

    从192.168.11.186 上登录 192.168.11.185 数据库: root 13246 547 0 13:39 pts/1 00:00:00 mysql -uroot -px xxxxx ...