jdk代理和cglib代理区别和例子

代理目标

给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问,这句话的重点在于控制

如何实现

既然要控制那么如何实现呢?在方法执行之前、之后加上自己的逻辑处理就可以了。比如权限检查:如果一个方法在执行之前我们使用权限代理去检查一下这个人有没有执行某个方法的权限,如果有就执行,没有就直接return返回或者报错,这样就可以达到控制的目的。

实现方式

定义一个接口和实现类

Hello接口:

package com.bsx.test.proxy;

public interface Hello {
    void say(String name);
}

HelloImpl实现类:

package com.bsx.test.proxy;

class HelloImpl implements Hello {
    @Override
    public void say(String name) {
        System.out.println("Hello! " + name);
    }
}

1.静态代理

package com.bsx.test.proxy;

public class StaticProxy implements Hello {
    private Hello hello;

    public StaticProxy(Hello hello) {
        this.hello = hello;
    }

    private void before() {
        System.out.println("static proxy before ...");
    }

    private void after() {
        System.out.println("static proxy after ...");
    }

    @Override
    public void say(String name) {
        before();
        hello.say(name);
        after();
    }
}

2.jdk 动态代理

package com.bsx.test.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 接口动态代理需要2步
 * 第一步:定义额外的操作
 *  通过实现 InvocationHandler 接口,来定义在执行代理对象方法前后自己的动作。
 * 第二步:获取代理对象
 *  通过 Proxy.newProxyInstance 获取代理对象,这一步的作用是根据指定的1.classLoader,2.要代理的接口,以及3.传递进来的处理者。来生成真正的代理对象。
 */
public class InterfaceProxy implements InvocationHandler {
	// 获取代理对象
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    private Object target;

    public InterfaceProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object object = method.invoke(target, args);
        after();
        return object;
    }

    private void before() {
        System.out.println("interface proxy before ...");
    }

    private void after() {
        System.out.println("interface proxy after ...");
    }
}

2.1.保存jdk动态代理生成的class文件

默认输出到当前项目的跟目录下面的包中:com/sun/proxy/$Proxy0.class

public class HelloTest {
    public static void main(String[] args) {
// 设置系统属性,输出生成的.class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Hello helloImpl = new HelloImpl();
        InterfaceProxy interfaceProxy = new InterfaceProxy(helloImpl);
        Hello hello = interfaceProxy.getProxy();
        hello.say("dada");
        System.out.println(hello.add(10));
    }
}

2.2.查看jdk代理生成的class文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.dada.test.delegate.Hello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Hello {
	//可以看到这里面生成了至少4个方法属性
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
	/**
	动态代理类有一个带有InvocationHandler参数的构造方法
	目的是接受一个InvocationHandler参数然后在调用具体方法的时候通过
	InvocationHandler去调用
	 */
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

	/**
	* 可以看到我们的say方法被重写了,调用的过程是通过 InvocationHandler
	* 来调用的,而InvocationHandler在我们创建这个代理的时候就传入进了,
	* 而我们真正的对象,在创建InvocationHandler实例的时候被传入到
	* InvocationHandler中了,所以我们可以在InvocationHandler的
	* invoke方法中调用我们真正的代理对象的方法。然后在invoke调用真实对象
	* 方法的前后添加自己的处理逻辑
	*/
    public final void say(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
        	// 通过反射机制生成N个默认方法
        	// n=3+我们接口的方法数,如果接口中有一个方法那么n=3+1=4
        	// 另外的3个方法分别是Object的equals、hashCode、toString(),这么做的目的是跟我们自己的类保持一致
        	// 其中m1是Object类的equals方法
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            // 其中m2是Object类的toString方法
            m2 = Class.forName("java.lang.Object").getMethod("toString");
             // 其中m3是我们自己的方法,其实就是根据反射获取接口的方法
            m3 = Class.forName("com.dada.test.delegate.Hello").getMethod("say", Class.forName("java.lang.String"));
            // 其中m0是Object的hashCode方法
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

2.3.jdk动态代理的原理

从上面我们可以发现jdk动态代理的逻辑其实就是通过InvocationHandler接口来实现的

1.我们通过实现这个InvocationHandler接口InterfaceProxy来添加自己的切面逻辑,同时把我们的接口实现类HelloImpl作为属性传递到InvocationHandler实例中。
2.因为第一步已经持有了对自己接口Hello的实现类HelloImpl的引用,因此可以在invoke方法中调用这个接口的实现类来HelloImpl执行真正的方法调用,这个方法会通过参数传递进来。
3.真正的调用是通过代理类实现的,这个代理类继承了Proxy类,通过带有InvocationHandler参数的构造方法来创建实例,并把InvocationHandler传递到了Proxy类中,它覆写了接口中所有的方法,同时覆写了Object类的equals、hashCode、toString方法。而这些方法的实现全部都是通过super.h.invoke方法来实现的,我们知道super.h正好就是我们通过构造方法传递给父类的,因此对于接口的调用其实调用的父类的invoke方法来实现的,这里实现类我们已经有了,那么方法如何确定呢?通过源码我们可以看到类的开始就定义了所有需要覆写的方法,这些方法通过static{}代码块来初始化,当我真正调用代理类的say方法的时候其实就是把我们的接口的say方法作为Method类型的参数传递给父类的invoke方法去执行了,
代码如下:

 public final void say(String var1) throws  {
        try {
        // 这里的m3就是static块中进行初始化的
        // m3 = Class.forName("com.dada.test.delegate.Hello").getMethod("say", Class.forName("java.lang.String"));
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

3.CGLIB动态代理

package com.bsx.test.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 动态代理需要2步:
 * 第一步:定义额外的操作
 *  通过实现 MethodInterceptor 接口,来定义在执行代理对象方法前后自己的动作。
 * 第二步:获取代理对象
 *  通过 Enhancer.create 获取代理对象,因为这个只需要。
 */
public class CglibProxy implements MethodInterceptor {
	// 获取代理对象
    public <T> T getProxy(Class<T> clazz) {
        return (T) Enhancer.create(clazz, this);
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    private void before() {
        System.out.println("cglib proxy before ...");
    }

    private void after() {
        System.out.println("cglib proxy after ...");
    }
}

4.cglib动态代理的字节码分析

4.1.保存动态代理生成的字节码文件

 public static void main(String[] args) {

//通过设置系统属性来输出cglib生成的字节码文件
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/admin/IdeaProjects/temp/jarTest/com/sun/proxy");
        CglibProxy cglibProxy = new CglibProxy();
        // cglib 代理的是类,它的实现方式是通过继承一个类作为它的子类来覆盖父类中的方法
        HelloImpl helloProxy = cglibProxy.getProxy(HelloImpl.class);
        helloProxy.say("dada");
    }

4.2.查看动态代理生成的字节码文件主要方法

// cglib生成了3个文件,一个是继承了FastClass类的文件

public class HelloImpl$$EnhancerByCGLIB$$d77abcd1$$FastClassByCGLIB$$843af610 extends FastClass {
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        d77abcd1 var10000 = (d77abcd1)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
          // 这里的方法很多我们不需要关心,只需要知道我根据一个int值就可以知道要调用的是那个方法
          // invokeSuper最终调用是在这里执行的,也就是调用了实现类的CGLIB$say$1方法,那这个方法自然应该是在代理类里面定义的,我们去看看
            case 16:
                var10000.CGLIB$say$1((String)var3[0]);
                return null;
           
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
// 一个是代理类,代理类里面方法也很多,我们只需要关注2个方法
public class HelloImpl$$EnhancerByCGLIB$$d77abcd1 extends HelloImpl implements Factory {
	// 这个就是fast类里面调用的方法,可以看到这个方法就是调用父类的方法
    final void CGLIB$say$1(String var1) {
        super.say(var1);
    }

// 这个方法就是覆写的父类的say方法
    public final void say(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
// 如果拦截器不为空就调用拦截器的逻辑
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$say$1$Method, new Object[]{var1}, CGLIB$say$1$Proxy);
        } else {
        // 如果没有定义拦截器就直接调用父类的方法
            super.say(var1);
        }
    }
}

4.3.cglib调用流程

第一步:调用代理类覆写后的say方法
public final void say(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
// 如果拦截器不为空就调用拦截器的逻辑,这里自然执行拦截逻辑
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$say$1$Method, new Object[]{var1}, CGLIB$say$1$Proxy);
        } else {
        // 如果没有定义拦截器就直接调用父类的方法
            super.say(var1);
        }
    }
}
第二步:调用拦截器中的intercept方法
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        // 调用父类方法
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

第三步:调用methodProxy.invokeSuper(o, objects);方法
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            // 具体参数值看下面debug图,可以看到这里调用的是上面我们提到的那个方法
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

在这里插入图片描述

第四步:调用FastClass的方法,这一步可以看到这个invoke方法中数字为9的正是代理类里面的方法
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        4eca5d4c var10000 = (4eca5d4c)var2;
        int var10001 = var1;
        try {
            switch(var10001) {
            case 9:
                var10000.CGLIB$say$1((String)var3[0]);
                return null;
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
第五步:调用代理类里面的方法CGLIB$say$1,正是这里调用了父类的方法
 final void CGLIB$say$1(String var1) {
        super.say(var1);
    }
总结

cglib的调用流程就是:通过调用拦截器的intercept方法来实现对被代理类的调用。而我们的拦截逻辑可以写在intercept方法的methodProxy.invokeSuper(o, objects);的前后实现拦截。

5.测试例子

package com.bsx.test.proxy;

public class TestProxy {
    public static void main(String[] args) {
        HelloImpl helloImpl = new HelloImpl();
        //1.静态代理
        StaticProxy staticProxy = new StaticProxy(helloImpl);
        staticProxy.say("dada");
        System.out.println("==========");
        //2.jdk 动态代理
        InterfaceProxy interfaceProxy = new InterfaceProxy(helloImpl);
        // 注意动态代理返回的是接口类型的对象
        // 所以,使用接口来标记改对象是可以的,
        // 如果使用实现类来接收该类型是会报错的
        // 这就像是说人是动物是正确的,但是动物是人就不对了
        Hello hello = interfaceProxy.getProxy();
        hello.say("dada");
        System.out.println("==========");
        // 3.cglib 动态代理
        CglibProxy cglibProxy = new CglibProxy();
        // cglib 代理的是类,它的实现方式是通过继承一个类作为它的子类来覆盖父类中的方法
        HelloImpl helloProxy = cglibProxy.getProxy(HelloImpl.class);
        helloProxy.say("dada");
    }
}

jdk代理和CGLIB代理的区别

一、简单来说:

JDK动态代理只能对实现了接口的类生成代理,而不能针对类,它的实现原理是通过InvocationHandler.invoke方法实现对实现类方法的调用(InvocationHandler实例已经持有了对实现类对象的引用了),然后实现方法前后的拦截。

CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承),然后通过MethodIntercept.intercept方法来实现在调用父类方法的前后执行拦截。
  
  jdk代理是通过持有实现类的引用来实现对实现类方法的调用的,而CGLIB是通过调用父类的方法来实现对被代理类的方法调用的。

二、Spring在选择用JDK还是CGLiB的依据:

  • (1)当Bean实现接口时,Spring就会用JDK的动态代理
  • (2)当Bean没有实现接口时,Spring使用CGlib实现。
  • (3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)

三、CGlib比JDK快?

(1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

(2)在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。

四、代理的嵌套

代理的嵌套是指目标类方法内部调用会导致多次拦截的情况,如果是jdk代理不会出现这种情况,因为jdk对于方法的执行最终是通过实现类自己调用实现的,实现类内部调用自然不会被拦截。而CGLIB代理则不一样,它是通过复写父类方法实现的,虽然最终会调用父类的方法,但是执行的主体是代理类对象,因此方法之间的调用其实是代理类方法之间的调用,很明显代理类中的方法都被拦截过了,因此会出现代理嵌套的情况。

验证一下上面的结论:

public class HelloImpl implements Hello {
    public void say(String name) {
        System.out.println("Hello! " + name);
    }

    @Override
    public String toString() {
        return "HelloImpl";
    }

    public void add() {
        say("方法内部调用");
    }

}

测试代理效果

public class CglibTest {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        HelloImpl helloProxy = cglibProxy.getProxy(HelloImpl.class);
        helloProxy.add();
    }
}
// 输出结果:
cglib proxy before ...
cglib proxy before ...
Hello! 方法内部调用
cglib proxy after ...
cglib proxy after ...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值