聊聊代理模式
介绍
代理模式(Proxy Pattern)为其他对象提供一种代理以控制对这个对象的访问。比如,在反问这个对象时,想要加一些操作但是又不想改动原来已有的对象。这个时候就访问代理对象,先通过代理对象,进行一些额外的操作之后再执行原来已有的对象。
举个,我们非常常见的例子,客户端请求服务端接口,想统计接口的执行时间。但是,又不想改变原有类,在里面添加代码。
这个时候,我们就可以使用代理模式来帮助我们完成这个需求。
首先看一个没有使用代理时,获取执行时间的示例:
public class UserController {
/**
* 依赖注入
*/
private UserService userService;
public UserVO login(UserVO userVO) {
long startTime = System.currentTimeMillis();
UserVO result = userService.login(userVO);
System.out.println("responseTime:" + (System.currentTimeMillis() - startTime));
return result;
}
public Boolean register(UserVO userVO) {
long startTime = System.currentTimeMillis();
boolean result = userService.register(userVO);
System.out.println("responseTime:" + (System.currentTimeMillis() - startTime));
return result;
}
}
这样的方式去统计执行时间,与业务代码的耦合太高了。这样的代码不应该出现在这里。
静态代理
所以为了解耦,我们先使用静态代理模式来实现这个需求。
现在,我们需要将现有的代码进行修改:
public interface IUserController {
Boolean register(UserVO vo);
UserVO login(UserVO vo);
}
public class UserController implements IUserController {
// 省略
@Override
public UserVO login(UserVO userVO) {
// 业务代码
}
@Override
public Boolean register(UserVO userVO) {
// 业务代码
}
}
// 代理类
public class ProxyUserController implements IUserController {
private final UserController userController;
public ProxyUserController(UserController userController) {
this.userController = userController;
}
@Override
public Boolean register(UserVO vo) {
long startTime = System.currentTimeMillis();
Boolean result = userController.register(vo);
System.out.println("ResponseTime:" + (System.currentTimeMillis() - startTime));
return result;
}
@Override
public UserVO login(UserVO vo) {
long startTime = System.currentTimeMillis();
UserVO result = userController.login(vo);
System.out.println("ResponseTime:" + (System.currentTimeMillis() - startTime));
return result;
}
}
// 使用
IUserController userController = new ProxyUserController(new UserController);
// 调用...
我们通过面向接口编程,将原始类对象替换成代理类对象。再将之前的的统计代码在实现一次即可。
不过,需要注意的是静态代理是基于接口来实现的,要是原先的类并没有定义接口,我们也没办法修改原先的类,去给它去定义一个相同的接口。而且,越来越多的类需要实现这个需求,那么也就要定义越来越多的接口,开发成本太大,而且重复性的代码也太多了。
动态代理
好在有动态代理(Dynamic Proxy),能帮我们解决这个问题,我们事先不必为每个原始类编写代理类,而是在运行时动态生成的。
少说多做,直接来试试吧:
public class ControllerProxy {
public Object createProxy(Object proxy) {
Class<?>[] interfaces = proxy.getClass().getInterfaces();
DynamicProxyHandler handler = new DynamicProxyHandler(proxy);
return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), interfaces, handler);
}
private static class DynamicProxyHandler implements InvocationHandler {
private final Object proxyObject;
public DynamicProxyHandler(Object proxyObject) {
this.proxyObject = proxyObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object invoke = method.invoke(proxyObject, args);
long endTime = System.currentTimeMillis();
System.out.println("ResponseTime:" + (endTime - startTime));
return invoke;
}
}
}
// 使用
ControllerProxy proxy = new ControllerProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());
userController.login(new UserVO(1, "1111"));
不过使用JDK的动态代理,原始类必须要有有接口,否则就会报错。
像常用的后端框架Spring,它的AOP就是基于动态代理来实现的,但被代理类(原始类)有接口时使用的是JDK的动态代理,没有接口时就使用Cglib,通过动态生成被代理类的子类,去重写它的方法以及增强。