1. 程式人生 > >曹工說Spring Boot原始碼(19)-- Spring 帶給我們的工具利器,建立代理不用愁(ProxyFactory)

曹工說Spring Boot原始碼(19)-- Spring 帶給我們的工具利器,建立代理不用愁(ProxyFactory)

寫在前面的話

相關背景及資源:

曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享

曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解

曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下

曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?

曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean

曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的

曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)

曹工說Spring Boot原始碼(8)-- Spring解析xml檔案,到底從中得到了什麼(util名稱空間)

曹工說Spring Boot原始碼(9)-- Spring解析xml檔案,到底從中得到了什麼(context名稱空間上)

曹工說Spring Boot原始碼(10)-- Spring解析xml檔案,到底從中得到了什麼(context:annotation-config 解析)

曹工說Spring Boot原始碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)

曹工說Spring Boot原始碼(12)-- Spring解析xml檔案,到底從中得到了什麼(context:component-scan完整解析)

曹工說Spring Boot原始碼(13)-- AspectJ的執行時織入(Load-Time-Weaving),基本內容是講清楚了(附原始碼)

曹工說Spring Boot原始碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎麼和Spring Instrumentation整合

曹工說Spring Boot原始碼(15)-- Spring從xml檔案裡到底得到了什麼(context:load-time-weaver 完整解析)

曹工說Spring Boot原始碼(16)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【上】)

曹工說Spring Boot原始碼(17)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【中】)

曹工說Spring Boot原始碼(18)-- Spring AOP原始碼分析三部曲,終於快講完了 (aop:config完整解析【下】)

工程程式碼地址 思維導圖地址

工程結構圖:

概要

本篇是接著前三篇講的,但是本篇相對獨立,即使不使用spring aop 和spring ioc,我們也可以利用今天要講的ProxyFactory為我們所用。

曹工說Spring Boot原始碼(16)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【上】)

曹工說Spring Boot原始碼(17)-- Spring從xml檔案裡到底得到了什麼(aop:config完整解析【中】)

曹工說Spring Boot原始碼(18)-- Spring AOP原始碼分析三部曲,終於快講完了 (aop:config完整解析【下】)

前面幾篇說到,spring如何實現aop,即將匹配切點的bean,生成動態代理,並將生成的動態代理放到ioc容器,來替換原先的bean,一系列騷操作,完成"代理換真身"的操作。

jdk動態代理

比較老套的話題,但是,我問大家幾個問題,看看大家是否真的足夠了解他呢?

在代理物件上,呼叫不在介面中的方法

package foo;


public class Performer implements Perform {
    @Override
    public void sing() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("男孩在唱歌");

    }

    public void eat() {
        System.out.println("男孩在吃飯");
    }
}

可以看到,我們sing是實現了介面中的方法,而eat不在介面中定義。

那麼,如下程式碼,結果會是啥:

@Test
public void createJdkDynamicProxyManual() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    Object generatedProxy = Proxy.newProxyInstance(loader, new Class[]{Perform.class}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("proxy:" + proxy.getClass());
            return "hahh";
        }
    });
    Method eat = Perform.class.getMethod("eat");
    eat.setAccessible(true);
    eat.invoke(generatedProxy,null);
}

程式碼中,我們建立了一個代理物件:generatedProxy;然後,呼叫了其eat方法,結果會是啥呢?

java.lang.NoSuchMethodException: foo.Perform.eat()
at java.lang.Class.getMethod(Class.java:1665)
at java.lang.Class.getMethod(Class.java:1665)

為啥會這樣呢?因為我們建立代理物件時,是在Perform.class這個介面上建立的。大家可以再仔細看看。

jdk 動態代理(Proxy.newProxyInstance)有哪幾個步驟

這個問題,有人思考過嗎?簡單來說,其實有3個步驟。

  1. 生成動態代理類的class,雖然不像其他class檔案那樣,是編譯了就有的,這裡,是動態生成的;
  2. 載入第一步拿到的位元組流,丟給jvm載入該class,拿到Class物件
  3. 根據第二步的Class物件,反射生成動態代理物件。

我剛仔細看了Proxy.newProxyInstance的方法註釋:

Returns an instance of a proxy class for the specified interfaces
that dispatches method invocations to the specified invocation
handler.  This method is equivalent to:

Proxy.getProxyClass(loader, interfaces).         // 對應步驟1和2
  getConstructor(new Class[] { InvocationHandler.class }).   // 對應步驟3
  newInstance(new Object[] { handler }); // 對應步驟3

其中,第一步,細問一下,class是怎麼生成的,很多人估計又答不上了。咱們這裡就看一下:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, interfaces);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    return newInstance(cons, ih);
}

可以看到,主要的獲取Class,是getProxyClass0方法,這個方法裡面程式碼不少,去掉非核心的快取等部分,核心的部分如下:

String proxyName = proxyPkg + "$Proxy" + num;

/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces);  
proxyClass = defineClass0(loader, proxyName,
                          proxyClassFile, 0, proxyClassFile.length);

這其中,ProxyGenerator.generateProxyClass 負責生成class的位元組流,對應我們前面講到的步驟1;defineClass0對應類載入。我們仔細說說:

  1. 位元組流生成

    這部分呢,其實就是呼叫了ProxyGenerator.generateProxyClass,我們跟蹤發現,它的全名為:sun.misc.ProxyGenerator,是sun包下的。這部分沒法看原始碼,還好我之前下載過openjdk的原始碼,這裡我給大家全文貼一下:

     /**
         * Generate a class file for the proxy class.  This method drives the
         * class file generation process.
         */
        private byte[] generateClassFile() {
    
            /* ============================================================
             * Step 1: Assemble ProxyMethod objects for all methods to
             * generate proxy dispatching code for.
             */
    
            /*
             * Record that proxy methods are needed for the hashCode, equals,
             * and toString methods of java.lang.Object.  This is done before
             * the methods from the proxy interfaces so that the methods from
             * java.lang.Object take precedence over duplicate methods in the
             * proxy interfaces.
             */
            addProxyMethod(hashCodeMethod, Object.class);
            addProxyMethod(equalsMethod, Object.class);
            addProxyMethod(toStringMethod, Object.class);
    
            /*
             * Now record all of the methods from the proxy interfaces, giving
             * earlier interfaces precedence over later ones with duplicate
             * methods.
             */
            for (int i = 0; i < interfaces.length; i++) {
                Method[] methods = interfaces[i].getMethods();
                for (int j = 0; j < methods.length; j++) {
                    addProxyMethod(methods[j], interfaces[i]);
                }
            }
    
            /*
             * For each set of proxy methods with the same signature,
             * verify that the methods' return types are compatible.
             */
            for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                checkReturnTypes(sigmethods);
            }
    
            /* ============================================================
             * Step 2: Assemble FieldInfo and MethodInfo structs for all of
             * fields and methods in the class we are generating.
             */
            try {
                methods.add(generateConstructor());
    
                for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                    for (ProxyMethod pm : sigmethods) {
    
                        // add static field for method's Method object
                        fields.add(new FieldInfo(pm.methodFieldName,
                            "Ljava/lang/reflect/Method;",
                             ACC_PRIVATE | ACC_STATIC));
    
                        // generate code for proxy method and add it
                        methods.add(pm.generateMethod());
                    }
                }
    
                methods.add(generateStaticInitializer());
    
            } catch (IOException e) {
                throw new InternalError("unexpected I/O Exception");
            }
    
            if (methods.size() > 65535) {
                throw new IllegalArgumentException("method limit exceeded");
            }
            if (fields.size() > 65535) {
                throw new IllegalArgumentException("field limit exceeded");
            }
    
            /* ============================================================
             * Step 3: Write the final class file.
             */
    
            /*
             * Make sure that constant pool indexes are reserved for the
             * following items before starting to write the final class file.
             */
            cp.getClass(dotToSlash(className));
            cp.getClass(superclassName);
            for (int i = 0; i < interfaces.length; i++) {
                cp.getClass(dotToSlash(interfaces[i].getName()));
            }
    
            /*
             * Disallow new constant pool additions beyond this point, since
             * we are about to write the final constant pool table.
             */
            cp.setReadOnly();
    
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            DataOutputStream dout = new DataOutputStream(bout);
    
            try {
                /*
                 * Write all the items of the "ClassFile" structure.
                 * See JVMS section 4.1.
                 */
                                            // u4 magic;
                dout.writeInt(0xCAFEBABE);
                                            // u2 minor_version;
                dout.writeShort(CLASSFILE_MINOR_VERSION);
                                            // u2 major_version;
                dout.writeShort(CLASSFILE_MAJOR_VERSION);
    
                cp.write(dout);             // (write constant pool)
    
                                            // u2 access_flags;
                dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
                                            // u2 this_class;
                dout.writeShort(cp.getClass(dotToSlash(className)));
                                            // u2 super_class;
                dout.writeShort(cp.getClass(superclassName));
    
                                            // u2 interfaces_count;
                dout.writeShort(interfaces.length);
                                            // u2 interfaces[interfaces_count];
                for (int i = 0; i < interfaces.length; i++) {
                    dout.writeShort(cp.getClass(
                        dotToSlash(interfaces[i].getName())));
                }
    
                                            // u2 fields_count;
                dout.writeShort(fields.size());
                                            // field_info fields[fields_count];
                for (FieldInfo f : fields) {
                    f.write(dout);
                }
    
                                            // u2 methods_count;
                dout.writeShort(methods.size());
                                            // method_info methods[methods_count];
                for (MethodInfo m : methods) {
                    m.write(dout);
                }
    
                                             // u2 attributes_count;
                dout.writeShort(0); // (no ClassFile attributes for proxy classes)
    
            } catch (IOException e) {
                throw new InternalError("unexpected I/O Exception");
            }
    
            return bout.toByteArray();
        }

    這裡其實是有面試題的,我之前還被問過,問我用的什麼技術來生成class位元組流,這裡其實是沒有用任何第三方工具的,這個類的import語句部分,也沒有asm、javaasist等工具。

    import java.io.ByteArrayOutputStream;
    import java.io.DataOutputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.lang.reflect.Array;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.ListIterator;
    import java.util.Map;
    import sun.security.action.GetBooleanAction;
    1. 載入class位元組流為Class

      這部分的程式碼即為前面提到的:

      try {
          proxyClass = defineClass0(loader, proxyName,
              proxyClassFile, 0, proxyClassFile.length);
      }

      其中,defineClass0 是一個native方法:

      java.lang.reflect.Proxy#defineClass0
      private static native Class defineClass0(ClassLoader loader, String name,
                                               byte[] b, int off, int len);

      讓我比較驚訝的是,這個native方法,是在Proxy類裡,且除了此處的呼叫,沒有被其他程式碼呼叫。

      我去看了Classloader這個類的程式碼,裡面也有幾個native的defineClass的方法:

      private native Class defineClass0(String name, byte[] b, int off, int len,
                                        ProtectionDomain pd);
      
      private native Class defineClass1(String name, byte[] b, int off, int len,
                                        ProtectionDomain pd, String source);
      
      private native Class defineClass2(String name, java.nio.ByteBuffer b,
                                        int off, int len, ProtectionDomain pd,
                                        String source);

      看來,Proxy是自己自立門戶啊,沒有使用Classloader類下面的defineClass等方法。

      如果大家想看生成的class的檔案的內容,可以加這個虛擬機器啟動引數:

      -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

      或者main最前面,加這個:

      System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    2. 反射生成代理物件

      這步就沒啥好說的了,經過上面第二步,已經拿到Class物件了。反射對於大家,也是輕車熟路了。

      Constructor<?> cons = cl.getConstructor({ InvocationHandler.class });
      final InvocationHandler ih = h;
      newInstance(cons, ih);
      
      private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
          return cons.newInstance(new Object[] {h} );
      }

      這裡,我們看到,獲取的建構函式,就是要接收一個InvocationHandler物件的。拿到了建構函式後,接下來,就呼叫了建構函式的newInstance,來生成代理物件。

      具體的呼叫就不說了,反正你呼叫的任何方法(只能呼叫接口裡有的那些),都會轉到invocationHandler的invoke方法。

關於jdk動態代理的思考

其實,大家看到上面,會想下面這個問題不?現在在代理物件上,呼叫方法,最終都會進入到:

 java.lang.reflect.InvocationHandler#invoke
 public Object invoke(Object proxy, Method method, Object[] args)
     throws Throwable;

我如果想在這個邏輯裡面,去呼叫原始目標的方法,怎麼辦呢?

我們看看傳給我們的幾個引數:

1. proxy,代理物件;這個沒辦法拿到原始物件
2. method,是被呼叫的方法,也拿不到原始物件
3. args,給method的引數,也拿不到原始物件。

這就迷離了。那我咋辦呢?

答案是,在建立InvocationHandler時,把原始物件傳進去,以及其他一切必要的資訊,都傳進去。

當然,你也可以不傳進去,在invoke方法裡,為所欲為,比如下面的方法:

  ClassLoader loader = Thread.currentThread().getContextClassLoader();
  Object generatedProxy = Proxy.newProxyInstance(loader, new Class[]{Perform.class}, new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          System.out.println("到我這為止,不會呼叫target了");
          return null;
      }
  });
  // 這裡,雖然呼叫了sing,但裡面的邏輯也不會執行。
  ((Perform)generatedProxy).sing();

其實,這個代理,已經相當於是Perform介面的另一個實現了;和之前的實現類,沒有半毛錢關係。

如果要讓它實施代理的工作,可以這樣做:

  @Test
  public  void createJdkDynamicProxyManual() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
      Performer performer = new Performer();
      MyCustomInvocationHandler myCustomInvocationHandler = new MyCustomInvocationHandler(performer);
  
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      Object generatedProxy = Proxy.newProxyInstance(loader,
              new Class[]{Perform.class}, myCustomInvocationHandler);
  
      ((Perform)generatedProxy).sing();
  
  }
  
  public static class MyCustomInvocationHandler implements InvocationHandler {
      Performer performer;
  
      public MyCustomInvocationHandler(Performer performer) {
          this.performer = performer;
      }
  
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          System.out.println("我是一個稱職的代理");
          return method.invoke(performer,args);
      }
  }

上面這個程式碼,就沒問題了。會輸出如下:

我是一個稱職的代理

男孩在唱歌

jdk動態代理實現思路的案例程式碼

我們上面說了怎麼樣正確地實現代理的思路,就是要把target/原始bean,在new invocationHandler的時候,傳遞給它,後續在invoke裡再使用。我們看看框架對invocationHandler的其他實現,是怎麼做的吧?

我在project裡找了下InvocationHandler的實現類,發現了jdbc中的一個實現類。

org.springframework.jdbc.datasource.ConnectionProxy

public interface ConnectionProxy extends Connection {

   /**
    * Return the target Connection of this proxy.
    * <p>This will typically be the native driver Connection
    * or a wrapper from a connection pool.
    * @return the underlying Connection (never {@code null})
    */
   Connection getTargetConnection();

}

這個是Connection的子介面,通過這個介面,獲取真正的資料庫連線。我們看看其代理實現:

org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy#getConnection(java.lang.String, java.lang.String)
public Connection getConnection(String username, String password) throws SQLException {
   return (Connection) Proxy.newProxyInstance(
         ConnectionProxy.class.getClassLoader(),
         new Class[] {ConnectionProxy.class},
         new LazyConnectionInvocationHandler(username, password));
}

這個代理實現,主要是延遲獲取資料庫連線,等到使用的時候,才去獲取連線;而不是啟動時,即建立連線池。

private class LazyConnectionInvocationHandler implements InvocationHandler {

   private String username;

   private String password;

   private Boolean readOnly = Boolean.FALSE;

   private Integer transactionIsolation;

   private Boolean autoCommit;

   private boolean closed = false;

   private Connection target;

   public LazyConnectionInvocationHandler() {
      this.autoCommit = defaultAutoCommit();
      this.transactionIsolation = defaultTransactionIsolation();
   }

   public LazyConnectionInvocationHandler(String username, String password) {
      this();
      this.username = username;
      this.password = password;
   }

   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // Invocation on ConnectionProxy interface coming in...

      if (method.getName().equals("equals")) {
         // We must avoid fetching a target Connection for "equals".
         // Only consider equal when proxies are identical.
         return (proxy == args[0]);
      }
      ...
      else if (method.getName().equals("getTargetConnection")) {
         // Handle getTargetConnection method: return underlying connection.
         return getTargetConnection(method);
      }
      ...
   }
   
   

這裡呢,如果方法為getTargetConnection,則呼叫了以下方法:

private Connection getTargetConnection(Method operation) throws SQLException {
      if (this.target == null) {
         // No target Connection held -> fetch one.
         if (logger.isDebugEnabled()) {
            logger.debug("Connecting to database for operation '" + operation.getName() + "'");
         }

         // 通過使用者名稱,密碼去獲取資料庫連線
         this.target = (this.username != null) ?
               getTargetDataSource().getConnection(this.username, this.password) :
               getTargetDataSource().getConnection();

         // If we still lack default connection properties, check them now.
         checkDefaultConnectionProperties(this.target);

         // Apply kept transaction settings, if any.
         if (this.readOnly) {
            try {
               this.target.setReadOnly(this.readOnly);
            }
            catch (Exception ex) {
               // "read-only not supported" -> ignore, it's just a hint anyway
               logger.debug("Could not set JDBC Connection read-only", ex);
            }
         }
         if (this.transactionIsolation != null &&
               !this.transactionIsolation.equals(defaultTransactionIsolation())) {
            this.target.setTransactionIsolation(this.transactionIsolation);
         }
         if (this.autoCommit != null && this.autoCommit != this.target.getAutoCommit()) {
            this.target.setAutoCommit(this.autoCommit);
         }
      }

      return this.target;
   }
}

大家從上面程式碼,可以看到,是有通過使用者名稱密碼去獲取資料庫連線的。

所以,看來,正確的實現代理的思路就是,在構造proxy的時候,把你需要的東西,都通過建構函式或setter,傳遞給invocationHandler。然後再在invoke方法內去使用這些東西,來完成你的邏輯。

Spring提供給我們的強大工具類:ProxyFactory

大家看了上面,覺得生成代理,簡單,還是複雜呢?也許還不是很難。但如果是使用cglib的方式去建立代理,程式碼可就要多好一些了。(這個留到後面講)

其實,spring裡給我們提供了神器的,即我們要說的:ProxyFactory。其註釋如下,意思是,aop代理工廠,不用通過bean factory,可以直接使用。這個類提供一個簡單的獲取和配置aop代理的方式。

* Factory for AOP proxies for programmatic use, rather than via a bean
* factory. This class provides a simple way of obtaining and configuring
* AOP proxies in code.

意思是,我們平時,實現aop,主要依靠spring的aop,即通過註解或者xml的方式,宣告式地建立aop(比如配置事務時)。這裡的意思是,我們可以通過程式碼方式來實現同樣的效果,即,建立代理。

大家把這個類,理解為代理工廠即可,工廠嘛,就是給它東西,它給你返回產品,這個產品,就是代理物件。

如何利用ProxyFactory建立代理

我們看看,把它當成黑盒的話,如何利用它,來簡化我們建立代理的過程:

 @Test
    public void createJdkDynamicProxy() {
        ProxyFactory proxyFactory = new ProxyFactory();
//        Performer performer = new Performer();
//        proxyFactory.setTarget(performer);

        proxyFactory.addInterface(Perform.class);

        Perform proxy = (Perform) proxyFactory.getProxy();

        log.info("proxy class:{}",proxy.getClass().getName());
        proxy.sing();
        log.info("proxy:{}",proxy);
    }

正常情況下,按照我們前面對jdk動態代理的理解,上面這樣就夠了。但是,上面程式碼會報錯,說沒有指定target 物件。所以,我們實際上,需要把上面那兩行註釋給放開,否則報如下錯誤。


org.springframework.aop.framework.AopConfigException: No advisors and no TargetSource specified

    at org.springframework.aop.framework.JdkDynamicAopProxy.<init>(JdkDynamicAopProxy.java:103)
    at org.springframework.aop.framework.DefaultAopProxyFactory.createAopProxy(DefaultAopProxyFactory.java:65)
    at org.springframework.aop.framework.ProxyCreatorSupport.createAopProxy(ProxyCreatorSupport.java:105)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:98)
    at ProxyFactoryTest.createJdkDynamicProxy(ProxyFactoryTest.java:44)

上面放開那個註釋程式碼後,預設就會去呼叫target的對應方法,會有如下輸出:

2020-02-25 08:32:29.828 [main] INFO  ProxyFactoryTest - proxy class:com.sun.proxy.$Proxy5
男孩在唱歌
2020-02-25 08:32:30.910 [main] INFO  ProxyFactoryTest - proxy:foo.Performer@502775a1

如何建立代理的同時,織入切面

我們上面只是建立了代理,預設去呼叫了target的對應方法,假設我們要切一下,怎麼辦?

不慌!

@Test
    public void createJdkDynamicProxyWithAdvisor() {
        ProxyFactory proxyFactory = new ProxyFactory();
        Performer performer = new Performer();
        proxyFactory.setTarget(performer);

        proxyFactory.addInterface(Perform.class);
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setAdvice(new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                Object result = invocation.proceed();
                System.out.println("男孩唱完要行禮");
                return result;
            }
        });

        proxyFactory.addAdvisor(advisor);

        Perform proxy = (Perform) proxyFactory.getProxy();

        ProxyFactoryTest.log.info("proxy class:{}",proxy.getClass().getName());
        proxy.sing();
    }

這裡的重點程式碼就是:

        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setAdvice(new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                Object result = invocation.proceed();
                System.out.println("男孩唱完要行禮");
                return result;
            }
        });

        proxyFactory.addAdvisor(advisor);

這上面的幾行程式碼,主要是建立了一個advisor,一個advisor 幾乎等於切點+通知。

advisor的setAdvice呢,主要接受一個Advice型別的引數。而MethodInterceptor就是它的子介面。

當然了,其實advice的實現很多,包括spring裡都有很多內部實現。我這裡找了一個,對方法執行耗時,進行監測的。

我把上面的程式碼改動了一行:

advisor.setAdvice(new PerformanceMonitorInterceptor());

這個類,的繼承關係如下:

主要功能就是記錄耗時,此時,輸出如下:

2020-02-25 08:40:06.825 [main] INFO ProxyFactoryTest - proxy class:com.sun.proxy.$Proxy5
男孩在唱歌
2020-02-25 08:40:07.868 [main] TRACE o.s.aop.interceptor.PerformanceMonitorInterceptor - StopWatch 'foo.Perform.sing': running time (millis) = 1006

總結

今天大概講了jdk動態代理的原理,和ProxyFactory的使用。下一講,繼續aop之旅,主要講解ProxyFactory的原理