JAVA实现动态代理的两种方式及主要的区别

两种动态代理

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代理不同。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值