Spring 2.0 中的 POJO 切面:一个简单的示例

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

虽然这篇文章的内容很简单,但它实际上会让你了解 Spring 2.0 中一些相当重要的新特性。我希望通过一点想象力,你能够将你在这里看到的内容应用到你自己更不平凡的用例中。

我实际上将展示两个例子。第一个将使用一个相当简单的日志记录器。


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` 包含两种类型的通知。一种是 `before` 类型的通知,另一种是 `afterReturning` 类型的通知。接下来,你会看到该通知实际上映射到SimpleLoggerPOJO 的方法——logTwoStrings()用于 `before` 通知,以及logOneString()用于 `afterReturning` 通知。这种声明式映射到 POJO 方法的选项是实现通知接口的有用替代方案。

最后,简单介绍一下绑定和切点。在 `before` 通知中,`args(s1,s2)` 指定当有两个参数可以绑定到两个String参数时,此切点将适用logTwoStrings()方法——正如你稍后将看到的,这里正是这样发生的。在 `afterReturning` 的情况下,返回值将绑定到单个String参数logOneString()方法。

现在,对于切点……上面 `pointcut` 属性中的值实际上是标准的 AspectJ 切点表达式。在本例中,它们定义了哪些方法将被通知。`*` 是通配符,第一个 `..` 表示 *任何后代包*,第二个 `..` 表示 *任何数量和类型的参数*。基本上,这个切点将应用于以 `Service` 结尾的类的任何方法,无论其参数类型或数量如何,只要它以某种方式继承自 `example` 包即可。好的,也许这听起来不太 *简单*——但如果它至少听起来很有趣,那么你可以在 AspectJ 网站上阅读更多关于 AspectJ 表达式语言的内容。

注意:虽然这里使用了 AspectJ 表达式,但通知仍然是通过 Spring 的 *基于代理的 AOP* 应用的,而不是 AspectJ 织入。这意味着拦截器只能在方法执行连接点添加行为。方法执行拦截很可能满足你大部分 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* 服务。事实上,在某些情况下,使它们成为切面的唯一事情就是配置。在其他情况下,当你需要更多运行时信息时,JoinPointStaticPart可能非常有用。

如果你有兴趣更全面地了解这个主题,请访问 Adrian Colyer 的 这篇博客

注意:在那篇文章中,你会看到带有 `` 元素的示例。在 Spring 2.0 M3 中,这些元素已被更具体的元素所取代,如本条目中使用的 ``。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加快您的进度。

了解更多

获得支持

Tanzu Spring在一个简单的订阅中提供对OpenJDK™、Spring和Apache Tomcat®的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部