Java设计模式再相识 (六)——代理模式

11 篇文章 0 订阅
11 篇文章 0 订阅

项目开发中,我们有时通常不能直接访问一个对象,需要一个中间层来代理完成。例如租房往往不会直接联系房东,而是通过中介。中介就是充当代理的角色,直接跟用户交谈。这是生活中的例子。在软件开发中,代理模式也被广泛使用。本文将详细介绍代理模式的实现以及应用场景。

代理模式

代理模式(Proxy):当一个对象被禁止直接使用另外一个对象时,往往需要使用一个代理对象以实现对该对象的间接使用,代理对象作为访问对象和目标对象之间的中介,这就是代理模式。在软件开发中,代理模式可以用于隐藏复杂任务,例如网络通信之间的复杂协议就可以使用代理进行封装。代理模式也可以用于某些对安全性要求较高的场景。例如一个不希望用户关心内部细节的防火墙系统。代理模式分为静态代理和动态代理,下文我们将详细介绍这两种代理方式。其实在实际项目的开发中,直接使用静态代理的场景并不多见,但掌握代理模式,可以让你能够轻松看懂一些模块的源码。

我们从静态代理开始介绍。

静态代理(基础)

静态代理,即开发者手动编写的代理。开发者自己编写代理类,并编译成.class文件。在程序执行前,这些文件就已经被写好并编译完成了。这样的代理称为静态代理。

静态代理的应用场景

  • 文件上传:通过静态代理实现大文件的上传以及访问。
  • 隐藏接口:有些接口不希望直接暴露到外部容器,需要使用静态代理实现中间件来隐藏接口。

代理模式的结构

  1. 抽象主题(Subject):通过接口或抽象类提供抽象的业务方法。
  2. 真实主题(Real Subject):实现抽象主题的具体方法,是代理类最终调用的对象。
  3. 代理(Proxy):也叫做委托类,它负责对真实角色进行调用,把所有抽象主题定义的方法委托给真实主题来实现,并且在真实主题处理代码逻辑前后做相关的预处理或后处理。

静态代理示例

在本节的示例中,我们来模拟使用代理模式实现一个发送短信验证码接口的程序。

首先我们实现抽象主题:

ISmsService.java

package com.yeliheng.proxy;

/**
 * 抽象主题
 */
public interface ISmsService {

    void send(String message); // 发送短信验证码

}

抽象主题中提供了send()方法来发送验证码。

接着,我们实现真实主题。

SmsService.java

package com.yeliheng.proxy;

/**
 * 真实主题
 */
public class SmsService implements ISmsService{

    @Override
    public void send(String message) {
        System.out.println("发送短信成功,内容为:" + message);
    }
}

真实主题中实现了send()方法,输出我们需要的内容。

最后来实现代理类。

SmsProxy.java


package com.yeliheng.proxy;

/**
 * 代理类
 */
public class SmsProxy implements ISmsService{
    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public void send(String message) {
        //在这里执行相关的预处理操作...
        //-------------

        //发送验证码
        smsService.send(message);

        //后处理操作...
        //--------------
    }
}

我们可以注意到,代理类实现了ISmsService抽象主题而并非直接实现真实主题。然后我们通过构造函数进行注入,获取到真实主题的实例。并重写抽象主题中的send()方法,在send()方法中调用具体主题的send()方法。这样我们就实现了一个代理。使用者不用直接访问具体的主题,而是通过代理来进行访问操作。这样做,我们就可以隐藏真实主题的具体逻辑,并且在代理中可以实现相关的预处理(例如发送前先弹出图形验证码,图形验证码正确才发送短信验证码)和后处理(成功发送后,记录发送日志和手机号信息,防止接口滥用)操作,而无需去修改真实主题的代码,这样可以提升系统的可拓展性。

我们在main函数中进行模拟调用:

Main.java

package com.yeliheng.proxy;

public class Main {
    public static void main(String[] args) {

        //生成验证码
        int code = 123456;
        //使用代理发送短信验证码
        SmsService smsService = new SmsService();
        SmsProxy proxy = new SmsProxy(smsService);
        proxy.send("您的短信验证码是" + code);
    }
}

最后可以看到本例的输出结果:

output.png

这样我们就实现了一个简单的静态代理。

接下来,我们来讲讲静态代理的优缺点。

静态代理的优缺点

优点

  • 能够隐藏实例的真实调用。
  • 可拓展性高,在需要加入预处理和后处理代码时,无需修改真实主题的具体代码。

缺点

  • 代理类和委托类(抽象主题)实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。
  • 代理对象只服务于一种类型的对象,如果要服务多类型的对象,需要编写多个代理类。(这个问题可通过动态代理进行解决)

动态代理(进阶)

讲完了静态代理,我们来看看什么是动态代理。

动态代理:静态代理中的每个代理类只能为一个接口服务,这样,在复杂项目中必然会产生许多的代理类,又会有许多重复的代码。与静态代理不同,动态代理无需开发人员手动编写代理类。动态代理在编译时并没有代理类存在,相反动态代理是在运行时,通过反射机制实现,并在运行期间提供给用户使用。在Java中实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy类的支持。

动态代理的应用场景

动态代理的应用就广泛了,以下举几个常见场景。

  • Spring AOP,面向切面编程:通过动态代理将切点动态地织入到源码中。

注:可以参考我之前写的Spring AOP实现日志记录的文章

  • Mybatis Mapper
  • 接口级别的权限控制

动态代理示例

下面,我们通过动态代理实现方法调用次数的统计。

首先,我们定义一个测试类接口,往里面定义一个测试方法。

ITest.java

package com.yeliheng.proxy.dynamic;

/**
 * 测试类接口
 */
public interface ITest {
    void test();
}

接着我们来实现ITest接口。

在里面写一个test()方法,这个方法输出一串信息。

Test.java


package com.yeliheng.proxy.dynamic;

public class Test implements ITest{

    @Override
    public void test() {
        System.out.println("测试方法被执行!");
    }
}

接着,我们的重点来了。我们使用动态代理模式,来统计test()方法被用户调用的次数。

开始写具体的处理逻辑。

MethodInvocationCountHandler.java


package com.yeliheng.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 统计调用方法的次数
 */
public class MethodInvocationCountHandler implements InvocationHandler {

    private int count = 0;

    private final Object implementation;

    public MethodInvocationCountHandler(final Object implementation) {
        this.implementation = implementation;
    }

    public int getCount( ) {
        return count;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        try {
            this.count++;
            System.out.println("测试方法被调用: " + this.count + "次");
            return method.invoke(implementation, args);
        } catch (final InvocationTargetException e) {
            throw e.getTargetException( );
        }
    }
}

在这个处理类中,我们实现了java.lang.reflect中的InvocationHandler,重写了invoke方法。并且,与静态代理相同,我们采用构造函数进行注入。invoke方法中,我们通过反射来获取对测试方法的调用。这样,我们就可以在main函数中使用我们写好的计数器了。

Main.java


package com.yeliheng.proxy.dynamic;

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        ITest test = new Test();
        MethodInvocationCountHandler handler = new MethodInvocationCountHandler(test);
        ITest proxy = (ITest) Proxy.newProxyInstance(test.getClass().getClassLoader(), test.getClass().getInterfaces(),handler);
        proxy.test();
        proxy.test();
    }
}

调用方法与静态代理模式类似,只不过在动态代理中,我们没有显式声明Proxy代理类,而是通过反射的形式进行代理。在获取代理时,使用Proxy类提供的newProxyInstance来获取真实主题对象的引用。然后通过代理调用test()方法。

最终运行结果如下:

output.png

这就是一个简单的动态代理使用例子。使用动态代理,我们能够为任意数量的真实主题生成代理,而无需重复定义代理类。

总结

本文列举了两个具体例子,分别是短信验证码的发送和方法调用的计数器,详细讲解了静态代理和动态代理的使用方法以及应用场景。

完整的源代码可参见:Github

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值