领先一步
VMware 提供培训和认证,以加快您的进度。
了解更多虽然这篇文章的内容很简单,但它实际上会让你了解 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` 是单独定义的,并被两种通知类型重复使用。也许这里最有趣的是,没有为方法参数进行显式绑定,也不需要配置任何内容来识别JoinPoint或StaticPart参数。事实上,你总是可以将其中一个指定为方法的第一个参数,以便访问有关方法执行上下文更多信息。
要运行此示例,我将使用相同的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 的 这篇博客。
注意:在那篇文章中,你会看到带有 `