Before Advice

Before Advice會在目標物件的方法執行之前被呼叫,您可以實現org.springframework.aop.MethodBeforeAdvice介面來實作Before Advice的邏輯,該介面於Spring中的定義如下所示:

   package org.springframework.aop;
   public interface MethodBeforeAdvice extends BeforeAdvice {
       void before(Method method, Object[] args, 
                 Object target) throws Throwable;
   }

在定義中可以看到,MethodBeforeAdvice繼承自BeforeAdvice介面,而BeforeAdvice介面又繼承自Advice介 面,後兩者都是標籤介面(Tag interface),只是用作標示而無定義任何方法,MethodBeforeAdvice繼承了BeforeAdvice,before()方法會在 目標物件(Target)上指定的方法執行之前被呼叫,您可以取得被執行的Method實例、引數及目標物件,before()方法上宣告為void,所 以不傳回任何的結果,在before()方法執行完畢之後,除非您丟出例外,否則目標物件上的方法就會被執行。

以實例來示範如何使用Before Advice,首先要定義目標物件必須實現的介面:

   IHello.java
   package onlyfun.caterpillar;
   
   public interface IHello {
       public void hello(String name);
   }

接著定義一個HelloSpeaker類別,讓其實現IHello介面:

   HelloSpeaker.java
   package onlyfun.caterpillar;
   
   public class HelloSpeaker implements IHello {
       public void hello(String name) {
           System.out.println("Hello, " + name); 
       }
   } 

現在HelloSpeaker已經撰寫完畢,在不對它進行任何修改的情況下,您想要在hello()方法執行之前,可以記錄一些訊息,想像一下這是您拿到 的一個組件,您沒有原始碼,但您想對它增加一些日誌的服務。您可以先實作MethodBeforeAdvice介面,例如:

   LogBeforeAdvice.java
   package onlyfun.caterpillar;
   
   import java.lang.reflect.Method;
   import java.util.logging.Level;
   import java.util.logging.Logger;
   import org.springframework.aop.MethodBeforeAdvice;
   
   public class LogBeforeAdvice 
           implements MethodBeforeAdvice { 
       private Logger logger = 
               Logger.getLogger(this.getClass().getName()); 
   
       public void before(Method method, Object[] args, 
                        Object target) throws Throwable { 
           logger.log(Level.INFO, 
                   "method starts..." + method); 
      } 
   } 

在before()方法的實作中,您加入了一些記錄資訊的程式碼,LogBeforeAdvice類別被設計為一個獨立的服務,可以重複提供服務給需要的物件,接著您只要在定義檔中如下定義:

   beans-config.xml
   <?xml version="1.0" encoding="UTF-8"?> 
   <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" 
     "http://www.springframework.org/dtd/spring-beans.dtd"> 
   
   <beans> 
   <bean id="logBeforeAdvice" 
         class="onlyfun.caterpillar.LogBeforeAdvice"/> 
   
   <bean id="helloSpeaker" 
         class="onlyfun.caterpillar.HelloSpeaker"/> 
   
   <bean id="helloProxy" 
         class="org.springframework.aop.framework.ProxyFactoryBean"> 
       <property name="proxyInterfaces"> 
           <value>onlyfun.caterpillar.IHello</value> 
       </property> 
       <property name="target"> 
           <ref bean="helloSpeaker"/> 
       </property> 
       <property name="interceptorNames"> 
           <list> 
               <value>logBeforeAdvice</value> 
           </list> 
       </property> 
   </bean> 
   </beans>

注意到除了建立Advice及Target的物件實例之外,您還使用了 org.springframework.aop.framework.ProxyFactoryBean,這個類別會被BeanFactory或是 ApplicationContext用來建立代理物件(回憶一下前一個小節,Spring AOP主要是透過代理機制來實現,因而需要建立代理物件),您要在"proxyInterfaces"屬性上告知代理時的可運用的介面,在 "target"上告知 Target物件,在"interceptorNames"上告知所要應用的Advice實例,在不指定目標方法時,Before Advice會被縫合(Weave)至介面上所有定義的方法之前。

可以撰寫以下的程式測試一下Before Advice的運作:

   SpringAOPDemo.java
   package onlyfun.caterpillar;
   
   import org.springframework.context.ApplicationContext;
   import org.springframework.context.
             support.FileSystemXmlApplicationContext;
   
   public class SpringAOPDemo {
       public static void main(String[] args) {
           ApplicationContext context = 
                   new FileSystemXmlApplicationContext(
                           "beans-config.xml"); 
           IHello helloProxy = 
               (IHello) context.getBean("helloProxy"); 
           helloProxy.hello("Justin"); 
       }
   } 

記得在操作取回的代理物件時,必須轉換操作介面為IHello介面,執行結果將會在呼叫hello()方法前進行日誌動作。

您所設計的HelloSpeaker與LogBeforeAdvice是兩個獨立的物件,對於HelloSpeaker來說,它不用知道 LogBeforeAdvice的存在(也就是沒有任何與LogBeforeAdvice相關的API撰寫在HelloSpeaker中),而 LogBeforeAdvice也可以運用至其它的物件之上,HelloSpeaker與LogBeforeAdvice都是可以重複使用的設計。

可以看出AOP的精神,著重於Aspects的辨識,設計可重複使用的Advices,就如OOP重視物件的辨識,設計可重複使用的物件。