Java动态代理
以前做动态代理都是通过Java的动态代理,今天看公司代码,公司的是使用Javassist来实现的代理,于是又去了解了一下动态代理的东西。
动态代理主要有三种实现方式:
1、 Jdk原生的动态代理
Jdk动态代理要求被代理的对象必须实现一个接口,没有接口的条件下可以使用cglib
参考代码:
package cn.edu.knowledge.proxy; public interface Person { void sayHello(); }
package cn.edu.knowledge.proxy; public class Student implements Person{ @Override public void sayHello() { System.out.println("I am student"); } }
package cn.edu.knowledge.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JDKProxy implements InvocationHandler { private Object target; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before say hello"); Object result = method.invoke(target, args); System.out.println("After say hello"); return result; } public Object getProxy(Object target){ this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } public static void main(String[] args) { Person p = new Student(); JDKProxy proxy = new JDKProxy(); Person p2 = (Person) proxy.getProxy(p); p2.sayHello(); } }
2、 动态字节码生成
使用动态字节码生成技术实现 AOP 原理是在运行期间目标字节码加载后,生成目标类 的子类,将切面逻辑加入到子类中,所以使用 Cglib 实现 AOP 不需要基于接口。
本节介绍如何使用 Cglib 来实现动态字节码技术。Cglib 是一个强大的,高性能的 Code 生 成类库,它可以在运行期间扩展 Java 类和实现 Java 接口,它封装了 Asm,所以使用 Cglib 前 需要引入 Asm 的 jar。 清单七:使用CGLib实现AOP
package my.test; 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 CglibProxyTest implements MethodInterceptor { private CglibProxyTest() { } public static <T extends Test> Test newProxyInstance(Class<T> targetInstanceClazz){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(targetInstanceClazz); enhancer.setCallback(new CglibProxyTest()); return (Test) enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }
3、 自定义类加载器
如果我们实现了一个自定义类加载器,在类加载到 JVM 之前直接修改某些类的方法, 并将切入逻辑织入到这个方法里,然后将修改后的字节码文件交给虚拟机运行,那岂不是更 直接。
Javassist 是一个编辑字节码的框架,可以让你很简单地操作字节码。它可以 在运行期定义或修改 Class。使用 Javassist 实现 AOP 的原理是在字节码加载前直 接修改需要切入的方法。这比使用 Cglib 实现 AOP 更加高效,并且没太多限制
package cn.edu.knowledge.proxy; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; import javassist.CtField.Initializer; import javassist.CtNewMethod; import javassist.Modifier; import javassist.NotFoundException; public class JavassistTest { public static void main(String[] args) throws CannotCompileException, NotFoundException, SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, ClassNotFoundException, IllegalArgumentException, InvocationTargetException { ClassPool pool = ClassPool.getDefault(); CtClass cls = pool.makeClass("cn.edu.knowledge.TestClass"); // 添加私有成员name及其getter、setter方法 CtField param = new CtField(pool.get("java.lang.String"), "name", cls); param.setModifiers(Modifier.PRIVATE); cls.addMethod(CtNewMethod.setter("setName", param)); cls.addMethod(CtNewMethod.getter("getName", param)); cls.addField(param, Initializer.constant("")); // 添加无参的构造体 CtConstructor cons = new CtConstructor(new CtClass[] {}, cls); cons.setBody("{name = \"Brant\";}"); cls.addConstructor(cons); // 添加有参的构造体 cons = new CtConstructor( new CtClass[] { pool.get("java.lang.String") }, cls); cons.setBody("{$0.name = $1;}"); cls.addConstructor(cons); // 打印创建类的类名 System.out.println("类名:"+cls.toClass()); // 通过反射创建无参的实例,并调用getName方法 Object o = Class.forName("cn.edu.knowledge.TestClass").newInstance(); Method getter = o.getClass().getMethod("getName"); System.out.println("name: "+getter.invoke(o)); // 调用其setName方法 Method setter = o.getClass().getMethod("setName", new Class[] { String.class }); setter.invoke(o, "Adam"); System.out.println("name: " + getter.invoke(o)); // 通过反射创建有参的实例,并调用getName方法 o = Class.forName("cn.edu.knowledge.TestClass") .getConstructor(String.class).newInstance("Liu Jian"); getter = o.getClass().getMethod("getName"); System.out.println("name: "+ getter.invoke(o)); } }