Java代理

来源:互联网 发布:淘宝售后职责 编辑:程序博客网 时间:2024/06/11 01:00

突然想了解下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。可以通过反射来执行