代理模式是平时经常会遇到的设计模式之一,比如Spring里面的AOP,Mybatis的Mapper等。现实生活中也有很多这样的例子,比如有保险代理人和其他的各种代理人。代理人可以替代被代理人去做一些事情,且在做这些事情的前后可以做一些其他的事情,张三开了一家公司,但是遇到了民事纠纷,需要找一个专业的律师,于是找到了李四,此时李四就是张三的诉讼代理人,会帮助张三通过法律途径来解决问题,再比如现在出现的游戏代玩等各种现实生活中的场景。
我们就拿游戏代玩(代练号)来举例子。
静态代理
何为静态代理?我们来举一个简单的场景来看一下,小明是魔兽世界的一个玩家,平时工作比较忙,只能周末玩一下,但是开始的练级有点让他头疼,于是小明找到他的一个小伙伴小红,小红家里有矿,平时有很多时间来玩游戏,在几次撸串之后,小红终于答应帮忙小明代练号。此时,我们用代码来实现这个场景:
代码如下:
public interface Player {
void playGame();
}
public class XiaoMing implements Player{
@Override
public void playGame() {
System.out.println("小明的号玩游戏");
}
}
public class XiaoHong implements Player{
private Player player;
public XiaoHong(Player player) {
this.player = player;
}
@Override
public void playGame() {
System.out.println("小红代玩---begin");
player.playGame();
System.out.println("小红代玩---end");
}
}
// 测试类
public class PlayerTest {
public static void main(String[] args) {
Player xiaoMing = new XiaoMing();
Player xiaoHong = new XiaoHong(xiaoMing);
xiaoHong.playGame();
}
}
输出结果如下:
小红代玩---begin
小明的号玩游戏
小红代玩---end
其大概图示如下所示:
可以看到是很容易理解的,对应抽象化的UML类图如下:
这其实就是静态代码的现实,可以看到静态代理简单易懂,但是又限制的比较死,比如如果要在service的所有接口入口与出口记录日志,统计操作消耗的时间,那么就需要为每个service实现类写一个代理理,这样相对比较麻烦,且不方便后续的维护。
动态代理
动态代理可以在程序运行的时候通过一定的机制动态生成,功能强大,可以满足一些复杂的场景。
JDK动态代理
JDK的动态代理是jdk自带的机制。通过Invocationhandler与Proxy结合使用。上面的例子我们可以用JDK动态代理的方式来实现,代码如下:
public interface Player {
void playGame();
}
public interface PlayerCustomHandler {
void before();
void after();
}
public class XiaoMing implements Player {
@Override
public void playGame() {
System.out.println("小明的号玩游戏");
}
}
public class PlayerInvocationHandler implements InvocationHandler {
//目标对象
private Object target;
private PlayerCustomHandler playerCustomHandler;
public PlayerInvocationHandler(Object target, PlayerCustomHandler playerCustomHandler) {
this.target = target;
this.playerCustomHandler = playerCustomHandler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
playerCustomHandler.before();
Object result = method.invoke(target, args);
playerCustomHandler.after();
return result;
}
}
public class JdkProxyClient {
public static void main(String[] args) {
Player xiaoMing = new XiaoMing();
InvocationHandler invocationHandler = new PlayerInvocationHandler(xiaoMing, new PlayerCustomHandler() {
@Override
public void before() {
System.out.println("代玩开始");
}
@Override
public void after() {
System.out.println("代玩结束");
}
});
Player other = (Player) Proxy.newProxyInstance(xiaoMing.getClass().getClassLoader(), xiaoMing.getClass().getInterfaces(),
invocationHandler);
other.playGame();
}
}
执行代码输出如下结果:
代玩开始
小明的号玩游戏
代玩结束
JDK 动态代理最致命的问题是只能代理实现了接口的类。
cglib动态代理
cglibj是一个基于asm(字节码生成)实现代理的工具类库,它允许我们在运行时对字节码进行修改和动态生成,cglib是通过类继承的方式来实现动态代理。
还是上面的例子,我们再用cglib来实现一下,其代码如下:
public interface Player {
void playGame();
}
public interface PlayerCustomHandler {
void before();
void after();
}
public class XiaoMing implements Player {
@Override
public void playGame() {
System.out.println("小明的号玩游戏");
}
}
public class PlayerMethodInterceptor implements MethodInterceptor {
private PlayerCustomHandler playerCustomHandler;
public PlayerMethodInterceptor(PlayerCustomHandler playerCustomHandler) {
this.playerCustomHandler = playerCustomHandler;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
playerCustomHandler.before();
Object result = proxy.invokeSuper(obj, args);
playerCustomHandler.after();
return result;
}
}
public class CglibClient {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(XiaoMing.class);
enhancer.setCallback(new PlayerMethodInterceptor(new PlayerCustomHandler() {
@Override
public void before() {
System.out.println("代玩开始");
}
@Override
public void after() {
System.out.println("代玩结束");
}
}));
Player player = (Player) enhancer.create();
player.playGame();
}
}
执行代码输出如下结果:
代玩开始
小明的号玩游戏
代玩结束
Javassist动态代理
参考:Javassist 实现动态代理
Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
Javassist 生成动态代理可以使用两种方式,一种使用代理工厂创建,和普通的JDK动态代理和 CGLIB类似,另一种则可以使用 动态代码创建。我们接下来看一下使用Javassist的动态代理实现代码:
public interface Player {
void playGame();
}
public interface PlayerCustomHandler {
void before();
void after();
}
public class XiaoMing implements Player {
@Override
public void playGame() {
System.out.println("小明的号玩游戏");
}
}
public class JavassitClient {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(XiaoMing.class);
Class<ProxyObject> proxyClass = proxyFactory.createClass();
Player player = (Player) proxyClass.newInstance();
((ProxyObject) player).setHandler(new MethodHandler() {
XiaoMing test = new XiaoMing();
public Object invoke(Object self, Method thisMethod,
Method proceed, Object[] args) throws Throwable {
System.out.println("代玩开始");
Object result = thisMethod.invoke(test, args);
System.out.println("代玩结束");
return result;
}
});
player.playGame();
}
}
执行代码输出如下结果:
代玩开始
小明的号玩游戏
代玩结束
总结
基于字节码可以实现更强大的功能,这里我们只聚焦于代理模式。
可以看到Java的代理模式实现有多种方式,spring即支持JDK实现的动态代理,也支持cglib实现的动态代理,基于代理模式实现的AOP是spring的核心功能之一。灵活的运用代理模式可以让我们更加游刃有余的完成的复杂功能。