Spring 2.0 中的 POJO 切面:一個簡單示例

工程 | Mark Fisher | 2006年3月22日 | ...

儘管這篇文章中的材料非常簡單,但它實際上將展示 Spring 2.0 中一些相當重要的新功能。我希望透過一點想象力,您能夠將您在此處看到的內容應用於您自己的遠非簡單的用例。

我實際上將展示 2 個示例。第一個將使用一個相當簡單的記錄器


package example;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SimpleLogger {

  private static Log log = LogFactory.getLog(SimpleLogger.class);

  public void logOneString(String s) {
    log.info("string=" + s);
  }

  public void logTwoStrings(String s1, String s2) {
    log.info("string1=" + s1 + ",string2=" + s2);
  }
}

我將使用 AOP 為字串連線服務應用日誌記錄。這是介面


package example;

public interface ConcatService {
  public String concat(String s1, String s2);
}

一個實現類


package example;

public class ConcatServiceImpl implements ConcatService {

  public String concat(String s1, String s2) {
    return s1 + s2;
  }
}

好的——到目前為止,這沒什麼令人興奮的,但最重要的是要注意,到目前為止,我*只處理 POJO*。

現在,看看這些 bean 定義。注意新 Spring 2.0 XML Schema 的使用,尤其是 ìaopî 名稱空間


<?xml version="1.0" encoding="UTF-8"?>
<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

  <aop:config>
    <aop:aspect id="loggingAspect" ref="simpleLogger">
      <aop:before
        method="logTwoStrings"
        pointcut="execution(* example..*Service.*(..)) and args(s1,s2)"/>
      <aop:after-returning
        method="logOneString"
        returning="s"
        pointcut="execution(* example..*Service.*(..))"/>
    </aop:aspect>
  </aop:config>

  <bean id="simpleLogger" class="example.SimpleLogger"/>

  <bean id="concatService" class="example.ConcatServiceImpl"/>

</beans>

ìloggingAspectî 定義為對上面第一個程式碼片段中看到的 ìsimpleLoggerî 的引用。同樣,有趣的是,它是一個簡單的 POJO——它不實現任何介面或遵循任何合同,以便被用作切面。事實上,你可能已經有了這樣的程式碼。;)

ìloggingAspectî 包含 2 種通知。一種是 ìbeforeî 型別的通知,另一種是 ìafterReturningî 型別的通知。接下來,您可以看到通知實際上對映到SimpleLoggerPOJO -logTwoStrings()用於*before*通知,以及logOneString()用於*afterReturning*通知。這種宣告式對映到 POJO 方法的選項是實現通知介面的一個有用的替代方法。

最後,關於繫結和切點快速說幾句。在 ìbeforeî 通知中,*args(s1,s2)* 指定此切點將在存在 2 個引數時應用,這些引數可以繫結到String方法的logTwoStrings()引數——而這正是此時發生的情況,正如您稍後將看到的。在 ìafterReturningî 的情況下,返回值將繫結到單個String引數的logOneString()方法實現。

現在,對於切點……上面 ìpointcutî 屬性中的值實際上是標準的 AspectJ 切點表示式。在這種情況下,它們定義了哪些方法將被通知。ì*î 是一個萬用字元,第一個 ì..î 表示*任何子包*,而第二個 ì..î 表示*任意數量和型別的引數*。本質上,這個切點將應用於任何以 ìServiceî 結尾的類的任何方法,無論其引數型別或計數如何,只要它以某種方式繼承自 ìexampleî 包。好吧,也許這聽起來並不那麼*簡單*——但如果它至少聽起來很有趣,那麼您可以在 AspectJ 網站上閱讀更多關於 AspectJ 表示式語言的內容。

注意:雖然這裡使用了 AspectJ 表示式,但通知仍然透過 Spring 的*基於代理的 AOP* 應用,而不是 AspectJ weaving。這意味著攔截器只能在方法執行連線點新增行為。方法執行攔截很可能滿足您的大多數 AOP 用例。但是,要在其他連線點(例如欄位訪問)應用通知,您可以使用 AspectJ 的全部功能(這超出了本文的範圍)。

那麼,毫不猶豫……這是一個簡單的main()方法來嘗試一下


public static void main(String[] args) {
  ApplicationContext context = new ClassPathXmlApplicationContext("example/simpleLoggerContext.xml");
  ConcatService concatService = (ConcatService)context.getBean("concatService");
  concatService.concat("some", "thing");
}

結果!

string1=some,string2=thing
string=something

現在,來看第二個例子……

當然,您可能想記錄更多資訊,例如方法引數、呼叫方法本身等等。為了展示如何實現這一點,我將修改這個SimpleLogger一點點。秘密在於JoinPoint類(和StaticPart類),它現在將提供給我新類的那些方法,MethodLogger:


package example;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;

public class MethodLogger {

  private static Log log = LogFactory.getLog(MethodLogger.class);

  public void logMethodEntry(JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();
    String name = joinPoint.getSignature().toLongString();
    StringBuffer sb = new StringBuffer(name + " called with: [");
    for(int i = 0; i < args.length; i++) {
      Object o = args[i];
      sb.append(o);
      sb.append((i == args.length - 1) ? "]" : ", ");
    }
    log.info(sb);
  }

  public void logMethodExit(StaticPart staticPart, Object result) {
    String name = staticPart.getSignature().toLongString();
    log.info(name + " returning: [" + result + "]");
  }
}

正如您所見,JoinPoint提供了對所需執行時資訊的訪問。在logMethodExit()方法中,只需要型別,所以StaticPart就足夠了(它實際上是JoinPoint的一部分,其中JoinPoint提供了一個getStaticPart()方法)。一般來說,每當您可以在無需訪問執行時資訊的情況下完成您需要的事情時,您就應該這樣做。

這是使用MethodLogger:


<?xml version="1.0" encoding="UTF-8"?>
<beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

  <aop:config>
    <aop:pointcut id="servicePointcut" expression="execution(* example..*Service+.*(..))"/>
    <aop:aspect id="loggingAspect" ref="methodLogger">
      <aop:before
        method="logMethodEntry"
        pointcut-ref="servicePointcut"/>
      <aop:after-returning
        method="logMethodExit"
        returning="result"
        pointcut-ref="servicePointcut"/>
    </aop:aspect>
  </aop:config>

  <bean id="methodLogger" class="example.MethodLogger"/>

  <bean id="concatService" class="example.ConcatServiceImpl"/>

</beans>

的 bean 定義。再次,您可以看到切面和通知。這次 ìpointcutî 是單獨定義的,併為兩種通知型別重複使用。也許這裡最有趣的是,沒有顯式繫結方法引數,也沒有需要配置任何內容來識別JoinPointStaticPart引數。事實上,您總是可以將其中一個指定為方法的第一個引數,以便能夠訪問有關方法執行上下文的更多資訊。

要執行此示例,我將使用相同的main(),但這次將新的 bean 定義檔案的路徑傳遞到ClassPathXmlApplicationContext建構函式。這是結果

public abstract java.lang.String example.ConcatService.concat(java.lang.String,java.lang.String) called with: [some, thing]
public abstract java.lang.String example.ConcatService.concat(java.lang.String,java.lang.String) returning: [something]

這個*簡單*的例子就到這裡了。要點是*POJO*服務可以透過*POJO 切面*來裝飾其他行為。事實上,在某些情況下,使它們成為切面的唯一因素是配置。在其他情況下,當您需要更多執行時資訊時,*JoinPoint還是StaticPart就可能非常有用。

如果您對該主題的更全面介紹感興趣,請訪問 Adrian Colyer 的部落格

注意:在該帖子中,您將看到帶有 <aop:advice> 元素的示例。在 Spring 2.0 M3 中,這些元素已被替換為本文中使用的更具體的元素,例如:<aop:before>。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有