领先一步
VMware 提供培训和认证,助您加速进步。
了解更多尽管这篇文章中的材料非常简单,但它实际上将展示 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î 是单独定义的,并为两种通知类型重复使用。也许这里最有趣的是,没有显式绑定方法参数,也没有需要配置任何内容来识别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 的博客。
注意:在该帖子中,您将看到带有 <aop:advice> 元素的示例。在 Spring 2.0 M3 中,这些元素已被替换为本文中使用的更具体的元素,例如:<aop:before>。