• 代理模式:静态代理与动态代理(JDK、CGLIB、javassist动态代理)


    参考资料


    一,代理模式

      代理模式是23种设计模式中的一种,代理模式就是给一个对象提供一个代理,就像我们生活中的中介(租房、留学、相亲),由代理对象控制原目标对象的访问,所以在代理对象中就可以实现对目标对象功能的增强、扩展、甚至改写。

    • 为什么需要代理?

      因为一个良好的设计不应该被轻易的修改,这正是开闭原则的体现:一个良好的设计应该对修改关闭,对扩展开放。而代理正是为了扩展类而存在,他可以控制对现有类(也就是被代理的类)的访问,通俗的说就是可以拦截对现有类方法的调用并做出响应的处理

      代理模式的实际应用场景:aop、权限控制、服务监控、缓存、日志、限流、事务、拦截过滤等。

    1.1 代理模式分为静态代理动态代理

    1.1.1 静态代理

      静态代理就是我们自己手写代理类;aspect静态代理(编译器生成代理类)。静态代理:(目标接口、目标接口实现、代理类)。

    // 创建目标接口TargetInterface
    public interface TargetInterface {
        void sayHello(String name);
        void sayThanks(String name);
    }
    
    // 创建目标接口的实现类
    public class TargetIntegerfaceImpl implements TargetInterface{
        @Override
        public void sayHello(String name) {
          System.out.println("sayHello: " + name);
        }
    
        @Override
        public void sayThanks(String name) {
          System.out.println("sayThanks: " + name);
        }
    }
    
    // 创建测试类
    public class proxy_static_test {
        public static void main(String[] args) {
            // 1. 通过创建接口的实现类, 来代理该接口
            TargetInterface targetInterface = new TargetIntegerfaceImpl();
            targetInterface.sayHello("张三");
            targetInterface.sayThanks("李四");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    // 创建静态代理对象TargetProxy
    public class TargetProxy implements TargetInterface {
    
        private TargetInterface targetInterface;
    
        public TargetProxy(TargetInterface targetInterface) {
            this.targetInterface = targetInterface;
        }
    
        @Override
        public void sayHello(String name) {
            // 使用代理来达到对目标方法增强
            System.out.println("start..........");
    
            // 中间调用目标接口的真正实现
            targetInterface.sayHello(name);
    
            System.out.println("end..........");
        }
    
        @Override
        public void sayThanks(String name) {
            // 使用代理来达到对目标方法增强
            System.out.println("start..........");
    
            // 中间调用目标接口的真正实现
            targetInterface.sayThanks(name);
    
            System.out.println("end..........");
        }
    }
    
    // 创建测试类
    public class proxy_static_test {
        public static void main(String[] args) {
            // 2. 使用静态代理的方法来调用接口中的方法
            /*
                创建静态代理实例TargetProxy, 传入一个该接口的实现类, 这样静态代理Proxy 对象就已经被我们创建好了, 它含有目标接口的引用,
             同时也含有目标接口的具体实现类, 当我们想调用接口中的方法时, 直接去找代理对象就可以了, 这个步骤中代理对象就可以对我们的方法进行增强.
             */
            TargetProxy targetProxy = new TargetProxy(new TargetIntegerfaceImpl());
            targetProxy.sayHello("王五");
            targetProxy.sayHello("赵六");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    1.1.2 动态代理

    A. JDK 动态代理

      底层采用的是Java反射技术,获取真正的代理类,代理对象会在jvm 中进行创建,通过指定接口的ClassLoader 来创建一个指定接口的代理对象,代理类会实现InvocationHandler接口,来拦截我们的接口请求,在invoke()中做增强与请求转发处理:

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class interfaces) {
    	// 1. jvm 内存中生成一个class 类
    	// 2. 根据该class 类反射一个代理对象 $Proxy@
    	return (T) Proxy.newProxyInstance(interfaces.getClassLoader(), new Class<?>[] {interfaces}, this);
    }
    
    /**
     * invoke() 是 InvocationHandler 接口中代理的拦截方法, 此处覆盖该方法, 让它对目标接口的方法进行拦截
     *
     * @param proxy 这个就是我们的代理类, 就是jdk 生成的那个以 $Proxy. 开头的代理类
     * @param method interface.method() 目标接口中的方法
     * @param args interface.method(args) 目标接口中的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("TargetProxy 前置增强.....");
    
        // 调用目标接口中的方法
        Object result = method.invoke(target, args);
    
        System.out.println("TargetProxy 后置增强.....");
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    B. CGLIB 动态代理

      使用CGLIB实现动态代理,完全不受代理类必须实现接口的限制,CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK8之前,CGLIB动态要比JDK的动态代理(JDK使用的是Java反射)效率要高。唯一需要注意的是,如果被代理的类被final修饰,那么它将不可被继承,即不可被代理,同样,如果被代理的类中存在final修饰的方法,那么该方法也不可被代理。因为CGLIB原理是动态生成被代理类的子类。

      CGLIB原理:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,它比Java反射的JDK动态代理要快。

      CGLIB底层:使用字节码处理器框架ASM,来转换字节码并生成新的类。不推荐直接使用ASM,因为他要求你必须对JVM内部结构,包括class文件的格式和属性都很熟悉。

      CGLIB缺点:对于final类与final方法,CGLIB无法进行代理。

      CGLIB在代理接口与代理实体类时有不同的处理代理逻辑,代理类实现MethodInterceptor接口,来拦截我们的接口请求,在intercept()中做增强与请求转发处理:

    /**
     * cglib动态代理关键代码, 获取真正的cglib 动态代理类
     * 1. jvm 内存中生成一个class 类
     * 2. 根据该class 类反射创建一个代理对象 $Proxy@564546548
     * @param tClass
     * @param 
     * @return
     */
    public <T> T getProxy(Class<T> tClass) {
    
        // 字节码增强工具类
        Enhancer enhancer = new Enhancer();
    
        // 实体类代理 -> 设置父类
        enhancer.setSuperclass(tClass);
        // 接口代理 -> 设置接口
    //        enhancer.setInterfaces(new Class[] {tClass});
    
        // 设置回调类
        enhancer.setCallback(this);
    
        //创建代理类
        return (T) enhancer.create();
    }
    
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib 代理增强开始......");
    
        // 如果是代理类则直接调用目标方法
        Object result = methodProxy.invokeSuper(obj, args);
        // 如果是代理接口则需要像mybatis一样自己实现接口
    //        System.out.println("CGLIB代理接口自己实现啦啦啦:方法参数含有+");
    //        Arrays.stream(args).forEach(System.out::println);
    
        System.out.println("cglib 代理增强结束......");
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    C. javassist 动态代理

      javassist与JDK代理类似,它是通过代理工厂来创建代理对象的,代理类实现MethodHandler接口,来拦截我们的接口请求,在invoke()中做增强与请求转发处理:

    public Object getProxy(Class<?> tClass) throws InstantiationException, IllegalAccessException {
        // 代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
    
        // 设置需要创建子类的父类
        proxyFactory.setSuperclass(tClass);
    
        // 通过字节码技术动态创建子类实例
        proxyFactory.writeDirectory = "D:\\software\\data\\idea\\mybatis-3-mybatis-3.5.4";
        Object proxy = proxyFactory.createClass().newInstance();
    
        // 在调用目标方法之前, Javassist会回调MethodHandler接口方法拦截, 在其中可以实现自己的代理增强方法, 类似JDK中的InvocationHandler
        ((ProxyObject) proxy).setHandler(this);
    
        // 返回代理对象
        return proxy;
    }
    
    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        System.out.println("Javassist 事务开始.....");
    
        // 调用目标类中的方法
        Object result = proceed.invoke(self, args);
    
        System.out.println("Javassist 事务结束.....");
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    刨根问底 Kafka,面试过程真好使
    xxl-job集成钉钉群告警
    向上转型 向下转型 重写 多态 ---java
    C和指针 第15章 输入/输出函数 15.12 刷新和定位函数
    Find和FirstOrDefault()的区别
    多线程处理大批量数据操作
    linux下vsode超级用户运行
    【论文学习】《Source Mixing and Separation Robust Audio Steganography》
    数据结构排序算法之直接插入排序与希尔排序【图文详解】
    docker学习笔记
  • 原文地址:https://blog.csdn.net/zy_zhangruichen/article/details/126510460