一、什么是Javassist
Javassist是一个开源的分析、编辑和创建Java字节码的类库。
二、如何通过Javassit实现动态代理
利用Javassist实现字节码增强时,可以无须关注字节码刻板的结构,其优点就在于编程简单。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构或者动态生成类。其中最重要的是ClassPool、CtClass、CtMethod、CtField这四个类:
- CtClass(compile-time class):编译时类信息,它是一个class文件在代码中的抽象表现形式,可以通过一个类的全限定名来获取一个CtClass对象,用来表示这个类文件。
- ClassPool:从开发视角来看,ClassPool是一张保存CtClass信息的HashTable,key为类名,value为类名对应的CtClass对象。当我们需要对某个类进行修改时,就是通过pool.getCtClass(“className”)方法从pool中获取到相应的CtClass。
- CtMethod、CtField:这两个比较好理解,对应的是类中的方法和属性。
下面通过代码演示如何使用Javassist.
现在有一个需求希望可以向对指定方法进行增强,支持,在原方法前、后增强或替换原方法。
首先定义一个枚举,表示增强位置
/**
* Desc:TODO
*
* @author zhangwei<wei.zw@corp.netease.com>
* @since 2016年1月14日 下午9:23:17
* @version v 0.1
*/
public enum InjectType {
BEFORE, AFTER, REPLACE;
}
增强实现方法
/**
* Desc:TODO
*
* @author zhangwei<wei.zw@corp.netease.com>
* @since 2016年1月14日 下午9:19:34
* @version v 0.1
*/
public final class ClassEnhancedGenerator {
private ClassEnhancedGenerator() {
}
/**
* 类方法增强<BR>
*
* 对指定类的方法进行代码增强(将指定的原方法名改为$enhanced,同时复制原方法名进行代码注入)
*
* @param className
* 待增强的类名
* @param methodName
* 待增强的方法名
* @param provider
* {@link ClassInjectProvider}实现类
* @throws Exception
*/
public static void enhancedMethod(Class<?> cls, Method[] methods, InjectType injectType, ClassInjectProvider provider)
throws Exception {
CtClass ctClass = ClassPool.getDefault().get(cls.getName());
for (int i = 0; i < methods.length; i++) {
injectCodeForMethod(ctClass, methods[i].getName(), injectType, provider);
}
String resource = cls.getName().replace(".", "/") + ".class";
URI uri = ClassLoader.getSystemClassLoader().getResource(resource).toURI();
String classFilePath = uri.getRawPath().substring(0, uri.getRawPath().length() - resource.length());
ctClass.writeFile(classFilePath);
}
/***
* 注入增强代码
*
* @param ctClass
* @param methodName
* @param injectType
* @param provider
* @throws Exception
* @author zhangwei<wei.zw@corp.netease.com>
*/
private static void injectCodeForMethod(CtClass ctClass, String methodName, InjectType injectType,
ClassInjectProvider provider) throws Exception {
CtMethod oldMethod = ctClass.getDeclaredMethod(methodName);
// 修改原有的方法名称为"方法名$enhanced",如果已存在该方法则返回
String originalMethod = methodName + "$enhanced";
CtMethod[] methods = ctClass.getMethods();
for (int i = 0; i < methods.length; i++) {
CtMethod method = methods[i];
if (method.getName().equals(originalMethod)) {
return;
}
}
oldMethod.setName(originalMethod);
// 增加代码,复制原来的方法名作为增强的新方法,同时调用原有方法即"方法名$enhanced"
CtMethod enhancedMethod = CtNewMethod.copy(oldMethod, methodName, ctClass, null);
// 对复制的方法注入代码
StringBuffer methodBody = new StringBuffer();
methodBody.append("{");
switch (injectType) {
case BEFORE:
methodBody.append(provider.injectCode(enhancedMethod));
methodBody.append("return " + originalMethod + "($$); ");
break;
case AFTER:
methodBody.append("try{");
methodBody.append("return " + originalMethod + "($$); ");
methodBody.append("}finally{");
methodBody.append(provider.injectCode(enhancedMethod));
methodBody.append("}");
break;
default:
String injectCode = provider.injectCode(enhancedMethod);
methodBody.append(injectCode);
}
methodBody.append("}");
enhancedMethod.setBody(methodBody.toString());
ctClass.addMethod(enhancedMethod);
}
}