动态代理解析

目录

静态代理

动态代理分类

1.jdk动态代理

2.cglib动态代理继承

基于接口代理(jdk动态代理)

基于继承代理(cglib动态代理)

增强类的三种方法


有动态则必有静态,先了解静态代理

静态代理

静态代理是指客户端无法直接访问目标类,通过访问代理类,由代理类帮忙访问目标类,代理类起到中介的作用。
比如:租客无法直接找到房东,由中介帮忙找房东,最后租客获得房东的信息。此时中介就是代理类。

下面介绍一下相关类。接口类,目标类,代理类,客户端类

1.接口类,规定目标类和代理类必须实现的方法

/**
 * 接口定义,目标类和代理类都要实现的接口
 */
public interface Subject {
    void test();
}

2.目标类,客户端想访问的最终目标

/**
 * 目标类,客户端最终想调用的类
 */
public class RealSubject implements Subject {
    @Override
    public void test() {
        System.out.println("我才是最终的目标类");
    }
}

3.代理类,由于客户端无法直接交互目标类,由代理作为中介传话。

/**
 * 代理类-代理客户类调用目标类
 */
public class ProxySubject implements Subject{
    private RealSubject realSubject;
    public ProxySubject(){
        System.out.println("我是代理类,我帮你调用目标类");
        realSubject = new RealSubject();
    }
    @Override
    public void test() {
        System.out.println("调用目标类开始。。。");
        if(realSubject != null){
            realSubject.test();
        }
        System.out.println("调用目标类结束。。。");
    }
}

4.客户端

public class Client {
    public static void main(String[] args) {
        //new代理类,代理帮忙调用目标类
        ProxySubject ps = new ProxySubject();
        ps.test();
    }
}

缺点:不灵活,重复代码太多

动态代理

动态代理的实现方式两种

  • jdk动态代理(基于接口实现)
  • cglib动态代理(基于继承)

注:spring 的AOP(面向切面编程)底层实现是动态代理

1、jdk动态代理(基于接口实现)

  • 有接口的情况下使用,即目标类和代理类必须基于统一的接口
  • 底层是通过反射来实现的
  • 在生成类的过程中比较高效

相关类和接口

  • 类:java.lang.reflect.Proxy     我们需要通过Proxy类动态生成代理类
  • 接口:InvocationHandler        代理类实现的接口(我们通过invoke方法,可以增强目标类的方法)

通过上面的两个接口,我们就可以生成代理对象

jdk动态代理为什么只能基于接口动态代理?因为生成的proxy class中,继承了Proxy类,实现了需要代理的接口,而Java是单继承,多实现的处理方式(需要看源码)

1.接口类,规定目标类和代理类必须实现的方法

/**
 * 接口定义,目标类和代理类都要实现的接口
 */
public interface Subject {
    String test(String info);
}

2.目标类

/**
 * 目标类,客户端最终想调用的类
 */
public class RealSubject implements Subject {
    @Override
    public String test(String info) {
        System.out.println(info);
        System.out.println("我是目标类:我才是最终的方法执行者");
        return "目标类回的信息。。。完成";
    }
}

3.代理类

/**
 * 动态代理类
 * 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,
 * 当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke方法来进行调用,
 * 在这里还可以对目标方法的加强
 */
public class JDKProxySubject implements InvocationHandler{
    //注入目标类
    private Subject proxySubject;
    public JDKProxySubject(Subject realSubject) {
        this.proxySubject = realSubject;
    }

    /**
     * @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 = null;
        try{
            //调用目标方法
            //利用反射构造目标对象
            //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
            result=method.invoke(proxySubject,args);
        }catch(Exception e){
            System.out.println("ex:"+e.getMessage());
            throw e;
        }finally{
            System.out.println("我是代理类:调用目标对象结束");
        }
        return result;
    }
}

4.客户端

public class Client {
    public static void main(String[] args) {
        /**
         * public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * 创建代理对象是在jvm运行时动态生成的,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,
         * 而是在运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
         *
         * 参数一:ClassLoader loader  类加载器对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
         * 参数二:Class<?>[] interfaces 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
         *参数三:InvocationHandler h 表示的是当动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
         *
         */
        //1.生成ClassLoader对象用来对生成的代理对象加载
        ClassLoader classLoader = Client.class.getClassLoader();
        //2.代理对象需要实现什么样的接口
        Class[] classes = {Subject.class};
        //3.生成代理类(静态代理的代理类是我们手动实现的,jdk动态代理动态生成,不用写实现类了)
        JDKProxySubject jdkPoxySubject = new JDKProxySubject(new RealSubject());
        Subject subject = (Subject) Proxy.newProxyInstance(classLoader, classes, jdkPorxySubject);

        System.out.println("我是代理类:我已经生成了");
        String test = subject.test("我是代理类:你好,目标类,我来调用你的方法");
        System.out.println(test);
    }
}

Prxoy.newProxyInstance的过程

  • 调用getProxyClass0寻找或生成指定代理类class对象(从缓存中取,如果没有,就生成一个放在缓存中 : 通过ProxyClassFactory生成)
  • 缓存调用ProxyClassFactory生成代理类,proxy class的生成最终调用ProxyClassFactory的apply方法.
  • ProxyGenerator.generateProxyClass使用生成的代理类的名称,接口,访问标志生成proxyClassFile字节码

总结,在使用静态代理的时候,我们要实现代理对象(具体方法),使用jdk动态代理,我们可以不实现代理对象,在运行时动态生成一个对象,直接调用目标类的方法。

jdk动态代理增强目标类的方法

下面说一下使用jdk动态代理,增强目标方法。其实所谓的增强就是对代理类的invoke方法进行改造。上面的invoke里面的system也可以理解为对目标方法的增强

/**
 * 动态代理类
 * 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,
 * 当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke 方法来进行调用
 */
public class JDKPorxySubject implements InvocationHandler{
    //注入目标类
    private Subject proxySubject;
    public JDKProxySubject(Subject realSubject) {
        this.proxySubject = realSubject;
    }

    /**
     * @param proxy  代表动态代理对象
     * @param method 代表正在执行的方法
     * @param args   代表调用目标方法时传入的实参
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
        System.out.println("我是代理类:我准备去调用目标类了");
        Object result = null;
        try{
            //调用目标方法
            //利用反射构造目标对象
            //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
            
            //1.增强传入参数
            args[0]=args[0]+",我被增强了";
            result=method.invoke(proxySubject,params);
            //2.增强返回参数
            result = result+"感谢来访(增强部分)";
        }catch(Exception e){
            System.out.println("ex:"+e.getMessage());
            throw e;
        }finally{
            System.out.println("我是代理类:调用目标对象结束");
        }
        return result;
    }
}

2.cglib动态代理(基于继承)

  • 没有接口的情况下,即由第三方类库实现。应用更加广泛,且在效率上更有优势
  • 底层是借助asm来实现的
  • 在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)

首先需要用到asm和cglib两个包

cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理

什么是ASM

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 子zi jie m文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。说白了asm是直接通过字节码来修改class文件。

1.目标类

/**
 * 目标类,客户端最终想调用的类
 */
public class RealSubject {
    public void test() {
        System.out.println("我才是最终的目标类");
    }
}

2.代理类

public class CglibMethodInterceptor implements MethodInterceptor {
    //如果cglib使用反射则用,否则无用
    private RealSubject realSubject;

    public CglibMethodInterceptor() {

    }
    public CglibMethodInterceptor(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    /**
     *原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,
     * 或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快
     * @param proxy   代理对像
     * @param method 目标类的方法
     * @param objects 目标类方法的参数
     * @param methodProxy 调用目标方法对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("开始cglib");

        Object result = null;
        try {
            //用methodProxy
            result = methodProxy.invokeSuper(proxy, objects);
            //使用反射
            //result = method.invoke(realSubject, objects);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("调用目标类结束");
        }
        return result;
    }
}

3.客户端

public class Client {
    public static void main(String[] args) {
        //一.主要增强类
        Enhancer enhancer = new Enhancer();
        //二.目标类,设置目标类(或者说被增强的类)
        enhancer.setSuperclass(RealSubject.class);
        //三.回调对象(设置代理对象)
        enhancer.setCallback(new CglibMethodInterceptor());
       // enhancer.setCallback(new CglibMethodInterceptor(new RealSubject()));
        //四.生成代理类对象,用cglib来增强RealSubject目标类
        RealSubject proxy = (RealSubject) enhancer.create();
        proxy.test();
    }
}

net.sf.cglib.proxy.Enhancer – 主要的增强类

net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现

net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:

        Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是代理对象,也不会出现死循环的问题。

-net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法

总结:

CGLIB与JDK动态代理区别
区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

增强类的三种方法

  • 继承
  • 装饰着模式
  • 动态代理

PS:idea debug动态代理的时候会重复打印输出

原因:单步调试时IDEA会调用目标类的toString()方法,代理类会代理目标类的所有方法(包括toString),因此会重复输出时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值