资讯专栏INFORMATION COLUMN

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

李文鹏 / 453人阅读

摘要:什么是代理模式什么是代理模式为其他对象提供一种代理以控制对这个对象的访问。另外一种方式是动态代理。代理模式应用场景代理模式应用场景业务系统的非功能性需求开发这是最常用的一个场景。

1、什么是代理模式

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

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

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

2、代理模式定义

①、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可乐
出处:http://www.cnblogs.com/ysocean/

资源:微信搜【IT可乐】关注我,回复 【电子书】有我特别筛选的免费电子书。
本文版权归作者所有,欢迎转载,但未经作者同意不能转载,否则保留追究法律责任的权利。
 

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/124128.html

相关文章

  • java设计模式动态代理

    摘要:场景描述病从口入这句成语告诉我们注意饮食健康,小六同学想吃苹果,在吃苹果之前需要清洗一下苹果和洗一下手,吃完苹果后,需要洗一下手保持个人卫生十分钟后。。。动态代理小六委托管家来代理洗食物和洗手,小六属于委托对象,管家属于代理对象。 前言 为了更好的理解代理模式,首先根据生活中实际场景进行模拟,让我们在生活中去体验设计思想的美妙。 场景描述 病从口入这句成语告诉我们注意饮食健康,小六同学...

    piglei 评论0 收藏0
  • php设计模式

    摘要:我们今天也来做一个万能遥控器设计模式适配器模式将一个类的接口转换成客户希望的另外一个接口。今天要介绍的仍然是创建型设计模式的一种建造者模式。设计模式的理论知识固然重要,但 计算机程序的思维逻辑 (54) - 剖析 Collections - 设计模式 上节我们提到,类 Collections 中大概有两类功能,第一类是对容器接口对象进行操作,第二类是返回一个容器接口对象,上节我们介绍了...

    Dionysus_go 评论0 收藏0
  • php设计模式

    摘要:我们今天也来做一个万能遥控器设计模式适配器模式将一个类的接口转换成客户希望的另外一个接口。今天要介绍的仍然是创建型设计模式的一种建造者模式。设计模式的理论知识固然重要,但 计算机程序的思维逻辑 (54) - 剖析 Collections - 设计模式 上节我们提到,类 Collections 中大概有两类功能,第一类是对容器接口对象进行操作,第二类是返回一个容器接口对象,上节我们介绍了...

    vspiders 评论0 收藏0
  • 当Kotlin邂逅设计模式代理模式(二)

    摘要:简述从这篇文章起,我们将继续邂逅设计模式系列篇中的第二篇代理模式。代理模式可以说很多初级中级开发者迷惑的设计模式。首先我们需要使用类图直观地表示出代理模式思想。所以基于代理模式很轻松就实现。简述: 从这篇文章起,我们将继续Kotlin邂逅设计模式系列篇中的第二篇代理模式。代理模式可以说很多初级中级开发者迷惑的设计模式。但是它确实应用很广,不用多说大家非常熟悉的Retrofit框架,内部使用了...

    番茄西红柿 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<