代理模式(Proxy Pattern)

代理模式是平时经常会遇到的设计模式之一,比如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

其大概图示如下所示:
image.png

可以看到是很容易理解的,对应抽象化的UML类图如下:
image.png
这其实就是静态代码的现实,可以看到静态代理简单易懂,但是又限制的比较死,比如如果要在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的核心功能之一。灵活的运用代理模式可以让我们更加游刃有余的完成的复杂功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值