zoukankan      html  css  js  c++  java
  • 动态代理简易剖析

    如果您对“代理模式”的知识还不清晰,可以看我的另一篇博文 -- 代理模式 ,谢谢

    两种代理模式结构(两种模式的区别?):

    (1)静态代理 是在程序运行前就事先写好代理类,可以手工编写也可以使用工具生成,缺点是每个业务类都要对应一个代理类,特别不灵活也不方便,于是就有了动态代理。

    (2)动态代理 程序在运行期间动态构建代理对象和动态调用代理方法的一种机制。

    动态代理的实现方式?

    实现方式有JDK Proxy和CGLib(Code Generation Library)两种。

    JDK Proxy和CGLib的区别?

    (1)   JDK Proxy是Java语言自带的功能,无需通过加载第三方类实现;而CGLib 是第三方提供的工具。

    (2)   实现方式不同

    • JDK Proxy 是通过拦截器加反射的方式实现的只能代理继承接口的类,实现和调用起来比较简单;
    • CGLib是 基于 ASM(一个Java字节码操作框架) 实现的,无需通过接口来实现,它是通过实现子类的方式来完成调用的,性能比较高;

    动态代理的使用场景有哪些?

    动态代理的常见使用场景有RPC框架的封装、AOP(面向切面编程)的实现、JDBC的连接等。

    Spring 中的动态代理是通过什么方式实现的?

    Spring框架中同时使用了两种动态代理JDKProxy和CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加 <aop:aspectj-autoproxy proxy-target-class="true"/> 即可。

    JDK Proxy

    JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可。

    示例代码

    首先假设一种业务场景,需要实现对用户进行CRUD的操作,所以我们创建了一个UserService接口和UserServiceImpl的实现类。

      用户接口

    /** 
     * @author 佛大Java程序员
     * @since 1.0.0
     */
    public interface UserService {
        /**
         * 添加用户
         */
        void addUser();
    
        /**
         * 删除用户
         */
        void updateUser();
    }

      用户实现类

    /**
     * @author 佛大Java程序员
     * @since 1.0.0
     */
    public class UserServiceImpl implements UserService {
    
        public void addUser() {
            System.out.println("添加一个用户");
        }
        public void updateUser() {
            System.out.println("更新一个用户");
        }
    }

       代理类

    /**
     * JDK Proxy
     * @author 佛大Java程序员
     * @since 1.0.0
     */
    public class JdkDynamicProxy implements InvocationHandler {
        /**
         * 代理对象
         */
        private Object target;
    
        /**
         * 获取到代理对象
         *
         * JDk帮我们实现了动态代理,使用的是newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * ClassLoader loader,:指定当前目标对象使用类加载器
         * Class<?>[] interfaces,:代理类需要实现的接口列表
         * InvocationHandler h:调用处理程序,将目标对象的方法分派到该调用处理程序
         * @param target
         * @return
         */
        public Object getInstance(Object target) {
            this.target=target;
            // 取得代理对象
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
        }
    
        /**
         * 执行代理方法
         * @param proxy 代理对象
         * @param method 代理方法
         * @param args 方法的参数
         * @return
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         *
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException,IllegalAccessException
        {
            //实现扩展功能,动态代理之前的业务处理.
            System.out.println("------插入前置日志-------------");
            // 执行调用方法,这个method就是addUser()/updateUser(),target是指new UserServiceImpl对象(也就是目标对象),参数为null。
            Object result = method.invoke(target,args);
            //实现扩展功能,动态代理之后的业务处理.
            System.out.println("------插入后置日志-------------");
            return result;
        }
    
    }

      测试类

    /**
     * @author 佛大Java程序员
     * @since 1.0.0
     */
    public class JdkDynamicProxyTest {
        public static void main(String [] args) {
            // 执行 JDK Proxy
            JdkDynamicProxy dynamicProxy = new JdkDynamicProxy();
            UserService userService = (UserService) dynamicProxy.getInstance(new UserServiceImpl());
            userService.addUser();
            userService.updateUser();
        }
    }

    运行结果:

    可以看出 JDK Proxy 实现动态代理的核心是实现 InvocationHandler 接口,我们查看 InvocationHandler 的源码,会发现里面其实只有一个 invoke() 方法,源码如下:

    这是因为在动态代理中有一个重要的角色也就是代理器,它用于统一管理被代理的对象,显然InvocationHandler就是这个代理器,而invoke()方法则是触发代理的执行方法,我们通过实现Invocation 接口来拥有动态代理的能力。

    补充:您可能对运行结果产生疑问,

         1)动态代理类是如何创建的?

         2)  它是如何调用JdkDynamicProxy中的invoke的方法的? invoke方法又是什么含义?

    请阅读  -- https://www.cnblogs.com/qdhxhz/p/9241412.html

    CGLib 的实现

    在使用 CGLib 之前,我们要先在项目中引入 CGLib 框架,在 pom.xml 中添加如下配置: 

     示例代码:

    说明:用到了上面JDK Proxy示例中,user用户接口类UserService和用户实现类 UserServiceImpl

    代理类

    /**
     * @author 佛大Java程序员
     * @since 1.0.0
     *
     */
    public class CGLibProxy implements MethodInterceptor {
        //代理对象
        private Object target;
    
        public Object getInstance(Object target) {
            this.target=target;
            Enhancer enhancer = new Enhancer();
            //设置父类为实例类
            enhancer.setSuperclass(this.target.getClass());
            //设置回调方法
            enhancer.setCallback(this);
            //创建代理对象(创建并返回子类对象)
            return enhancer.create();
        }
    
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
            System.out.println("方法调用前业务处理");
            //执行方法调用
            Object object = methodProxy.invokeSuper(o,objects);
            System.out.println("方法调用后业务处理,打印一下日志");
            return object;
        }
    
    }

      测试类

    /**
     * @author 佛大Java程序员
     * @since 1.0.0
     */
    public class CGLibProxyTest {
        /**
         * 执行 CGLib 的方法调用
         * @param args
         */
        public static void main(String [] args) {
            // 创建 CGLib 代理类
            CGLibProxy cgLibProxy = new CGLibProxy();
            // 初始化代理对象
            UserService userService = (UserService) cgLibProxy.getInstance(new UserServiceImpl());
            // 执行方法
            userService.addUser();
            userService.updateUser();
        }
    
    }

    运行结果:

    CGLib和JDKProxy的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib在初始化被代理类时,是通过Enhancer对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。

    常见面试题

    (1) 如何实现动态代理?JDK Proxy 和 CGLib 有什么区别?

    (2) 动态代理和静态代理有什么区别?

    (3) 动态代理的使用场景有哪些?

    (4) Spring 中的动态代理是通过什么方式实现的?

    参考/好文

    Java 面试真题及源码 34 讲 --

    https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59

    雨点的名字 -- 设计模式之代理模式

    https://www.cnblogs.com/qdhxhz/p/9241412.html

    掘金 -- 3种代理模式-理解Spring Aop

    https://juejin.im/post/5cea0180e51d4550bf1ae7db#heading-3

     

     

     

    希望本文章对您有帮助,您的转发、点赞是我的创作动力,十分感谢。更多好文推荐,请关注我的微信公众号--JustJavaIt
  • 相关阅读:
    python框架之Django(2)-简单的CRUD
    python框架之Django(1)-第一个Django项目
    实习进度13
    实习进度12
    实习进度11
    实习进度10
    学习进度08
    毕设进度07
    毕设进度06
    毕设进度05
  • 原文地址:https://www.cnblogs.com/liaowenhui/p/12659152.html
Copyright © 2011-2022 走看看