1. 代理模式概述
- 概念
- 给某一个对象提供一个代理,并由代理对象控制对原对象的引用
- 代理模式分为静态代理和动态代理,二者没有本质区别,只是换了一种写法
- 使用动态代理,需要把一个类传入,然后根据它正在调用的方法名判断是否需要加以控制
2. 静态代理模式 Demo
新建网络请求接口
1
2
3
4
5public interface IHttp {
void request(String sendData);
void onSuccess(String receivedData);
}新建 Http 请求工具类
1
2
3
4
5
6
7
8
9
10
11public class HttpUtil implements IHttp {
@Override
public void request(String sendData) {
System.out.println("网络请求中...");
}
@Override
public void onSuccess(String receivedData) {
System.out.println("网络请求完成。");
}
}新建 Http 代理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class HttpProxy implements IHttp {
private final HttpUtil httpUtil;
public HttpProxy(HttpUtil httpUtil) {
this.httpUtil = httpUtil;
}
public void request(String sendData) {
httpUtil.request(sendData);
}
public void onSuccess(String receivedData) {
httpUtil.onSuccess(receivedData);
}
}在代理类中增加打印日志信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class HttpProxy implements IHttp {
private final HttpUtil httpUtil;
public HttpProxy(HttpUtil httpUtil) {
this.httpUtil = httpUtil;
}
@Override
public void request(String sendData) {
System.out.println("发送数据:" + sendData);
httpUtil.request(sendData);
}
@Override
public void onSuccess(String receivedData) {
System.out.println("收到数据:" + receivedData);
httpUtil.onSuccess(receivedData);
}
}客户端验证
1
2
3
4
5
6
7
8
9public class Client {
@Test
public void test() {
HttpUtil httpUtil = new HttpUtil();
HttpProxy proxy = new HttpProxy(httpUtil);
proxy.request("request data");
proxy.onSuccess("received result");
}
}运行程序,输出如下
1
2
3
4发送数据:request data
网络请求中...
收到数据:received result
网络请求完成。分析
- 除了打印日志,还可以用来做权限管理。看起来代理模式和装饰模式一样,但两者的目的不同
- 装饰模式是为了增强功能或添加功能;代理模式主要是为了加以控制
3. 动态代理模式 Demo
伪代码:实现起来难点就是怎么让 HttpUtil 调用任意方法时,都通过一个方法间接调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class HttpProxy {
private final HttpUtil httpUtil;
public HttpProxy(HttpUtil httpUtil) {
this.httpUtil = httpUtil;
}
// 假设调用 httpUtil 的任意方法时,都要通过这个方法间接调用, methodName 表示方法名,args 表示方法中传入的参数
public visit(String methodName, Object[] args) {
if (methodName.equals("request")) {
// 如果方法名是 request,打印日志,并调用 request 方法,args 的第一个值就是传入的参数
System.out.println("发送数据:" + args[0]);
httpUtil.request(args[0].toString());
} else if (methodName.equals("onSuccess")) {
// 如果方法名是 onSuccess,打印日志,并调用 onSuccess 方法,args 的第一个值就是传入的参数
System.out.println("收到数据:" + args[0]);
httpUtil.onSuccess(args[0].toString());
}
}
}实际的动态代理类:使用反射技术
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class HttpProxy implements InvocationHandler {
private HttpUtil httpUtil;
public IHttp getInstance(HttpUtil httpUtil) {
this.httpUtil = httpUtil;
return (IHttp) Proxy.newProxyInstance(httpUtil.getClass().getClassLoader(), httpUtil.getClass().getInterfaces(), this);
}
// 调用 httpUtil 的任意方法时,都要通过这个方法调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if (method.getName().equals("request")) {
// 如果方法名是 request,打印日志,并调用 request 方法
System.out.println("发送数据:" + args[0]);
result = method.invoke(httpUtil, args);
} else if (method.getName().equals("onSuccess")) {
// 如果方法名是 onSuccess,打印日志,并调用 onSuccess 方法
System.out.println("收到数据:" + args[0]);
result = method.invoke(httpUtil, args);
}
return result;
}
}getInstance()
方法中,Proxy.newProxyInstance()
方法是 Java 系统提供的方法,专门用于动态代理。其中传入的第一个参数是被代理类的 ClassLoader,第二个参数是被代理类的 Interfaces,这两个参数都是 Object 中的,每个类都有,这里就是固定写法。只要知道系统需要这两个参数才能让我们实现我们的目的:调用被代理类的任意方法时,都通过一个方法间接调用。现在我们给系统提供了这两个参数,系统就会在第三个参数中帮我们实现这个目的- 第三个参数是 InvocationHandler 接口,这个接口中只有一个方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
。现在我们调用被代理类 HttpUtil 的任意方法时,都会通过这个invoke()
方法调用了。invoke()
方法中,第一个参数暂时用不上,第二个参数 method 就是调用的方法,使用method.getName()
可以获取到方法名,第三个参数是调用 method 方法需要传入的参数。本例中无论request()
还是onSuccess()
都只有一个 String 类型的参数,对应到这里就是 args[0]。返回的 Object 是 method 方法的返回值,本例中都是无返回值的。这里的method.invoke()
就是反射调用方法
修改客户端验证
1
2
3
4
5
6
7
8
9public class Client {
@Test
public void test() {
HttpUtil httpUtil = new HttpUtil();
IHttp proxy = new HttpProxy().getInstance(httpUtil);
proxy.request("request data");
proxy.onSuccess("received result");
}
}运行程序,输出与之前一样
1
2
3
4发送数据:request data
网络请求中...
收到数据:received result
网络请求完成。
4. 静态代理和动态代理的区别
- 静态代理和动态代理没有本质区别,动态代理的好处是节省代码量
- 例如被代理类有 20 个方法,我们只需要控制其中的两个方法,就可以用动态代理通过方法名对被代理类进行动态控制;如果使用静态代理,就需要将另外 18 个方法也写出来,非常繁琐