开发者社区 > 博文 > 一次性说透静态代理&动态代理
分享
  • 打开微信扫码分享

  • 点击前往QQ分享

  • 点击前往微博分享

  • 点击复制链接

一次性说透静态代理&动态代理

  • scy251147
  • 2023-07-12
  • IP归属:北京
  • 8480浏览

    在java编程中,代理模式是我们常见的东西,说道代理,从字面意义理解,就是代为执行,一个类代替另一个类,那么代理有什么好处呢?

    代理的好处

    1. 代理可以屏蔽复杂的业务逻辑

    2. 代理可以对代理对象运行前后做前置后置处理,而不改变代理对象的行为

    3. 代理模式具有安全性。诸如可以为client端提供代理打桩,从而安全的调用到server端上。

    静态代理

    顾名思义,就是对指定的类或者对象进行代理操作,诸如:

    public class Customer {
      private String firstName;
      private String lastName;
    
      public Customer(final String firstName, final String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
      }
    
      public void printFullName( ) {
        System.out.println(this.firstName + " " +this.lastName);
      }
    }
    
    public class CustomerProxy {
      private final Customer customer;
      private int invocationCount = 0;
    
      public CustomerProxy(final Customer customer) {
        this.customer = customer;
      }
    
      public void printFullName( ) {
        invocationCount++;
        this.customer.printFullName( );
      }
      
      public int getInvocationCount() {
        return invocationCount;
      }
    }

    如上代码中,CustomerProxy对Customer类进行了代理,由于是指定的,所以这里就是静态代理了。从这里可以看出,代理对象内部耦合了原对象并进行了调用,对原对象无任何改动和污染。

    不过这里的代理是通过直接耦合的方式来进行的,如果直接使用到业务中,会使得代码整体的可维护性较差,如果有多个代理对象需要代理的时候,则需要破开原来的代码来进行,按照OO设计五大原则中的对扩展开放,对修改关闭的话,这里我们利用简单工厂模式进行优化一下:

    public class CustomerClassFactory {
      public final static CustomerProxy getProxy( ) {
        Customer customer = new Customer("Shohra", "Afaque");
        return new CustomerProxy(customer);
      }
    }
    
    public class Demo {
      public static final void main(final String[] args) {
        CustomerProxy proxy = CustomerClassFactory.getProxy( );
        proxy.printFullName( );
        System.out.println(proxy.getInvocationCount());
      }
    }

    从上面代码看出,我们定义了一个代码工厂,此工厂可以为原对象创建各种各样的代理,如果用户需要注册多个代理,只需要在代理工厂中进行适配即可,大大的解藕了代理类和业务代码纠缠的问题。

    从如上代码中,可以看到,静态代理的一些问题,总体说来如下:

    1. 静态代理需要指定代理对象,不灵活。

    2. 静态代理很多对象的时候,将会使代理类爆炸增长,不利于维护。

    由此,我们来看看动态代理。

    动态代理

    动态代理和静态代理不同的地方是,动态代理在JDK执行的时候,才会生成,而不是编译的时候生成。这里来说下其需要用到的特性:

    Invocation Handler

    说到动态代理,就不得不提到Invocation handler,它是一个java.lang.reflect包中的一个接口,实现动态代理,必须继承自此接口。在此接口中,已经定义好了调用原对象的方法,我们只需要按照规约进行编码即可,如下:

    public class MethodInvocationCountHandler implements InvocationHandler {
    
      private int invocationCount = 0;
      private final Object implementation;
    
      public MethodInvocationCountHandler(final Object implementation) {
        this.implementation = implementation;
      }
    
      public int getInvocationCount( ) {
        return invocationCount;
      }
    
      public Object invoke(Object proxy, Method meth, Object[] args)
                                         throws Throwable {
           try {
             this.invocationCount++;
             Object result = meth.invoke(implementation, args);
             return result;
           } catch (final InvocationTargetException ex) {
             throw ex.getTargetException( );
           }
       }
    }

    上面就是InvocationHandler的一个实现,当原对象中的方法被调用的时候,就会转到此invocation handler类中来执行(具体说来是转到invoke方法中来执行)。当执行完毕之后,会将得到的结果回传回去。这里需要注意的是,大部分情况下,我们都无需显式调用invoke方法来触发,此方法会被自动触发。

    接下来,和之前一样,我们利用简单工厂方法来触发动态代理的调用:

    public class CustomerClassFactory {
      public static final Customer getDynamicSomeClassProxy( ) {
        Customer customer = new Customer("Shohra","Afaque");
        InvocationHandler handler = new MethodInvocationCountHandler(customer);
        Class[] interfaces = new Class[] { Customer.class };
        ClassLoader loader = CustomerClassFactory.class.getClassLoader( );
        Customer proxy = (Customer)Proxy.newProxyInstance(loader, interfaces, handler);
        return proxy;
      }
    
    }
    
    public class Demo {
      public static final void main(final String[] args) {
        Customer proxy = CustomerClassFactory.getDynamicSomeClassProxy( );
        proxy.printFullName( );
    
        InvocationHandler handler = Proxy.getInvocationHandler(proxy);
        if (handler instanceof MethodCountingHandler) {
          System.out.println(((MethodCountingHandler)handler).getInvocationCount());
        }
      }
    }  

    上面就是动态代理的触发调用方式,从这里看出,我们不需要像静态代理那样,专门定义一个代理类。我们只需要继承自InvocationHandler接口并实现就行了。同时在触发的时候,我们需要将原对象传入,然后利用Proxy.newProxyInstance创建代理对象并返回就行了。

    由于在RPC中,client端都是通过这种动态代理的原理来进行的,所以你应该能明白为什么consumer虽然没被显式调用,但是可以触发方法调用了吧。

    参考: https://medium.com/@shohraafaque/proxies-in-java-static-dynamic-8ccc51d16346

    文章数
    2
    阅读量
    470

    作者其他文章

    01 ChatGPT用后感