Java动态代理
一、JDK动态代理
基于接口实现,要求委托类要是接口的实现。
Java的 java.lang.reflect 包下提供了 Proxy 类和一个InvocationHandler接口。
Proxy定义了生成JDK动态代理类的方法 getProxyClass(ClassLoader loader,Class<?>... interfaces)生成动态代理类
InvocationHandler 是被动态代理类回调的接口,接口中的invoke()方法调用委托类(被代理的类)的实际方法,我们所有需要的针对委托类的统一增强逻辑都放在invoke()方法中,在调用委托类接口方法之前或之后。
生成JDK动态代理步骤:
1、定义需要被代理的接口
public interface Sleep { void goToSleep(); }
2、定义委托类,并实现1中的接口
public class SleepImpl implements Sleep { @Override public void goToSleep() { System.out.println("睡觉"); } }
3、定义 InvocationHandler 接口的实现类,并重写 invoke 方法,在invoke方法中调用委托类的方法,并可在调用前后添加增强逻辑
public class MyInvocationHandler<T> implements InvocationHandler { /** * 被代理的目标对象 */ private T target; public MyInvocationHandler(T target) { this.target = target; } /** * 调用委托类的实际方法及执行增强逻辑 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 增强逻辑 System.out.println("前增强"); // 调用委托类的实际方法 Object result = method.invoke(target, args); // 增强逻辑 System.out.println("后增强"); // 结果返回 return result; } }
4、实例化委托类,并通过Proxy.newProxyInstance 生成代理对象
// 实例化委托类 Sleep sleep = new SleepImpl(); // 生成代理对象 Sleep sleepProxy= (Sleep) Proxy.newProxyInstance(sleep.getClass().getClassLoader(),sleep.getClass().getInterfaces(),new MyInvocationHandler<Sleep>(sleep)); // 执行代理类增强后的方法 sleepProxy.goToSleep();
建议整合步骤3和4,使用匿名内部类的方式,如:
// 实例化委托类 Sleep sleep = new SleepImpl(); // 生成代理对象 Sleep sleepProxy = (Sleep) Proxy.newProxyInstance(sleep.getClass().getClassLoader(), sleep.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 增强逻辑 System.out.println("前增强"); // 调用委托类的实际方法 Object result = method.invoke(sleep, args); // 增强逻辑 System.out.println("后增强"); // 结果返回 return result; } }); // 执行代理类的方法 sleepProxy.goToSleep();
JDK动态代理特点
1、生成的代理类:$Proxy0 extends Proxy implements Subject,我们看到代理类继承了Proxy类,Java的继承机制决定了JDK动态代理类们无法实现对 类 的动态代理。所以也就决定了JDK动态代理只能对接口进行代理。
2、每个生成的动态代理实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行。
3、代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被调用处理器分派到委托类执行。
JDK动态代理的不足
JDK动态代理的代理类字节码在创建时,需要实现业务实现类所实现的接口作为参数。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。(JDK动态代理重要特点是代理接口)并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。动态代理只能对接口产生代理,不能对类产生代理。
二、CGLib动态代理
基于继承,CGlib是针对类来实现代理的,他的原理是对委托类(被代理的目标类)生成一个子类,并覆盖其中方法实现增强。
因为底层是基于创建被代理类的一个子类,所以它避免了JDK动态代理的缺陷。
但因为采用的继承,所以不能对final修饰的类进行代理。
生成CGLib动态代理的步骤:
1、定义委托类(需要被代理的类)
public class Sleep { public void goToSleep() { System.out.println("sleeping"); } }
2、定义 MethodInterceptor 接口的实现类,并重写 intercept方法,在intercept方法中调用委托类的方法,并可在调用前后添加增强逻辑
public class CGLibDynamicProxy implements MethodInterceptor { /** * 调用委托类的实际方法及执行增强逻辑 * @param obj * @param method * @param objects * @param methodProxy * @return * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 增强逻辑 System.out.println("前增强"); //调用委托类(父类中)的方法 Object result = methodProxy.invokeSuper(obj, objects); // 增强逻辑 System.out.println("后增强"); // 返回执行结果 return result; } }
3、通过 Enhancer.create 生成代理对象
// 生成Sleep类的代理对象 Sleep sleepProxy= (Sleep) Enhancer.create(Sleep.class,new CGLibDynamicProxy()); // 执行代理对象增强后的方法 sleepProxy.goToSleep();
注意:Enhancer 、 MethodInterceptor MethodProxy 均来自 org.springframework.cglib.proxy 包。
建议整合步骤2和3,使用匿名内部类的方式,如:
// 生成Sleep类的代理对象 Sleep sleepProxy = (Sleep) Enhancer.create(Sleep.class, new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 增强逻辑 System.out.println("前增强"); //调用委托类(父类中)的方法 Object result = methodProxy.invokeSuper(obj, objects); // 增强逻辑 System.out.println("后增强"); return result; } }); // 执行代理类增强后的方法 sleepProxy.goToSleep();
CGLib动态代理特点:
1、CGlib可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类;
2、由于是继承方式,如果是static方法,private方法,final方法等描述的方法是不能被代理的;
3、做了方法访问优化,使用建立方法索引的方式避免了传统JDK动态代理需要通过Method方法反射调用;
4、提供callback 和filter设计,可以灵活地给不同的方法绑定不同的callback。编码更方便灵活;
5、CGLIB会默认代理Object中equals,toString,hashCode,clone等方法。比JDK代理多了clone
END.