项目开发中,我们有时通常不能直接访问一个对象,需要一个中间层来代理完成。例如租房往往不会直接联系房东,而是通过中介。中介就是充当代理的角色,直接跟用户交谈。这是生活中的例子。在软件开发中,代理模式也被广泛使用。本文将详细介绍代理模式的实现以及应用场景。
代理模式
代理模式(Proxy):当一个对象被禁止直接使用另外一个对象时,往往需要使用一个代理对象以实现对该对象的间接使用,代理对象作为访问对象和目标对象之间的中介,这就是代理模式。在软件开发中,代理模式可以用于隐藏复杂任务,例如网络通信之间的复杂协议就可以使用代理进行封装。代理模式也可以用于某些对安全性要求较高的场景。例如一个不希望用户关心内部细节的防火墙系统。代理模式分为静态代理和动态代理,下文我们将详细介绍这两种代理方式。其实在实际项目的开发中,直接使用静态代理的场景并不多见,但掌握代理模式,可以让你能够轻松看懂一些模块的源码。
我们从静态代理开始介绍。
静态代理(基础)
静态代理,即开发者手动编写的代理。开发者自己编写代理类,并编译成.class文件。在程序执行前,这些文件就已经被写好并编译完成了。这样的代理称为静态代理。
静态代理的应用场景
- 文件上传:通过静态代理实现大文件的上传以及访问。
- 隐藏接口:有些接口不希望直接暴露到外部容器,需要使用静态代理实现中间件来隐藏接口。
代理模式的结构
- 抽象主题(Subject):通过接口或抽象类提供抽象的业务方法。
- 真实主题(Real Subject):实现抽象主题的具体方法,是代理类最终调用的对象。
- 代理(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);
}
}
最后可以看到本例的输出结果:
这样我们就实现了一个简单的静态代理。
接下来,我们来讲讲静态代理的优缺点。
静态代理的优缺点
优点
- 能够隐藏实例的真实调用。
- 可拓展性高,在需要加入预处理和后处理代码时,无需修改真实主题的具体代码。
缺点
- 代理类和委托类(抽象主题)实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象,需要编写多个代理类。(这个问题可通过动态代理进行解决)
动态代理(进阶)
讲完了静态代理,我们来看看什么是动态代理。
动态代理:静态代理中的每个代理类只能为一个接口服务,这样,在复杂项目中必然会产生许多的代理类,又会有许多重复的代码。与静态代理不同,动态代理无需开发人员手动编写代理类。动态代理在编译时并没有代理类存在,相反动态代理是在运行时,通过反射机制实现,并在运行期间提供给用户使用。在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()方法。
最终运行结果如下:
这就是一个简单的动态代理使用例子。使用动态代理,我们能够为任意数量的真实主题生成代理,而无需重复定义代理类。
总结
本文列举了两个具体例子,分别是短信验证码的发送和方法调用的计数器,详细讲解了静态代理和动态代理的使用方法以及应用场景。
完整的源代码可参见:Github