代理设计模式是我们日常开发中非常常见的设计模式。来看看下面的一段伪代码

  1. class A{
  2. void send(B b){
  3. b.doingSomeThing();
  4. }
  5. }

这是一个标准的对象调用,由A调用B。此时的调用示意图是这样的。
image.png
那么某一天我们发现,B中的doingSomeThing()方法实现的功能不满足我们的需求了,或者我们要往doingSomeThing()里面加入新的需求。而B这个类是我们无权修改,例如是某个jar提供的类。那么这个时候就要用到代理设计模式来生成B类的代理类,在不改变B类的功能的前提下拓展B类的功能代码。
image.png

而实现代理方式一般有两种:

  • 静态代理
  • 动态代理

静态代理

在写静态代理之前,我们需要思考几个问题。
而为了实现代码的灵活性,传统的代理必须得有一个公共的接口,例如参考上面的B,如果B是一个接口,那么整个代码就很灵活,代理对象也只需要实现这个接口就行了,这样就能被当做入参传入,并实现相应的功能。

接下来想想代理类里要有什么,或者代理类要是什么样的,才能实现对应功能?

  1. 代理类肯定实现了和被代理对象一样的接口,也就是业务接口。
  2. 代理对象肯定要持有被代理对象的引用,这样才能调用对应的方法。

我们静态代理代码可以尝试着这么写:

  • 业务接口

    1. public interface IMessageSender{
    2. /** 发送消息 */
    3. void sendMessage(String message);
    4. }
  • 被代理类

    1. public final class DefaultMessageSender implements IMessageSender{
    2. @Override
    3. public void sendMessage(String message){
    4. System.out.print("发送消息:" + message);
    5. }
    6. }
  • 客户端类

    1. public class Phone{
    2. public void sendMessage(String msg){
    3. IMessageSender messageSender = new DefaultMessageSender();
    4. messageSender.sendMessage(msg);
    5. }
    6. }

如果发送消息时,需要打开链接,发送完成以后需要关闭链接。那么代理类可以这么写

  • 代理类

    1. public Class MessageSenderProxy implements IMessageSender{
    2. private IMessageSender messageSender;
    3. public MessageSenderProxy(IMessageSender messageSender){
    4. this.messageSender = messageSender;
    5. }
    6. private void connect(){
    7. System.out.print("打开链接");
    8. }
    9. private void close(){
    10. System.out.print("关闭链接");
    11. }
    12. @Override
    13. public void sendMessage(String message){
    14. connect();
    15. messageSender.sendMessage(message);
    16. close();
    17. }
    18. }

此时客户端类可以这么调用

  1. public class Phone{
  2. public void sendMessage(String msg){
  3. IMessageSender messageSender = new MessageSenderProxy(new DefaultMessageSender());
  4. messageSender.sendMessage(msg);
  5. }
  6. }

image.png

这样,就实现了一个标准的静态代理。由MessageSenderProxy来增强了原先的DefaultMessageSender。

缺陷

虽然代理类增强了被代理类的功能,但是代理类也必须实现对应的接口,意味着如果某一天接口新增了一个方法,那么代理类也需要做对应的修改。这种就是代理类与接口的强耦合,显然是不灵活也不合理的。
如果能解除接口、代理对象的耦合,让代理对象更关注增强逻辑的编写,而不需要关心所代理的接口是最好的。
如果能有一个类似于工厂模式的类,其中有一个方法入参是接口、被代理的接口对象、将要增强的逻辑,然后返回一个代理类就好了。强大的JDK开发人员当然想到了这种需求,所以就出现了动态代理

动态代理

动态代理的方式有两种,一种是JDK自带的动态代理,以及CGLIB动态代理。这里只介绍基于JDK的Proxy来实现的动态代理。
Proxy类,在其位置在java.lang.reflect包中。通过这个类可以动态的生成代理类
动态代理生成的写法法:

  1. public class Test {
  2. @org.junit.Test
  3. public void test(){
  4. IMessageSender defaultSender = new DefaultMessageSender();
  5. Class<? extends IMessageSender> defaultSenderClass = defaultSender.getClass();
  6. //创建代理对象
  7. IMessageSender messageSenderProxy = (IMessageSender)Proxy.newProxyInstance(defaultSenderClass.getClassLoader(), defaultSenderClass.getInterfaces(), new MessageInvocationHandler(defaultSender));
  8. Phone phone = new Phone();
  9. phone.send(messageSenderProxy,"我来了");
  10. }
  11. /** 增强逻辑 */
  12. class MessageInvocationHandler implements InvocationHandler{
  13. /** 持有对象 */
  14. private Object object;
  15. public MessageInvocationHandler(Object object){
  16. this.object = object;
  17. }
  18. private void connect(){
  19. System.out.println("打开链接");
  20. }
  21. private void close(){
  22. System.out.println("关闭链接");
  23. }
  24. @Override
  25. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  26. connect();
  27. //反射调用
  28. Object invoke = method.invoke(object, args);
  29. close();
  30. return invoke;
  31. }
  32. }
  33. }

执行结果
image.png
和静态代理的结果是一样的。

Proxy.newProxyInstance()

方法结构

    /**
      * 生成代理类
      * @param loader 类加载器
      * @param interfaces 所需要实现的接口
      * @param h 增强逻辑
      * @throws
      * @return java.lang.Object
      * @author zhy
      * @date 2021/8/19 16:30
      */
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException {
        return null;
    }
  • 类加载器

有类加载器就说明,这个方法可以往JVM中写入字节码信息。就是通过此类加载器生成的代理类。

  • interfaces

所需要增强的接口,生成的代理类其实和静态代理里的写法一样,肯定是要实现接口的,这样才能实现增强具体的方法。

  • InvocationHandler

增强逻辑所在的接口,稍后会详细看一下接口的结构。

InvocationHandler

我称之为增强业务类的规范接口,接口中只有一个方法:

    interface InvocationHandler{
        /**
         * 调用代理方法,任何调用被代理类的方法都会走到此方法,由此方法进行调用被代理类的原始方法
         * @param proxy 被代理的对象,这个参数用的一般不多
         * @param method 要执行的接口方法名称
         * @param args 传递的参数
         * @throws Throwable 方法调用出现的错误,向外抛出
         * @return java.lang.Object 方法的返回值
         * @author zhy
         * @date 2021/8/20 15:59
         */
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable;
    }

可以看到invoke方法其实是一个基于反射实现的方法,我们通过Proxy生成了代理类,而客户端类通过代理类调用的所有的接口声明的方法都会走到这个invoke()方法,我们在invoke()方法中对调用逻辑进行增强,添加我们所需要的业务逻辑。
通过观察这个方法,还可以发现在这个方法中获取很多有用的信息,例如我们可以根据method对象获取方法名,对不同的方法进行代理,如果方法名相同,我们还可以通过args参数列表来判断我们所需要代理的方法。还可以拿到返回值,进行处理。总之这个方法能实现很多功能。

总结

静态代理现在已经不常用了。但是静态代理对我们帮助我们初步了解代理设计模式。因为知道了其缺陷,我们才能更好的理解动态代理。

动态代理之所以被称之为动态,是因为这种模式能动态的创建代理对象,不需要硬编码去显式的编写任何一个代理类,这样很好的解决了静态代理所带来的的耦合问题,让接口和增强逻辑实现了解耦,让增强类只关注于增强逻辑的实现,而其中的invoke()方法的入参,则可以帮助我们拿到调用的所有信息,进行代理逻辑的细化。

动态代理还有一个特点就是动态对象的创建,这个动态对象生成在内存中,是由JVM去动态创建的,主要依靠的就是Proxy来完成的,由newProxyInstance()方法来完成,该方法其中的入参有个类加载器ClassLoader,获取了类加载器,Proxy就可以往JVM中写入字节码信息。而Proxy要写入的就是代理类的字节码信息。至于其底层如何实现,我现在也不知道,因为真的很底层了,都是一些JVM的操作。

而动态代理从生成代理对象到调用结束的时序图是这样的:

未命名绘图.png