两种动态代理
JAVA中实现动态代理主要目的是为了实现AOP,即面向切面编程。
而动态代理主要是在程序运行期间,基于原类生成代理类,并且将需要织入的代码加入到代理类的方法中,可以实现动态的代码链接。
JAVA实现动态代理的两种方式分别为:
JDK代理
CGLIB代理
基于代码分析
结合着代码,我们进行两种动态代理方式的分析
JDK代理
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 基于JDK实现接口的代理
* 基于InvocationHandler类和Proxy类进行JVM代理实现
*/
public class JVMProxy {
//被代理对象
private Object target;
//param: 被代理对象实例
//return: 代理对象实例
public Object getProxy(Object target){
this.target = target;
ClassLoader classLoader = target.getClass().getClassLoader();
Class[] interfaces = target.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被代理方法织入代码
System.out.println("JDK代理方法执行前织入逻辑"+method.getName());
//调用被代理对象target,执行对应方法的原始逻辑
Object o = method.invoke(target,args);
//被代理方法后执行代码
System.out.println("JDK代理方法执行后织入逻辑"+method.getName());
return o;
}
};
return Proxy.newProxyInstance(classLoader,interfaces,h);
}
}
JVM代理基于InvocationHandler和Proxy类实现。每块重要代码的逻辑都在上面的示例中标明了;
GCLIB代理
package proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 基于CGLIB实现动态代理
* 使用Enhance类实现代理类的构建,基于intercept()方法捕获调用被代理对象的对应方法,随后调用代理对象增强后的方法,并且通过invokeSuper()实现原方法的调用
*/
public class CglibProxy implements MethodInterceptor {
//业务类对象,供代理方法中进行真正的业务方法调用
private Object target;
public Object getCglibProxy(Object target){
//为业务对象赋值
this.target = target;
//创建加强器,构造动态代理对象
Enhancer enhancer = new Enhancer();
//给动态代理对象设置被代理类
enhancer.setSuperclass(this.target.getClass());
//设置回调,对代理类所有方法的调用,都会调用CallBack,而CallBack则需要intercept()方法进行拦截
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before execute method " + method.getName());
//这里调用的是代码方法的invokeSuper()方法,而非invoke方法;和使用JVM实现代码有区别
//invokeSuper()调用的是被代理类的被调用的方法,是原本的方法;而invoke()调用的是代理类的本方法,会一直被intercept捕获,然后循环执行invoke;
Object result = methodProxy.invokeSuper(o,objects);
System.out.println("after execute method " + method.getName());
return result;
}
}
其中比较重要的就是Enhance类还有intercept方法,代理类的方法调用都会调用回调函数:CallBack方法,然后被intercept方法捕获,之后执行织入代码的逻辑;
两者的差别
面试官如果问到这个问题?你该怎么回答呢?
按照一些教程的说法,
两者的主要差别是:JDK动态代理只能代理接口,而CGLIB动态代理可以代理接口、普通的JavaBean。
但是其中的根因是什么?这个问题如果不加以探究,就会知其然,不知其所以然,如果面试官问到,我们也会支支吾吾,说不太清楚。
所以其中根因,我探究之后发现是因为:
JDK动态代理只在外部调用时生效,在通过this.method的调用时会失效。这也是@Transactional注解失效的场景之一,即类内部调用时,不会走JVM代理。这个原因我认为在于类间的调用,是通过Spring框架控制的,所以会走代理。
而CGLIB无论是内部调用还是外部调用,代理都会生效。
我们通过下面的代码验证一下
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import proxy.CglibProxy;
import proxy.JVMProxy;
import service.IJdkProxyService;
import service.Impl.AopDemoServiceImpl;
public class Application {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
IJdkProxyService a = (IJdkProxyService) cglibProxy.getCglibProxy(new AopDemoServiceImpl());
//代理类默认会将被代理类的所有方法都进行代理,比如hashCode()和toString()方法
a.toString();
JVMProxy jvmProxy = new JVMProxy();
IJdkProxyService b = (IJdkProxyService) jvmProxy.getProxy(new AopDemoServiceImpl());
b.toString();
}
}
执行的结果如下
CGLIB代理方法执行前织入逻辑 toString
CGLIB代理方法执行前织入逻辑 hashCode
CGLIB代理方法执行后织入逻辑 hashCode
CGLIB代理方法执行后织入逻辑 toString
JVM代理方法执行前织入逻辑toString
JVM代理方法执行后织入逻辑toString
有没有发现,执行结果中,CGLIB代理多打印了一个hashCode方法的记录,但是我们并没有调用hashCode方法;所以我们可以猜测是toString方法内部又调用了hashCode方法。
我们看下Object.toString()的源码,是否和我们猜测的一致?
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
我们发现果然如此,toString内部调用了hashCode方法,这次调用被CGLIB代理捕捉并拦截了,但没有被JVM代理捕捉到(实际上是没有调用JVM代理方法),导致两种代理的执行结果不一致。
所以分析到这里,我们就可以确定两种代理的主要区别了。而这种区别的主要原因,是两种代理底层实现不同,CGLIB中是对被代理对象方法的调用都会调用代理方法的回调函数,被intercept捕捉到之后就会调用织入的代码;这点和JVM代理不同。