- 定义
- 简单设计
- Java 的实现
3.1. 静态代理
3.2. JDK 动态代理
3.3. CGLIB 动态代理 - 应用实例
4.1. 权限控制
4.2. 方法执行时间统计
1. 定义
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
代表我们做一些业务,这些业务我们可能会因为资源的限制或者信息的匮乏等无法完成,也可以通过代理对用户进行保护,隔绝某些信息
现实世界的模型,比较常见的有
比如,我们使用代理服务器进行翻墙,访问外网。因为直接访问的话,会被 GFW 挡住了
比如,海淘、代购,进入海外,可以买到我们因为没有护照无法过去买到的产品
比如,一些商品的代理商,也是帮忙购买商品,但代理商有可能已经囤好货,直接发货过来
代理和我们的行为一样,但做了我们因为资源和某些限制做不到的事情。同时,代理可以做一些业务的增强,比如添加缓存、做安全策略(防火墙)、做任务调度合理分配资源等等
代理可以有很多丰富的功能,所以有很多分类,比如
-
远程代理(Remote Proxy)
为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。
-
保护代理(Protect Proxy)
控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限
-
虚拟代理(Virtual Proxy)
如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
-
缓冲代理(Cache Proxy)
为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
-
智能引用代理(Smart Reference Proxy)
当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
通过代理模式,可以做 AOP 编程
2. 简单设计
抽象主题角色
声明真实主题和代理主题的公共接口
使用者针对抽象主题角色编程
代理主题角色
持有对真实主题角色的引用,可以代理真实主题角色执行某些任务;可以控制真实主题角色的访问;可以控制真实主题的创建和销毁
真实主题角色
实现了真实的业务操作。由代理主题角色来间接调用完成操作
代理模式-类图.png
3. Java 的实现
3.1. 静态代理
代理对象和被代理对象实现同样的接口,或者继承相同的父类
比如,有一个请求类
public interface Request {
void get();
}
public class SimpleRequest implements Request {
@override
void get() {
System.out.println("简单请求");
}
}
需要代理这个请求类做一些增强业务,可以创建这样的代理类
public class RequestProxy implements Request {
private SimpleRequest mRequest;
public RequestProxy(SimpleRequest request) {
mRequest = request;
}
@override
void get() {
// 前置增强处理
mRequest.get();
// 后置增强处理
}
}
静态代理的缺点是我们需要创建多个代理类,量大了不好维护
3.2. JDK 动态代理
使用 reflect 包,在内存中动态生成代理对象
不需要实现接口,但是需要指定要代理的接口类型
比如我们有一个接口 SayHello
public interface SayHello {
void hello();
}
还有一个实现类 SayHelloImpl
public class SayHelloImpl implements SayHello {
public void hello() {
System.out.println("Hello world!");
}
}
如何使用 reflect 包提供的工具,在不需要静态创建代理类的情况下,动态创建代理对象?
首先实现 InvocationHandler,用来处理我们要代理的一些细节
public class SayHelloInvocationHandler implements InvocationHandler {
k
private Object mTarget;
public SayHelloInvocationHandler(Object target) {
mTarget = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("hello".equals(method.getName())) {
System.out.println("开始");
Object result = method.invoke(mTarget, args);
System.out.println("结束");
return result;
}
return method.invoke(mTarget, args);
}
}
method.invoke 相当于我们调用了 mTarget 的方法
然后用 Proxy 创建代理对象
SayHello hello = new SayHelloImpl();
SayHelloInvocationHandler handler = new SayHelloInvocationHandler(hello);
SayHello helloProxy = (SayHello) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);
helloProxy.hello();
所以,我们已经创建好 SayHello 的代理对象 helloProxy 了
执行 helloProxy.hello() 会有这样的输出
开始
Hello world!
结束
小结:
- 用这种方式实现代理,不需要静态创建代理类
- 被代理的对象必须实现接口
3.3. CGLIB 动态代理
底层使用 ASM 对字节码进行修改,动态生成被代理类继承被代理的类,重写方法,织入通知
即使代理类没有实现任何接口也能够进行动态代理
但是因为基于继承,所以无法代理 final 类型的类
运行速度会比JDK 动态代理快
核心类有
-
Enhance
增强器,传入要代理的对象,以及被代理的时候触发的回调实现。会生产出代理对象
-
MethodInterceptor
方法拦截器,代理触发的回调的一种,可以用来拦截方法做业务
-
MethodProxy
Method 的代理类,用来调用原本的方法
简单的应用如下:
我们有这样一个类 HelloWorld
public class HelloWorld {
public void hello() {
System.out.println("Hello world!");
}
}
然后实现了MethodInterceptor ,对方法进行拦截,并做增强处理
public class HelloWorldInterceptor implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("开始");
Object result = methodProxy.invokeSuper(o, args);
System.out.println("结束");
return null;
}
}
最后通过 Enhancer 来创建代理类,生成代理对象
HelloWorldInterceptor interceptor = new HelloWorldInterceptor();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloWorld.class);
enhancer.setCallback(interceptor);
HelloWorld helloWorld = (HelloWorld) enhancer.create();
helloWorld.hello();
运行输出
开始
Hello world!
结束
不过 cglib 不能直接在 android 上使用,因为 android 平台的字节码和 java 平台有区别
4. 应用实例
4.1. 权限控制
保护代理的应用,可以给不同的用户提供不同级别的使用权限
已经有一个类有一个执行方法,希望在不改变源码的情况下扩展,对内部方法的执行加入权限控制,有权限的执行,没有权限的不执行
代理模式是很多权限控制系统的基础,比如服务端的 Spring 的权限控制就是基于代理模式
我们用 JDK 动态代理来实现一个
4.1.1. 需求场景
我们有三个业务操作,评论、举报、删除,已经实现了功能。现在需要对每个业务操作都加入权限控制,只有登录的用户才能进行操作
4.1.2. 思路
我们为每一个业务都创建一个管理类。使用 JDK 动态代理,所有管理都实现了统一的接口
public interface Manager {
void handle();
}
然后分别创建 CommentManager,ReportManager,DeleteManager,实现了该接口。比如 CommentManager
public class CommentManager implements Manager {
public void handle() {
System.out.println("进行评论");
}
}
使用代理模式,为管理类创建代理类,对 handle 方法进行权限控制
public class LoginProxy implements InvocationHandler {
private boolean mLogin;
private Object mTarget;
public Manager getProxy(Object obj, boolean login) {
mLogin = login;
mTarget = obj;
return (Manager) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"handle".equals(method.getName())) {
return method.invoke(mTarget, args);
}
if (mLogin) {
System.out.println("已经登录,开始执行");
Object result = method.invoke(mTarget, args);
System.out.println("执行结束\n");
return result;
} else {
System.out.println("未登录,不执行");
return null;
}
}
}
然后,想要为每一个操作新增登录权限的验证,只需要创建有对登录权限进行校验的代理即可。下面的例子是用户登录的情况,login = true
public static void main(String[] args) {
LoginProxy proxy = new LoginProxy();
// 评论
Manager commentManager = proxy.getProxy(new CommentManager(), true);
commentManager.handle();
// 举报
Manager reportManager = proxy.getProxy(new ReportManager(), true);
reportManager.handle();
// 删除
Manager deleteManager = proxy.getProxy(new DeleteManager(), true);
deleteManager.handle();
}
4.1.3. 变化
权限发生变化,需要登录,并且只有管理员才可以进行删除
如何在不修改几个管理类的情况下,再新增管理员权限检验代理类
public class AdminProxy implements InvocationHandler {
private Object mTarget;
private boolean mAdmin;
public Manager getProxy(Object obj, boolean admin) {
mTarget = obj;
mAdmin = admin;
return (Manager) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"handle".equals(method.getName())) {
return method.invoke(mTarget, args);
}
if (mAdmin) {
System.out.println("是管理员,开始执行");
return method.invoke(mTarget, args);
} else {
System.out.println("不是管理员,不执行");
return null;
}
}
}
然后把代理嵌套使用。下面的例子是用户已经登录,但无管理员权限
public static void main(String[] args) {
LoginProxy loginProxy = new LoginProxy();
AdminProxy adminProxy = new AdminProxy();
Manager deleteManager = loginProxy.getProxy(adminProxy.getProxy(new DeleteManager(), false), true);
deleteManager.handle();
}
可以得到以下输出:
已经登录,开始执行
不是管理员,不执行
执行结束
这样还可以继续添加不同的权限,不同的业务逻辑,需要更换或者修改权限,只需要重新组合一下他们的权限代理类
4.1.4. 小结
上面的例子很简单,仅仅是提供了一个思路,具体的场景要复杂得多
使用动态代理做权限控制系统,有这样的好处
- 符合开闭原则,没有修改 Manager 类就增加了权限控制
- 符合依赖反转,代理类没有在内部创建要代理的对象,由外部配置,然后传入
- 符合单一职责,Manager 只做相应的业务,代理对象只处理权限控制
- 比起静态代理,可以少创建很多的代理类
4.2. 方法执行时间统计
这是智能引用代理的应用,并不会改变被代理对象的行为,而是增加了一些额外的操作
可以使用代理模式,对方法的执行时间进行记录。复杂的话,可以做一套框架,集成记录、存储、上报等功能,还可以加入注解来进行过滤
我们这里做一个小案例,使用 cglib 的动态代理,监控方法执行时间并且打印出来
假设我们有一个类,里面有多个方法,都有耗时操作,比如
public class SimpleObject {
public void execute1() {
int time = new Random().nextInt(9);
try {
Thread.sleep(time * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void execute2() {
int time = new Random().nextInt(9);
try {
Thread.sleep(time * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void execute3() {
int time = new Random().nextInt(9);
try {
Thread.sleep(time * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这时候,就可以使用 cglib 动态代理,创建一个监控器,对被代理的对象的方法进行耗时统计
public class ExecuteTimeMonitor<T> implements MethodInterceptor {
@SuppressWarnings("unchecked")
public T getProxy(Class<T> cls) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cls);
enhancer.setCallback(this);
return (T) enhancer.create();
}
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = methodProxy.invokeSuper(o, args);
long passTime = System.currentTimeMillis() - startTime;
System.out.println(method.getName() + " pass " + passTime);
return result;
}
}
使用的时候,不使用原生的 SimpleObject, 而是由 ExecuteTimeMonitor 生成的代理类
public static void main(String[] args) {
SimpleObject object = new ExecuteTimeMonitor<SimpleObject>().getProxy(SimpleObject.class);
object.execute1();
object.execute2();
object.execute3();
}
这样,在没有修改 SimpleObject 实现的基础上,对内部的方法都进行了耗时的统计
execute1 pass 2010
execute2 pass 8001
execute3 pass 4001
Process finished with exit code 0














网友评论