4_SpringAOP实现机制

  • 代理模式

    (1) 代理模式下一共有4种角色

    1° Subject接口 —> 用户访问的超类型

    2° SubjectImpl类 —> Subject接口的实现类

    3° SubjectProxy类 —> SubjectImpl的代理类

    4° Client —> 访问Subject的用户,它不在乎Subject接口的具体实现,只要是Subject超类型即可

  • 静态代理

    (1) 过程就是:为每个需要代理的类的外面套上一层

    (2) 静态代理__存在的问题__:

    很多时候,代理类的功能是类似的(例如统计被代理方法的执行时间是共通的需求);但是,为了给这些需要代理的类套上一个壳子,如果使用了静态代理,意味着明明是相同的功能(统计运行时间),却要每个类都加上它们的代理类,要手动编写很多个代理类

    示例

    第一个要被代理的类是 Subject1.class

    此时要实现第一个代理Subject1类的代理类ServiceControlSubjectProxy1

      public class ServiceControlSubjectProxy1 implements Subject1 {
    
          private Subject1 subject;
    
          public ServiceControlSubjectProxy1(Subject1 subject) {
              this.subject = subject;
          }
    
          public String request() {
    
              if ( currentTime.isAfter(6:00) ) {
                  return null;
              }
    
              return this.subject.request();
          }
      }
    

    然后有第二个被代理的类 Subject2.class

    很不幸还要实现第二个代理Subject2类的代理类ServiceControlSubjectProxy2

      public class ServiceControlSubjectProxy2 implements Subject2 {
    
          private Subject2 subject;
    
          public ServiceControlSubjectProxy1(Subject2 subject) {
              this.subject = subject;
          }
    
          public void request() {
    
              if ( currentTime.isAfter(6:00) ) {
                  return null;
              }
    
              this.subject.request();
          }
      }
    

    明明ServiceControlSubjectProxy1和ServiceControlSubjectProxy2的功能是一样的,但是很不幸,静态代理就是要每个都实现一遍

  • Java原生动态代理

    (1) Java原生动态代理主要由一个Proxy类和一个InvocationHandler接口搞定,其中InvocationHandler就是我们要在代理中添加的逻辑(对于相同的逻辑,我们只需要实现一次InvocationHandler接口就可以用到所有需要代理的地方);而Proxy类提供的static方法newProxyInstance()是用来动态产生代理类的

    (2) InvocationHandler接口

    InvocationHandler接口只有一个方法invoke(Object proxy, Method method, Object[] args);

    这个方法会在调用代理实例的方法时被触发;proxy参数代表触发它的那个代理实例;method代表触发的那个方法,注意必须要是接口中的方法;args代表方法中的参数,如果没有的话就是null,基本类型(比如int)会变成包装类传进来

    invoke()方法的返回值是Object超类型:这意味着如果接口方法本该返回基本类型,而在这里返回了null,就会报NullPointerException异常;如果这里返回的类型和接口方法返回的类型无法cast,那么会报ClassCastException异常

      public interface InvocationHandler {
    
          /**
           * Processes a method invocation on a proxy instance and returns
           * the result.  This method will be invoked on an invocation handler
           * when a method is invoked on a proxy instance that it is
           * associated with.
           *
           * @param   proxy the proxy instance that the method was invoked on
           *
           * @param   method the {@code Method} instance corresponding to
           * the interface method invoked on the proxy instance.  The declaring
           * class of the {@code Method} object will be the interface that
           * the method was declared in, which may be a superinterface of the
           * proxy interface that the proxy class inherits the method through.
           *
           * @param   args an array of objects containing the values of the
           * arguments passed in the method invocation on the proxy instance,
           * or {@code null} if interface method takes no arguments.
           * Arguments of primitive types are wrapped in instances of the
           * appropriate primitive wrapper class, such as
           * {@code java.lang.Integer} or {@code java.lang.Boolean}.
           *
           * @return  the value to return from the method invocation on the
           * proxy instance.  If the declared return type of the interface
           * method is a primitive type, then the value returned by
           * this method must be an instance of the corresponding primitive
           * wrapper class; otherwise, it must be a type assignable to the
           * declared return type.  If the value returned by this method is
           * {@code null} and the interface method's return type is
           * primitive, then a {@code NullPointerException} will be
           * thrown by the method invocation on the proxy instance.  If the
           * value returned by this method is otherwise not compatible with
           * the interface method's declared return type as described above,
           * a {@code ClassCastException} will be thrown by the method
           * invocation on the proxy instance.
           *
           * @throws  Throwable the exception to throw from the method
           * invocation on the proxy instance.  The exception's type must be
           * assignable either to any of the exception types declared in the
           * {@code throws} clause of the interface method or to the
           * unchecked exception types {@code java.lang.RuntimeException}
           * or {@code java.lang.Error}.  If a checked exception is
           * thrown by this method that is not assignable to any of the
           * exception types declared in the {@code throws} clause of
           * the interface method, then an
           * {@link UndeclaredThrowableException} containing the
           * exception that was thrown by this method will be thrown by the
           * method invocation on the proxy instance.
           *
           * @see     UndeclaredThrowableException
           */
           public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
      }
    

    (3) Proxy类

    为了动态产生一个代理类,要用到的就是newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法;

    其中,loader参数代表代理类用的类加载器;interfaces代表代理类要实现的所有接口(注意,必须是接口,不能是类);h代表代理类实际添加的业务逻辑对应的InvocationHandler;

    newProxyInstance返回的是一个由loader类加载器标记的、实现了所有interfaces的实例

    注意这些interface的名字必须要能被找到(也就是说用class.forName()要找的到,因为要靠反射实现动态代理)

      /**
       * Returns a proxy instance for the specified interfaces
       * that dispatches method invocations to the specified invocation
       * handler.
       * <p>
       * <a id="restrictions">{@code IllegalArgumentException} will be thrown
       * if any of the following restrictions is violated:</a>
       * <ul>
       * <li>All of {@code Class} objects in the given {@code interfaces} array
       * must represent interfaces, not classes or primitive types.
       *
       * <li>No two elements in the {@code interfaces} array may
       * refer to identical {@code Class} objects.
       *
       * <li>All of the interface types must be visible by name through the
       * specified class loader. In other words, for class loader
       * {@code cl} and every interface {@code i}, the following
       * expression must be true:<p>
       * {@code Class.forName(i.getName(), false, cl) == i}
       *
       * <li>All of the types referenced by all
       * public method signatures of the specified interfaces
       * and those inherited by their superinterfaces
       * must be visible by name through the specified class loader.
       *
       * <li>All non-public interfaces must be in the same package
       * and module, defined by the specified class loader and
       * the module of the non-public interfaces can access all of
       * the interface types; otherwise, it would not be possible for
       * the proxy class to implement all of the interfaces,
       * regardless of what package it is defined in.
       *
       * <li>For any set of member methods of the specified interfaces
       * that have the same signature:
       * <ul>
       * <li>If the return type of any of the methods is a primitive
       * type or void, then all of the methods must have that same
       * return type.
       * <li>Otherwise, one of the methods must have a return type that
       * is assignable to all of the return types of the rest of the
       * methods.
       * </ul>
       *
       * <li>The resulting proxy class must not exceed any limits imposed
       * on classes by the virtual machine.  For example, the VM may limit
       * the number of interfaces that a class may implement to 65535; in
       * that case, the size of the {@code interfaces} array must not
       * exceed 65535.
       * </ul>
       *
       * <p>Note that the order of the specified proxy interfaces is
       * significant: two requests for a proxy class with the same combination
       * of interfaces but in a different order will result in two distinct
       * proxy classes.
       *
       * @param   loader the class loader to define the proxy class
       * @param   interfaces the list of interfaces for the proxy class
       *          to implement
       * @param   h the invocation handler to dispatch method invocations to
       * @return  a proxy instance with the specified invocation handler of a
       *          proxy class that is defined by the specified class loader
       *          and that implements the specified interfaces
       * @throws  IllegalArgumentException if any of the <a href="#restrictions">
       *          restrictions</a> on the parameters are violated
       * @throws  SecurityException if a security manager, <em>s</em>, is present
       *          and any of the following conditions is met:
       *          <ul>
       *          <li> the given {@code loader} is {@code null} and
       *               the caller's class loader is not {@code null} and the
       *               invocation of {@link SecurityManager#checkPermission
       *               s.checkPermission} with
       *               {@code RuntimePermission("getClassLoader")} permission
       *               denies access;</li>
       *          <li> for each proxy interface, {@code intf},
       *               the caller's class loader is not the same as or an
       *               ancestor of the class loader for {@code intf} and
       *               invocation of {@link SecurityManager#checkPackageAccess
       *               s.checkPackageAccess()} denies access to {@code intf};</li>
       *          <li> any of the given proxy interfaces is non-public and the
       *               caller class is not in the same {@linkplain Package runtime package}
       *               as the non-public interface and the invocation of
       *               {@link SecurityManager#checkPermission s.checkPermission} with
       *               {@code ReflectPermission("newProxyInPackage.{package name}")}
       *               permission denies access.</li>
       *          </ul>
       * @throws  NullPointerException if the {@code interfaces} array
       *          argument or any of its elements are {@code null}, or
       *          if the invocation handler, {@code h}, is
       *          {@code null}
       *
       * @see <a href="#membership">Package and Module Membership of Proxy Class</a>
       * @revised 9
       * @spec JPMS
       */
      @CallerSensitive
      public static Object newProxyInstance(ClassLoader loader,
                                            Class<?>[] interfaces,
                                            InvocationHandler h)
    

    (4) 示例

      public class Solution {
    
          public static void main(String[] args) throws InterruptedException{
    
              Person person = new MyPerson(1234, "cb");
    
              Person personProxy = (Person) Proxy.newProxyInstance(
                  person.getClass().getClassLoader(),
                  person.getClass().getInterfaces(),
                  new MyInvocationHandler(person));
    
              System.out.println(personProxy.getId());
              System.out.println(personProxy.getName());
              Thread.sleep(300);
    
              try {
                  personProxy.setId(5678);
              } catch (Exception e) {
                  e.printStackTrace();
              }
    
              Thread.sleep(300);
              try {
                  personProxy.setName("kw");
              } catch (Exception e) {
                  e.printStackTrace();
              }
    
              Thread.sleep(300);
              System.out.println(personProxy.getId());
              System.out.println(personProxy.getName());
          }
      }
    
      class MyInvocationHandler implements InvocationHandler {
    
          private Person person;
    
          public MyInvocationHandler(Person person) {
              this.person = person;
          }
    
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
              if (method.getName().startsWith("set")) {
                  throw new IllegalAccessException();
              } else {
                  return method.invoke(person, args);
              }
          }
      }
    
      interface Person {
    
          String getName();
    
          void setName(String name);
    
          void setId(int id);
    
          int getId();
      }
    
      class MyPerson implements Person {
    
          private int id;
          private String name;
    
          public MyPerson(int id, String name) {
              this.id = id;
              this.name = name;
          }
    
          public String getName() {
              return name;
          }
    
          public void setName(String name) {
              this.name = name;
          }
    
          public void setId(int id) {
              this.id = id;
          }
    
          public int getId() {
              return id;
          }
      }
    

    (5) 动态代理很遗憾的地方是,如果某个类没有实现任何interface,那么就无法对这个类添加代理(因为Proxy的static newProxyInstance方法明确指明了interfaces数组必须是interface)

    (6) 为了解决(5)中的问题,有一个叫做CGLIB的开源的动态字节码生成库诞生了

  • CGLib

    (1) CGLib的原理是:将横切逻辑(即InvocationHandler中实现的)放到子类中,然后让Client使用子类(因为子类和父类拥有相同的超类型,所以Client不管它实际用的是什么),只不过这个子类的字节码是动态生成的

    (2) CGLib既可以针对某个实现了interface的类进行扩展,也可以对没有实现任何接口的类进行扩展。但是由于Java原生的Proxy和InvocationHandler两件套对前者支持的够好了,所以CGLib一般用于支持后一种情况

    (3) CGLib的具体生成代理类字节码的流程和Proxy+InvocationHandler两件套很像

      Enhancer enhancer = new Enhancer();   //这个类目标就是生成代理类(子类)
    
      enhancer.setSuperclass(MyPerson.class);  //设置一下被代理类是谁
    
      enhancer.setCallback(new RequestCallback());   //再设置一下横切逻辑的实现类是谁(跟InvocationHandler差不多)
    
      MyPerson proxy = (MyPerson)enhancer.create();  //这样一个代理子类就创建出来了
    
      proxy.request();     //接下来就可以用这个代理了
    
      ...
    
  • SpringAOP中,如果被代理对象实现了相应的interface,就用Java动态代理;

    如果被代理对象没有实现相应的interface,就用CGLib

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值