一、概念
代理模式(Proxy Pattern):它在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加新功能。
使用场景:代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。
二、实现
- 静态代理
借鉴下面参考文章VPN的例子,实现了静态代理:
1、定义网站接口
public interface Website {
void processRequest();
void saveHistory();
}
2、实现网站Baidu实例
public class Baidu implements Website{
@Override
public void processRequest() {
System.out.println("正在访问Baidu");
}
@Override
public void saveHistory() {
System.out.println("保存访问Baidu记录");
}
}
3、访问baidu网站
public class Client {
public static void main(String[] args) {
Baidu baidu = new Baidu();
baidu.processRequest();
baidu.saveHistory();
}
}
4、访问结果:
5、实现网站Youtube实例
public class Youtube implements Website{
@Override
public void processRequest() {
System.out.println("正在访问Youtube");
}
@Override
public void saveHistory() {
System.out.println("保存访问Youtube记录");
}
}
6、如果我们要访问国外的网站Youtube,Facebook等,直接访问是不行的,我们就需要加一个vpn,登录vpn后可正常访问。所以我们需要在访问Youtube这些网站前加上vpn登录校验功能,如果未登录先登录vpn在去访问。但是如果每个网站的每个方法前挨个加判断太麻烦了,所以我们写一个代理类,统一给这些国外的网站加上登录校验功能。
public class VPN implements Website {
private Website proxiedWebsite;
public VPN (Website wb){
this.proxiedWebsite = wb;
}
@Override
public void processRequest() {
loginVpn();
proxiedWebsite.processRequest();
}
@Override
public void saveHistory() {
loginVpn();
proxiedWebsite.saveHistory();
}
private void loginVpn() {
System.out.println("校验vpn登录状态,已登录");
}
}
总结:个人理解,vpn这里相当于一个网站代理,我们把要访问的网站给代理类,代理类会处理vpn登录等判断并帮我们访问我们需要的网站。vpn登录功能相对于之前的网站访问及网站历史记录等网站业务功能是相对独立的,所以直接加在原来代码中不合理,因此该功能抽出来用代理类去处理。
7、访问Youtube :
public class Client {
public static void main(String[] args) {
//Baidu baidu = new Baidu();
//baidu.processRequest();
//直接访问
//Youtube youtube = new Youtube();
//youtube.processRequest();
//youtube.saveHistory();
//代理访问
Website youtube = new VPN(new Youtube());
youtube.processRequest();
youtube.saveHistory();
}
}
8、运行结果:
总结:从上面的例子可以看出,VPN需要实现Website接口,代理类需要把实现类的方法都实现一遍。后面增加登录校验功能,就需要在每个实现方法中都加一遍。
- 动态代理
动态代理就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
代码实现:
1、动态代理实现,先实现接口InvocationHandler
,然后通过Proxy类的newProxyInstance()
方法创建这个代理。被代理类方法通过反射调用invoke
执行,我们在每次执行前加一个vpn
登录校验。
public class WebsiteHandler implements InvocationHandler {
private Website proxiedObject;
public WebsiteHandler(Website wb) {
this.proxiedObject = wb;
}
public Website getProxyInstance() {
return (Website) Proxy.newProxyInstance(this.getClass().getClassLoader()
, proxiedObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
loginVpn();
return method.invoke(proxiedObject, args);
}
private void loginVpn() {
System.out.println("校验vpn登录状态,已登录");
}
}
2、代理实现和调用
public class Client {
public static void main(String[] args) {
//Baidu baidu = new Baidu();
//baidu.processRequest();
Youtube youtube = new Youtube();
WebsiteHandler websiteHandler = new WebsiteHandler(youtube);
Website website = websiteHandler.getProxyInstance();
website.processRequest();
website.saveHistory();
}
}
3、运行结果
总结:这里我们没有去写一个代理类,而是交给Proxy去生成,然后在InvocationHandler
的invoke
方法去实际执行。这个代理类,是在运行阶段生成的,无需事前写好。从上面例子可以看到,如果加功能的话在反射调用的时候统一加上就行,无需像静态代理一样在每个实现方法中都加一遍。
参考文章:
设计模式之代理模式