• 代理设计模式之静态代理与动态代理(超..)详解


      在学习Spring框架的时候,有一个重要的思想就是AOP,面向切面编程,利用AOP的思想结合Spring的一些API可以实现核心业务与辅助业务的分离,即可以在执行核心业务时,将一些辅助的业务加进来,而辅助业务(如日志,权限控制等)一般是一些公共业务,这样就实现了两者的分离,使得核心业务的代码更加纯粹,而且辅助业务也能得到复用,这一篇笔记是当时学习spring的时候写的,使用springAPI以及自定义类 实现AOP的一个例子 ,.AOP底层就是通过动态代理来实现的,最近专门学习了一下代理模式,反射机制,(动态代理也是基于反射机制来实现的)对代理的理解又立体了一点点,下面通过例子来讲解一下代理.

    为什么需要代理:

      打一个最简单的比方,我现在想要学习,那么就必须得把书包拿过来,把书掏出来,准备好纸笔,然后开始学习,等学完了我还得收拾书,把书塞回书包里,还得整理一下书包,这是一个完整的学习的过程,但是我很懒,不想动弹,只想学习,那可能就得让妈妈帮我把书包拿过来,把书打开,我只管学习就好了,学完以后,妈妈再帮我把书整理好放回去.(我这是打个什么破比方...),在这里,妈妈就代表了一个代理对象,要学习的人是我,而我只管学习,这样效率才最高,至于其他的交给代理对象(妈妈)做就好了,画一个丑陋的的图表示一下:

      再联想我们最开始接触jdbc操作数据库的时候,业务层每一个方法,都需要1.打开数据库连接,2.执行我们想要的操作3.关闭数据库连接.这样就使得业务层代码不够纯粹,我的功能是查询用户数据,打开和关闭数据库连接关我毛事?我干嘛要去干这件事?这就是传统开发中存在的一个问题,现在通过代码演示一下:

    /**
     * 简单业务层接口,只有一个save方法
     */
    interface UserService{
        public void saveUser();
    }
    ////////////////////////////////////////////////////////
    /** * 业务层实现类,实现save方法 */ class UserServiceImpl implements UserService{ @Override public void saveUser() { System.out.println("1:打开数据库连接"); System.out.println("2:保存用户信息"); System.out.println("3:关闭数据库连接"); } }

    我们可以看到其实这个方法的实现是有问题的,核心业务与辅助业务写在了一个方法中,不但业务冗余了不说,像开关数据库连接这样的公共操作也大量的重复,这时候就出现了代理模式的思想,我们可以使用代理模式的思想改写一下上面的代码:

    package com.wang.proxy;
    /**
     * 简单业务层接口,只有一个save方法
     */
    interface UserService{
        public void saveUser();
    }
    
    /**
     * 代理类
     */
    class UserServiceProxy implements UserService{
    
        private UserService userService;
        
        public UserServiceProxy(UserService userService) {
            super();
            this.userService = userService;
        }
    
        public void open(){
            System.out.println("1:打开数据库连接");
        }
        public void close(){
            System.out.println("3:关闭数据库连接");
        }
        @Override
        public void saveUser() {
            this.open();
            userService.saveUser();
            this.close();
        }
        
    }
    
    /**
     * 业务层实现类,实现save方法
     */
    class UserServiceImpl implements UserService{
    
        @Override
        public void saveUser() {
            System.out.println("2:保存用户信息");
        }
        
    }
    /**
     * 测试类
     */
    public class TestProxy {
        
        public static void main(String[] args) {
            UserService userService =new UserServiceProxy(new UserServiceImpl());
            userService.saveUser();
        }
    }

      通过测试代码打印结果,和上面没有使用代理的代码是完全一样,但是通过修改可以清晰地看到,业务层代码变得很纯很纯的,只剩下最核心的保存用户信息的代码.通过代理模式,我们可以抽取出核心业务与辅助业务,但是问题随之而来了,我这里编写的UserServiceProxy是挺不错,可是它只能服务与UserService这个接口的对象啊,如果我有一千个业务,那岂不是要编写一千个代理类,毕竟一千个人心中就有一千个哈姆雷特啊,其实这种代理模式就是静态代理,它的缺点很明显,静态代理只能服务于一种类型的对象,不利于业务的扩展,那么我们就想了,能不能设计一个代理类可以服务于所有的业务对象呢?于是,这时候,动态代理就闪亮登场了.

      如果要实现动态代理,那么你要编写的那个代理类就需要实现一个InvocationHandle接口.这个接口所在位置是java.lang.reflect.InvocationHandler.看到reflect我们就能知道,动态代理肯定是通过反射来实现的了,这个接口中有一个方法:

        Object  invoke(Object proxy, Method method, Object[] args)    :在代理实例上处理方法调用并返回结果。

        invoke方法其实是反射里边的一个方法,在这个方法中有三个参数:

        Ojbect proxy:表示需要代理的对象

        Method method:表示要操作的方法

        Object[] args:method方法所需要传入的参数(可能没有为,null.也可能有多个)

      如果要想让代理设计真正可用,我们还必须有一个代理类对象产生,这有用到了另一个类:java.lang.reflect.Proxy.我的中文jdk文档对他的描述是:

     Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。 

      在这个类下面,我们找到了这样一个方法:

      public static Object newProxyInstance(ClassLoader loader,
                                           Class<?>[] interfaces,
                                           InvocationHandler h)
                                      throws IllegalArgumentException

      该方法返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序.方法中有三个参数:

       参数:

         loader - 定义代理类的类加载器

         interfaces - 代理类要实现的接口列表

         h - 指派方法调用的调用处理程序

       返回:

        一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口 

      ok,有了这些知识,我们就可以来编写一个万能的动态代理类了.

    class ServiceProxy implements InvocationHandler {
    
        private Object target=null;//保存真实业务对象
        /**
         * 返回动态代理类的对象,这样用户才可以利用代理类对象去操作真实对象
         * @param obj  包含有真实业务实现的对象
         * @return   返回代理对象
         */
        public Object getProxy(Object obj) {
            this.target=obj;//保存真实业务对象
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
                    .getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            Object result=method.invoke(target, args);//通过反射调用真实业务对象的业务方法,并且返回
            return result;
        }
    
    }

    现在来测试一下好不好使:

    /**
     * 测试类
     */
    public class TestProxy {
    
        public static void main(String[] args) {
                UserService service=(UserService) new ServiceProxy().getProxy(new UserServiceImpl());
                service.saveUser();
        }
    }
    
    
    //打印结果:
    //        2.保存用户信息

      看!!!!完美,你说什么?为什么没有打开数据库连接,和关闭数据库连接呢?  简单,我们直接在ServiceProxy中加入两个方法,open()和close(),一个放到method.invoke()上面,一个放到下面,就可以了,注意,我们现在编写的是一个万能的动态代理类了,没有和任何的业务层接口关联,所以接下来你就可以为所欲为了.

      下面来总结一下:

      动态代理和静态代理相比较,最大的好处就是接口中声明的所有的方法都被转移到一个集中的方法中去处理,就是invocke()方法.这样在接口中声明的方法比较多的情况下我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

          动态代理只能代理接口,代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法的返回值是被代理接口的一个实现类。

      那么有没有可能我们可以不依赖接口呢?这时候就需要CGLIB实现动态代理了,这个jar包可以让我们摆脱接口的烦恼,感兴趣的自己去查一下吧,反正我也没用,不会...

  • 相关阅读:
    解决安装mysql 到start service出现未响应问题
    【日历】自定义(上下月切换)
    html2canvas 无法渲染网络图片及本地 解决方案
    css 弹性盒子--“垂直居中”--兼容写法
    CSS垂直居中
    window.postMessage 在iframe父子页面数据传输
    小程序 rich-text 处理显示
    前端规范
    CSS技巧(一):清除浮动
    博客地址迁移
  • 原文地址:https://www.cnblogs.com/fingerboy/p/5335328.html
Copyright © 2020-2023  润新知