突然想了解下java动态修改类执行行为的知识。
1. 首先看了下AspectJ,它可以通过静态植入在不改变原java代码点情况下修改代码的执行逻辑。启中cflow功能更强大更灵活。cflow一般指的是一个方法里的所有代码的片段切换。
一、Java Instrumentation。 详细讲解可参考http://blog.csdn.net/productshop/article/details/50623626
premain方式(>=JDK1.5)。应用启动时需要通过-javaagent:xxx.jar指定代理类,且jar包含META-INF/MANIFEST.MF
Manifest-Version: 1.0
Premain-Class:com.pre.PreMain
Can-Redefine-Classes: true
在使用的时候遇到几个问题
问题1:使用redefineClasses方式时,Caused by: java.lang.UnsupportedOperationException: redefineClasses is not supported in this environment
通过在MANIFEST.MF增加配置Can-Redefine-Classes: true解决
问题2:使用addTransformer 方式时,报java.lang.NoClassDefFoundError: com/TransClass (wrong name: com/TransClass2)
这个主要是由于要替换的class为TransClass2,跟被替换的TransClass不匹配导致,后修一致改为TransClass解决
agentmain方式(>=JDK1.6),应用正常启动,代理类通过attach方式追加到应用系统
Manifest-Version: 1.0
Can-Retransform-Classes: true
Agent-Class: com.agent.AgentMain
问题1:Caused by: java.lang.UnsupportedOperationException: adding retransformable transformers is not supported in this environment
通过在MANIFEST.MF增加配置Can-Retransform-Classes: true解决
二、java代理
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
自动生成实现interfaces的java代理类,然后通过loader加载该代理类。实现没个接口的方法,调用h的
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable方法。主要的业务逻辑在InvocationHandler的invoke方法中实现。自我感觉proxy这个参数意义不大,至少还没有发现应用场景
代理是基于接口的,代理生成的对象也是接口。开发时首先定义需要代理的接口,然后在InvocationHandler定义代理的业务逻辑。业务逻辑可以实现代理接口,也可以与代理接口无关
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");是的动态代理的class文件保存在磁盘
三、ASM
下载地址http://forge.ow2.org/project/showfiles.php?group_id=23&release_id=5902
public class Account {
public void foo(){
operation();
}
public void operation(){
System.out.println("operation...");
}
public static void main(String[] args) throws Exception{
Checker.check();
}
}
public class AddCheckClassAdapter extends ClassAdapter {
public AddCheckClassAdapter(ClassVisitor arg0) {
super(arg0);
}
public MethodVisitor visitMethod(final int access, final String name, final String desc,final String signature, final String[] exceptions){
MethodVisitor mv = super.visitMethod(access, name, desc, signature,exceptions);
MethodVisitor wrappedMv = mv;
if (mv != null) {
// 对于 "operation" 方法
if (name.equals("operation")) {
// 使用自定义 MethodVisitor,实际改写方法内容
wrappedMv = new AddCheckMethodAdapter(mv);
}
}
return wrappedMv;
}
}
public class AddCheckMethodAdapter extends MethodAdapter {
public AddCheckMethodAdapter(MethodVisitor mv) {
super(mv);
// TODO Auto-generated constructor stub
}
public void visitCode() {
visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Checker.class), "check", "()V");
}
}
public class Generator {
public static void main(String[] args) throws Exception {
ClassReader cr = new ClassReader(Account.class.getName());
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter classAdapter = new AddCheckClassAdapter(cw);
cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
File file = new File("com/asm/Account.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}
public class Main {
public static void main(String[] args) throws Exception{
Account account = new Account();
account.foo();
}
}
首先执行Generator,再执行Main会发现代码已经被修改,使用反编译工具也会看到Account的operation已经被插入。
问题1:Exception in thread "main" java.lang.NoClassDefFoundError: com.Checker
at com.asm.Account.operation(Unknown Source)
at com.asm.Account.foo(Unknown Source)
at Main.main(Main.java:6)
主要是没有使用/格式的类名字,而是使用了“.”,使用Type.getInternalName(Checker.class)转换后解决
asm还可以实现不生成class文件进行动态代理
4:cglib
cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
package com.cglib;
public interface Test {
public void test();
}
package com.cglib;
public class TestImpl implements Test {
public void test(){
System.out.println("test");
}
}
package com.cglib;
public class TestMain {
public static void main(String[] args) {
TestProxy proxy = new TestProxy();
Test test = (Test)proxy.getInstance(new TestImpl());
test.test();
}
}
package com.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class TestProxy implements MethodInterceptor{
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("cglib 开始");
proxy.invokeSuper(obj, args);
System.out.println("cglib 结束");
return null;
}
}
5:spring cglib(同cglib)
package com.spring;
public interface Test {
public String test();
}
package com.spring;
public class TestImpl implements Test {
public String test(){
System.out.println("test");
return "test result";
}
}
package com.spring;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class TestProxy implements MethodInterceptor{
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("cglib 开始");
Object result = proxy.invokeSuper(obj, args);
System.out.println("cglib 结束");
return result;
}
}
package com.spring;
public class TestMain {
public static void main(String[] args) {
TestProxy proxy = new TestProxy();
Test test = (Test)proxy.getInstance(new TestImpl());
System.out.println(test.test());
}
}
6:javassist
比asm更加高级语言化的修改class。
package com.javassist;
public interface TestJavassistInt {
public void test();
}
package com.javassist;
public class TestJavassist1 implements TestJavassistInt{
public void test(){
System.out.println("javassist test");
}
public static void main(String[] args)throws Exception{
TestJavassist app = new TestJavassist();
app.test();
}
}
package com.javassist;
public class TestJavassist implements TestJavassistInt{
public void test(){
System.out.println("javassist test");
}
public static void main(String[] args)throws Exception{
TestJavassist app = new TestJavassist();
app.test();
}
}
package com.javassist;
import javassist.ClassPool;
import javassist.CtClass;
import com.asm.ByteClassLoader;
public class Generator {
// public static void main(String[] args) throws Exception {
// ClassPool pool = ClassPool.getDefault();
// //创建Programmer类
// CtClass cc= pool.makeClass("Programmer");
// //定义code方法
// CtMethod method = CtNewMethod.make("public static void main(String[] args)throws Exception{}", cc);
// //插入方法代码
// method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
// cc.addMethod(method);
// //保存生成的字节码
// cc.writeFile("/Users/wangql/source/spring_core/ssh/ssh");
// }
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建Programmer类
CtClass cc = pool.get(TestJavassist.class.getName());
cc.getMethod("test", "()V").insertBefore("System.out.println(\"Insert Javassist Test\");");
// cc.writeFile("/Users/wangql/source/spring_core/ssh/ssh/");
// ByteClassLoader loader = new ByteClassLoader();
// Object obj = loader.defineClassFromClassFile("com.javassist.TestJavassist1",cc.toBytecode()).newInstance();
// ((TestJavassist1)obj).test();
ByteClassLoader loader = new ByteClassLoader();
Class clazz = loader.defineClassFromClassFile("com.javassist.TestJavassist",cc.toBytecode());
Object o= clazz.newInstance();
try {
//调用Programmer的code方法
clazz.getMethod("test", null).invoke(o, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.javassist;
public class Main {
public static void main(String[] args)throws Exception{
TestJavassist app = new TestJavassist();
app.test();
}
}
本来想通过ctclass的toBytecode方法在程序中直接转换class,但是失败,总提示classcast错误,想了想应该是由于两个类的classloader不属于同一个造成的,但是转化为interface的化是可以的,因为interface是用的同一个classloader。可以通过反射来执行