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

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

虽然本文的内容相当简单,但它实际上会让你对 Spring 2.0 中一些相当重要的新特性有所了解。我希望通过一点点想象力,你能够将此处看到的内容应用到你自己的那些远不那么简单的用例中去。

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


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);
}

以及一个实现类(Implementing Class)


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——它不实现任何接口或遵循任何契约即可用作切面(aspect)。事实上,你很可能已经有这样的代码了。;)

ìloggingAspectî 包含两种通知(advice)。一种是 ìbeforeî 通知,另一种是 ìafterReturningî 通知。接下来,你会看到通知实际映射到SimpleLoggerPOJO 的方法——logTwoStrings()用于 before 通知,以及logOneString()用于 afterReturning 通知。这种声明性映射到 POJO 方法的选项是实现通知接口的一个有用替代方案。

最后,简单介绍一下绑定(binding)和切入点(pointcuts)。在 ìbeforeî 通知中,args(s1,s2) 指定当有两个参数可以绑定到这两个参数时,该切入点将应用到对应方法,这些参数是String参数,这些参数属于该logTwoStrings()方法——正如你稍后就会看到的,这里正是如此。在 ìafterReturningî 的情况下,返回值将绑定到该单个String参数,该参数属于该logOneString()方法。

现在,关于切入点... 上面 ìpointcutî 属性中的值实际上是标准的 AspectJ 切入点表达式。在这种情况下,它们定义了哪些方法会被通知。ì*î 是一个通配符,第一个 ì..î 表示任何后代包,而第二个 ì..î 表示任意数量和类型的参数。本质上,只要是 ìexampleî 包的后代,这个切入点将应用于任何名称以 ìServiceî 结尾的类的任何方法,无论其参数类型、参数数量或返回值如何。好的,也许这听起来不那么简单——但如果至少听起来有趣的话,你可以在 AspectJ 网站上阅读更多关于 AspectJ 表达式语言的内容。

注意:虽然这里使用了 AspectJ 表达式,但通知(advice)仍然是通过 Spring 的基于代理的 AOP(Proxy-based AOP) 应用的,而不是 AspectJ 织入(weaving)。这意味着拦截器只能在方法执行连接点(joinpoints)处添加行为。方法执行拦截很可能满足你的大多数 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 定义。再次,你看到了切面(aspects)和通知(advice)。这次 ì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 切面(aspects)来增加额外的行为。事实上,在某些情况下,让它们成为切面的唯一因素是配置。在其他情况下,当你需要更多的运行时信息时,JoinPointStaticPart会非常有用。

如果你对这个主题更全面的介绍感兴趣,请访问 Adrian Colyer 的这篇博客

注意:在那篇文章中,你会看到使用 <aop:advice> 元素的示例。在 Spring 2.0 M3 中,这些元素已被本示例中使用的更具体的元素所取代,例如:<aop:before>。

订阅 Spring 新闻通讯

订阅 Spring 新闻通讯,保持联系

订阅

抢占先机

VMware 提供培训和认证,助你加速进步。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅即可为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将举行的活动

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

查看全部