zoukankan      html  css  js  c++  java
  • Java设计模式-代理模式

    代理模式

    使用代理模式创建代表(representative)对象,让代理对象控制某对象的访问,被代理的对象可以是远程的对象,创建开销大的对象或需要安全控制的对象。——[Head First 设计模式]

    简单的讲就是 :为服务对象提供代理,通过代理控制对服务对象的访问范围

    • 生活中的场景
    • 代理模式的好处
    • 技术上的应用
    • 分类
    • 静态代理
    • JDK动态代理
    • Cglib动态代理
    • 小结

    生活中代理模式的场景

    设计模式-代理模式

    思考一下,其实在我们生活中不乏出现代理模式的场景:

    1. 滴滴出行;出门搭车并不需要直接联系某个司机,而是通过滴滴下单,滴滴就会帮我们将乘车需求通知到附近的司机;
    2. 淘宝购物;相信大多数人会在淘宝或其他电商平台上购物,而作为用户,我们可以通过淘宝就可以了解到商品的具体详情,而不需要到线下店了解;
    3. 链家租房;生活中找租房通常会选择中介代理,比如链家,这样一来,用户不需要四处奔波就可以通过代理获取到租房信息;

    为什么需要代理呢?因为代理最大的好处就是方便,上面例子,体现代理的第一个作用:服务透明,屏蔽具体实现,用户无法感知真正的服务提供方;但是通过代理就可以实现自己的需求;

    代理模式的好处

    1. 扩展,逻辑解耦,屏蔽真实实现,更换被代理对象,使用无感知;
    2. 保护,非直接调用,只提供本该提供的特定服务,防止越界访问;
    3. 简化,职责专一,被代理者只负责业务逻辑,不关心其他事项;

    保护的理解:比如链家,只提供房东的房屋信息,对于房东的其他信息,客户无从知道,从而起到了保护作用
    简化的理解:比如滴滴,对于司机来说,只负责正真的运输,不关心开车以外的事情,比如,找客源,讲价钱等等,简化了整个流程。

    技术上的应用

    代理模式最典型的的应用就是AOP;讲到AOP,相信大家脑海里,都会浮现出很多关键字:面向切面编程、静态代理、JDK动态代理,CGLIB;

    AOP(Aspect Orient Programming),面向切面编程,用于在系统各个模块中的业务流程中织入通用的处理逻辑,比如事务管理、操作日志、缓存、异常处理等等。

    有关AOP的介绍后续会用心的篇章来讲解,本文主要讲解代理的实现。

    分类

    从功能的角度划分,可以分为:远程代理、虚拟代理、保护代理、智能引用代理;
    按实现的角度划分,则有静态代理和动态代理;动态代理还可以分为JDK动态代理和CGLIB动态代理

    这里写图片描述

    在了解具体实现之前,先详细看一下上图;
    首先是Service提供了服务接口,ServiceImpl和Proxy都必须实现Service接口;通过实现同个接口,使得上层调用可以像ServiceImpl一样使用Proxy的服务;
    Proxy持有Service的引用,以便后续将请求转发给ServiceImpl;
    当然除了接口,基于继承的方式也是可以实现代理模式,比如Cglib动态代理;
    了解以上内容,已经大概了解了代理模式的具体实现。

    静态代理

    假设我们需要提供一个提供新增用户的服务接口给上层业务使用;
    我们首先要先制定好接口UserService,将接口 addUser(String user) 开放给上层业务;

    /**
     * 开放代理的接口
     */
    public interface UserService {
        public void addUser(String user);
    }
    

    接下来,编写具体实现的服务类 UserServiceImpl.java

    /**
     * 实际业务逻辑
     */
    public class UserServiceImpl implements UserService{
        @Override
        public void addUser(String user) {
            System.out.println(String.format("add user:%s success", user));
        }
    }
    

    测试使用静态代理

    /**
     * 代理模式测试类
     */
    public class ProxyTest {
        /**
         * 测试静态代理
         */
        @Test
        public void testStaticProxy(){
            UserService userService = new UserServiceImpl();
            StaticProxy staticProxy = new StaticProxy(userService);
            staticProxy.addUser("xupeng.zhang");
        }
    }
    

    运行上述代码输出结果:
    begin to execute static proxy to add user......
    add user:xupeng.zhang success
    execute static proxy to add user end. cost 0 ms**

    采用静态代理,我们需要在代理类StaticProxy中传入UserService的引用;
    同时,代理类需要同时实现UserService的接口;只是简单的将请求转发给UserServiceImpl对象;

    /**
     * 静态代理类
     */
    public class StaticProxy implements UserService{
        private UserService userService;
    
        public StaticProxy(UserService userService) {
            this.userService = userService;
        }
    
        @Override
        public void addUser(String user) {
            //统计耗时
            Long executeTime = System.currentTimeMillis();
            System.out.println("begin to execute static proxy to add user......");
            userService.addUser(user);
            System.out.println(String.format("execute static proxy to add user end. cost %s ms", System.currentTimeMillis() - executeTime));
        }
    }
    

    以上便是静态代理类的实现方式,比较简单;但是如果UserService新增一个接口,那么从UserService到Proxy,都要重新实现一下,而且统计耗时的代码逻辑每个方法都要写一份。
    有没有什么办法可以减少这种重复性的工作呢?当然有,那就是接下来要讲的动态代理;

    JDK动态代理

    同样需要实现 UserService 接口,需要持有 UserService的引用;
    所不同的是,需要JDK帮我们动态生成代理对象;

    具体做法如下:

    1. 实现InvocationHandler接口,代理类对象的执行最后会转发到invoke方法;
    2. 通过JDK的动态代理获取UserService的代理对象;
    /**
     * 实际执行处理类
     */
    public class RealInvocationHandler implements InvocationHandler{
        private Object target;
    
        public RealInvocationHandler(Object target) {
            this.target = target;
        }
    
        /**
         * 最终代理对象会将请求转发到invoke方法执行
         * @param proxy
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Long executeTime = System.currentTimeMillis();
            System.out.println("begin to execute method in dynamic proxy...");
            Object result = method.invoke(target, args);
            System.out.println(String.format("execute method by dynamic proxy end, cost %s ms", System.currentTimeMillis() - executeTime));
            return result;
        }
    
        /**
         * 基于反射实现动态代理对象生成
         * @param <T>
         * @return
         */
        public <T> T getProxy(){
            return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);
        }
    }
    

    测试使用JDK动态代理

    /**
     * 代理模式测试类
     */
    public class ProxyTest {
        /**
         * 测试JDK动态代理
         */
        @Test
        public void testJDKDynamicProxy(){
            UserService userService = new UserServiceImpl();
            RealInvocationHandler realInvocationHandler = new RealInvocationHandler(userService);
            UserService userProxy = realInvocationHandler.getProxy();
            userProxy.addUser("xupeng.zhang");
        }
    }
    

    运行上述代码输出结果:
    begin to execute method in dynamic proxy...
    add user:xupeng.zhang success
    execute method by dynamic proxy end, cost 0 ms
    Process finished with exit code 0**

    上述代码中,我们并没有手写过代理类,JDK基于反射动态帮我们生成了代理类:

    /**
         * 基于反射实现动态代理对象生成
         * @param <T>
         * @return
         */
        public <T> T getProxy(){
            return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);//this即 RealInvocationHandler对象
        }
    

    如果进去newProxyInstance看源码的话,无非就以下三句关键代码:

    
     1. Class<?> cl = getProxyClass0(loader, intfs); //获取代理类对象
     2. final Constructor<?> cons = cl.getConstructor(constructorParams);//获取代理类的构造方法
     3. return cons.newInstance(new Object[]{h});//构造方法生成代理对象并返回
    

    其中:
    loader,指定代理对象的类加载器;
    intfs 即 interfaces,代理对象需要实现的接口,可以同时指定多个接口;
    h 即 RealInvocationHandler对象,方法调用的实际处理者,代理对象的方法调用都会转发到invoke方法。

    继续查看代理类的相关信息,你会发现JDK的动态代理居然如此奇妙:

    /**
     * 代理模式测试类
     */
    public class ProxyTest {
        @Test
        public void testGetJDKProxyClassInfo(){
            UserService userService = new UserServiceImpl();
            RealInvocationHandler realInvocationHandler = new RealInvocationHandler(userService);
            UserService userProxy = realInvocationHandler.getProxy();
            System.out.println(userProxy.getClass().getName());
            System.out.println(userProxy.getClass().getSuperclass());
            System.out.println(userProxy.getClass().getInterfaces()[0].getName());
            ProxyGeneratorUtils.saveProxyClass("D:/JdkProxy.class", userProxy.getClass());
        }
    }
    

    运行上述代码输出结果:
    com.sun.proxy.$Proxy2
    class java.lang.reflect.Proxy
    com.orig.design.proxy.UserService**

    代理类的类型是com.sun.proxy.$Proxy2;且继承自Proxy.java
    此外还实现了UserService接口;
    而我们将UserSercieProxy.class的字节码文件反编译会发现以下关键代码:

    public final void addUser(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);
         }
     }
    

    最终addUser会将请求转发给 RealInvocationHandler的 invoke方法执行;invoke方法调用method.invoke执行服务类对应的方法;

    CGLIB动态代理

    JDK生成的代理类已经继承了Proxy类,Java不支持多重继承,因此无法实现继承式的动态代理;这个时候就可以采用Cglib动态代理来实现;
    原理上跟JDK动态代理其实大同小异:
    cglib会动态生成代理类,它只关注自己的父类,通过继承获得父类非final的接口;之后将请求转发给MethodInterceptor的intercept方法去做实际处理;

    具体做法如下:

    1. 实现MethodInterceptor接口,intercept方法用于后续实际请求处理;
    2. 通过Cglib的Enhancer对象构造代理对象,需要告知enhance继承的父类;

    具体实现代码如下:

    /**
     * cglib动态代理
     * 和JDK动态代理不同
     * 是基于继承实现
     */
    public class RealInterceptor implements MethodInterceptor{
        private Enhancer enhancer = new Enhancer();
    
        /**
         * 具体执行的回调方法
         * @param o
         * @param method
         * @param args
         * @param methodProxy
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Long currentTime = System.currentTimeMillis();
            System.out.println("begin to execute cglib proxy to add user");
            System.out.println(String.format("method: %s", method.getName()));
            Object result = methodProxy.invokeSuper(o, args);//注意这里是invokeSuper
            System.out.println(String.format("execute cglib proxy end, cost: %s ms", System.currentTimeMillis()-currentTime));
            return result;
        }
    
        /**
         * 设置代理的父类
         * 设置回调方法
         * @param tClass
         * @param <T>
         * @return
         */
        public <T> T newProxyInstance(Class<T> tClass){
            enhancer.setSuperclass(tClass);
            enhancer.setCallback(this);
            return (T) enhancer.create();
        }
    }
    

    运行上述代码输出结果:
    begin to execute cglib proxy to add user
    method: addUser
    add user:xupeng.zhang success
    execute cglib proxy end, cost: 22 ms**

    通过上述结果可以看出,Cglib性能上相对于JDK动态代理稍差;同样的代理的实现,Cglib的实现比JDK动态代理的实现要耗时;

    同样的,我们针对代理类的相关信息进行挖掘:

    /**
     * 代理模式测试类
     */
    public class ProxyTest {
    	/**
         * 获取cglib代理类信息
         */
        @Test
        public void testGetCglibProxyClassInfo() throws Exception {
            RealInterceptor realInterceptor = new RealInterceptor();
            UserRoleService userRoleProxy = realInterceptor.newProxyInstance(UserRoleService.class);
            System.out.println(userRoleProxy.getClass().getName());
            System.out.println(userRoleProxy.getClass().getSuperclass());
            System.out.println(userRoleProxy.getClass().getInterfaces()[0].getName());
            ProxyGeneratorUtils.saveCglibProxyClass("D:/CglibProxy.class",realInterceptor.getEnhancer());
        }
    }
    

    运行上述代码输出结果:
    com.orig.design.proxy.UserServiceImpl$$EnhancerByCGLIB$$591ab16e
    class com.orig.design.proxy.UserServiceImpl
    net.sf.cglib.proxy.Factory**

    Cglib生成的代理类关键代码如下:

    public class UserRoleService$$EnhancerByCGLIB$$8ba328ed extends UserRoleService implements Factory {
        .......省略部分代码
        public final void addUserRole(String var1, String var2) {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (this.CGLIB$CALLBACK_0 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }
    
            if (var10000 != null) {
                var10000.intercept(this, CGLIB$addUserRole$0$Method, new Object[]{var1, var2}, CGLIB$addUserRole$0$Proxy);
            } else {
                super.addUserRole(var1, var2);
            }
        }
    }
    

    Cglib动态代理类直接继承了UserRoleService,并重写了addUserRole方法;
    此外,对原有的方法做了增强处理,如果Enhancer有设置设置回调方法,则会执行对应的回调方法interceptor;

    /**
         * 设置代理的父类
         * 设置回调方法
         * @param tClass
         * @param <T>
         * @return
         */
        public <T> T newProxyInstance(Class<T> tClass){
            enhancer.setSuperclass(tClass);//设置继承的父类
            enhancer.setCallback(this);//设置回调函数,入参是Callback类型
            return (T) enhancer.create();
        }
    
    /**回调方法**/
    public interface MethodInterceptor extends Callback {
        Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
    }
    

    小结

    1. 动态代理原理:(1)动态生成字节码;(2)通过反射调用真实实现类方法
    2. JDK原生动态代理基于接口实现;Cglib动态代理基于继承方式实现;
    3. 动态代理相对静态代理的好处在于灵活,强大,不需要对每个接口进行代理逻辑的开发;
    4. 当然,增加了代理,无形中会增加调用链,意味着降低处理速度;实现代理也需要额外的开发工作,增加了实现成本。

    本文相关的代码Demo已经上传到github上:github


  • 相关阅读:
    IWorkspaceFactory pWorkspaceFactory = new ShapefileWorkspaceFactoryClass(); 时,报COMException
    Asp.net MVC 发布到IIS6
    String.Net “System.TypeInitializationException”类型的未经处理的异常在 Spring.NetDemo.exe 中发生
    C#通过外部别名,解决DLL冲突问题
    c# DPI SCale
    c# 技巧
    正则笔记
    php & c# DES
    WPF页面切换
    C# 委托与事件
  • 原文地址:https://www.cnblogs.com/xupengzhang/p/9207115.html
Copyright © 2011-2022 走看看