• Java设计模式之(五)——代理模式


    1、什么是代理模式

    Provide a surrogate or placeholder for another object to control access to it.

    Proxy Pattern:为其他对象提供一种代理以控制对这个对象的访问。

    说人话:在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能,比如Spring AOP。

    2、代理模式定义

    image-20210907235345478

    ①、Subject

    抽象主题角色,可以是抽象类,可以是接口,是一个最普通的业务类定义,无特殊要求。

    ②、RealSubject

    真实主题角色,也叫被代理角色,是业务逻辑的具体执行者。

    ③、Proxy

    代理主题角色,也叫代理类,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并在真实主题角色处理前后做一些预处理或善后工作。

    通用代码如下:

    /**
     * 抽象主题类
     */
    public interface Subject {
        void doSomething();
    }
    
    /**
     * 真实主题角色
     */
    public class RealSubject implements Subject{
    
        @Override
        public void doSomething() {
            //TODO 具体执行的事
        }
    }
    
    /**
     * 代理主题角色
     */
    public class Proxy implements Subject{
        //要代理的具体实现类
        private Subject realSubject;
    
        public Proxy(Subject realSubject){
            this.realSubject = realSubject;
        }
        @Override
        public void doSomething() {
            this.before();
            realSubject.doSomething();
            this.after();
        }
    
        // 预处理
        private void before(){
            // TODO
        }
    
        // 善后处理
        private void after(){
            // TODO
        }
    }
    

    3、代理模式的两种实现

    比如用代理模式实现统计某个接口的耗时。

    3.1 静态代理

    ①、基于接口编程

    抽象主题类:

    public interface IUserController {
        // 登录
        String login(String username,String password);
        // 注册
        String register(String username,String password);
    }
    

    具体主题类:

    public class UserController implements IUserController{
        @Override
        public String login(String username, String password) {
            // TODO 登录逻辑
            return null;
        }
    
        @Override
        public String register(String username, String password) {
            // TODO 注册逻辑
            return null;
        }
    }
    

    代理主题类:

    public class UserControllerProxy implements IUserController{
        private IUserController userController;
    
        public UserControllerProxy(IUserController userController){
            this.userController = userController;
        }
        @Override
        public String login(String username, String password) {
            long startTime = System.currentTimeMillis();
            // 登录逻辑
            userController.login("username","password");
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            System.out.println("接口响应时间:"+responseTime);
            return null;
        }
    
        @Override
        public String register(String username, String password) {
            long startTime = System.currentTimeMillis();
            // 注册逻辑
            userController.register("username","password");
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            System.out.println("接口响应时间:"+responseTime);
            return null;
        }
    }
    

    测试:

    因为原始类 UserController 和代理类 UserControllerProxy 实现相同的接口,是基于接口而非实现编程,将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码

    public class StaticProxyTest {
        public static void main(String[] args) {
            IUserController userController = new UserControllerProxy(new UserController());
            userController.login("username","password");
            userController.register("username","password");
        }
    }
    

    在上面的代码中,代理类和具体主题类需要实现相同的接口,假如具体主题类没有实现接口,并且不是我们开发维护的(比如来自第三方接口),我们要统计这个第三方接口的耗时,那应该如何实现代理模式呢?

    ②、基于继承

    继承具体主题类,然后扩展其方法即可,直接看代码。

    public class UserControllerProxy extends UserController {
        @Override
        public String login(String username, String password) {
            long startTime = System.currentTimeMillis();
            // 登录逻辑
            super.login("username","password");
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            System.out.println("接口响应时间:"+responseTime);
            return null;
        }
    
        @Override
        public String register(String username, String password) {
            long startTime = System.currentTimeMillis();
            // 注册逻辑
            super.register("username","password");
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            System.out.println("接口响应时间:"+responseTime);
            return null;
        }
    }
    

    3.2 动态代理

    在上面的例子中,有两个问题:

    ①、我们需要在代理类中,将具体主题类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑,如果方法很多,重复代码也会很多。

    ②、如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。

    那该如何解决上面的问题呢?答案就是动态代理(Dynamic Proxy)。

    动态代理:不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

    JDK动态代理:

    public class DynamicProxyHandler implements InvocationHandler {
    
        private Object target;
        public DynamicProxyHandler(Object target){
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long startTime = System.currentTimeMillis();
            Object result = method.invoke(this.target, args);
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            System.out.println("接口响应时间:"+responseTime);
            return result;
        }
    }
    

    测试:

    public class DynamicProxyTest {
        public static void main(String[] args) {
            // 1、创建具体主题类
            IUserController userController = new UserController();
            // 2、创建 Handler
            DynamicProxyHandler proxyHandler = new DynamicProxyHandler(userController);
            // 3、动态产生代理类
            IUserController o = (IUserController)Proxy.newProxyInstance(userController.getClass().getClassLoader(),
                    userController.getClass().getInterfaces(),
                    proxyHandler);
            o.login("username","password");
            o.register("username","password");
        }
    }
    

    这是 JDK 动态代理,利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,代理对象是在程序运行时产生的,而不是编译期,要求是具体主题类必须实现接口。

    另外一种方式是 Cglib 动态代理。CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,也就是通过修改字节码生成子类来处理。

    Cglib 动态代理:

    public class UserController{
        public String login(String username, String password) {
            // TODO 登录逻辑
            System.out.println("登录");
            return null;
        }
    
        public String register(String username, String password) {
            // TODO 注册逻辑
            System.out.println("注册");
            return null;
        }
    }
    

    注意:真实主题类是没有实现接口的。

    public class CglibDynamicProxy implements MethodInterceptor {
        private Object target;
    
        public CglibDynamicProxy(Object target){
            this.target = target;
        }
    
        // 给目标创建代理对象
        public Object newProxyInstance(){
            // 1.工具类
            Enhancer enhancer = new Enhancer();
            // 2.设置父类
            enhancer.setSuperclass(target.getClass());
            // 3.设置回调函数
            enhancer.setCallback(this);
            // 4.创建子类(代理对象)
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            long startTime = System.currentTimeMillis();
            Object result = method.invoke(this.target, args);
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            System.out.println("接口响应时间:"+responseTime);
            return result;
        }
    }
    

    测试:

    public class CglibDynamicProxyTest {
        public static void main(String[] args) {
            UserController userController = new UserController();
            CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(userController);
            UserController o = (UserController)cglibDynamicProxy.newProxyInstance();
            o.login("username","password");
            o.register("username","password");
        }
    }
    

    4、代理模式优点

    ①、职责清晰

    真实的角色就是实现实际的业务逻辑, 不用关心其他非本职责的事务, 通过后期的代理完成一件事务, 附带的结果就是编程简洁清晰。

    ②、高扩展性

    具体主题角色是随时都会发生变化的, 只要它实现了接口, 甭管它如何变化,代理类完全都可以在不做任何修改的情况下使用。

    5、代理模式应用场景

    ①、业务系统的非功能性需求开发

    这是最常用的一个场景。比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。

    典型例子就是 SpringAOP。

    ②、RPC

    RPC(远程代理) 框架也可以看作一种代理模式,通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。

    作者:IT可乐

    资源:微信搜【IT可乐】关注我,回复 【电子书】有我特别筛选的免费电子书。
    本文版权归作者所有,欢迎转载,但未经作者同意不能转载,否则保留追究法律责任的权利。
  • 相关阅读:
    C++面向对象笔记:继承、派生
    教你制作伪静态
    安装android环境别忘了加个环境变量sdk_home
    javascript获取asp.net后台代码的方法
    一天学会PHP(转)
    access数据库里面用sql语句随机调用一条数据
    【转】 android sdk setup时出现:HTTPS SSL error , Server:10.159.192.62
    AutoCode下载及具体使用方法
    讲故事谈.NET委托:一个C#睡前的故事
    使用activeskin控件制作VB和易语言的皮肤的视频教程
  • 原文地址:https://www.cnblogs.com/ysocean/p/15586382.html
Copyright © 2020-2023  润新知