spring2.0和AOP

来源:百度文库 编辑:神马文学网 时间:2024/04/19 18:08:34
在Spring 2.0中最激动人心的增强之一是关于Spring AOP,它变得更加便于使用而且更加强大,主要是通过复杂而成熟的AspectJ语言的支持功能来实现,而同时保留纯的基于代理的Java运行时。
我们一直坚信AOP(面向切面编程)很重要。为什么?因为它提供给我们一种新的思考程序结构的方法,能够解决很多纯OOP无法解决的问题——让我们能够在一个模块中实现某些需求,而不是以发散的方式实现。
为了理解这些好处,让我们考虑一些我们可以在需求中表达但无法直接用纯OO代码实现的情况。企业开发者使用一个通常的词汇表来让他们进行清楚的沟通。比如,像服务层,DAO层,Web层或者Web控制器这样的术语,这不需要什么解释。
许多需求是用这个词汇表中的术语来表达的。比如:
服务层应该是可以处理事务的。 当DAO操作失败时,SQLException或者其他特殊持久化技术的异常应该被翻译,以确保DAO接口不会有漏掉的抽象。 服务层对象不应该调用Web层,因为各层应该只依赖直接处在其下方的层。 由于并发相关操作的失败而导致失败的等幂业务服务可以重试。
虽然这些需求都是现实存在的,并来自于经验,但它们并不能用纯OOP来优雅地解决。为什么?主要有两个原因:
这些来自于我们词汇表的术语有意义,但它们并不是抽象。我们不能使用术语编程;我们需要抽象。 所有这些都是所谓横切关注点的例子。一个横切关注点,在用传统OO方法实现时,会分解成很多类和方法。比如,想象一下在跨DAO层遭遇特殊异常时要使用重试逻辑。这个关注点横切许多DAO方法,而且在传统的方式中会需要实现许多单独的修改。
AOP就是通过对横切关注点进行模块化,并让我们从普通的还可以编程的抽象的词汇表来表达术语,来解决这样问题的技术,这些抽象叫做切入点,我很快会再解释一些关于它们的细节。这种方法带来一些主要好处,比如:
因为减少了剪切粘贴风格的复制而减少代码行数。这在像异常转换和性能监测这样的try/catch/finally习惯用法中尤其有效。 在单个代码模块中捕捉这样需求的能力,提升可追踪能力。 在单个地方修补bug的能力,而不需要重新访问应用程序中许多位置。 确保横切关注点不混淆主要的业务逻辑——随着开发的进展,这很有可能成为危险之处。 开发者和团队之间更好的职责分离。比如,重试功能可以有单个开发者或者团队来编码,而不需要由许多开发者跨多个子系统进行编码。
因此AOP很重要,我们想提供最好的解决方案。
Spring AOP无疑是最广泛使用的AOP技术,归功于以下优点:
采用成本几近为零。 提供正确的切入点,这才称得上是AOP而不仅仅是拦截。 提供一个支持许多使用方式的灵活的框架,可编程也可通过XML。
然而,在Spring 2.0之前,Spring中的AOP有一些缺点:
不写Java代码,只能表达简单的切入点。并没有一种切入点表达语言来以字符串形式,简洁表达复杂的切入点,虽然RegexpMethodPointcutAdvisor允许定义简单正规的基于表达的切入点。 当配置复杂AOP使用场景时,XML配置会变得很复杂。泛型元素被用来配置AOP类;虽然这对一致性来说很棒,对切面和类提供DI和其他服务,但它没有一个专门的配置方法来得简洁。 Spring AOP不适合通知细粒度的对象——对象需要由Spring管理或者通过编程被代理。 基于代理的方法的性能负载在少数案例中成为问题。 因为Spring AOP分离了代理和目标(被修饰或者被通知的对象),如果某个目标方法调用了目标上的方法,就不会使用到代理,意味着AOP通知并不适用。AOP使用基于代理的方法的正反面影响超出了本文的范围:有一些积极的因素(比如能够对同一个类的不同实例应用不同的通知),但主要还是消极的。
为了在Spring 2.0中增强这个重要领域,我们希望在它的优势上构建,同时解决缺点。
目标
最先的两个缺点也是最显著的。它们都跟切入点相关。后面的三个缺点在Spring用户的正常使用中很少发生,如果它们证明是的确有问题的,我们建议使用AspectJ。(就像你会看到的,这是Spring AOP直接的进步。)
XML配置扩展解决了关键的挑战之一。因为我们想要保持Spring模块的设计,我们过去不能在Spring DTD中提供特定于AOP的标签——因此在这种情况下需要依赖可以详细一点的通用配置。随着Spring 2.0的出现,这样的问题没有了,因为XML schema并不像DTD,它允许扩展。我们可以提供一个AOP命名空间,看起来能让Ioc容器识别AOP结构,但不会影响模块化。
AOP术语101:理解切入点和通知
让我们简要地修正一下某些AOP术语。如果你使用过AOP这些概念,可能对你来说很熟悉——这些概念是相同的,仅仅有一点不同,即更加优雅和强大的表达方式。
切入点是匹配规则。它在程序执行中确定应该应用某个切面的点的集合。这些点叫做连接点。在应用程序运行时,连接点随时会有,比如对象的实例化和方法的调用。在Spring AOP(所有版本)的案例中,唯一支持的连接点是公有方法的执行。
通知是可以被切面应用到连接点的行为。通知能在连接点之前或之后应用。通知的所有类型包括:
Before advice:在连接点之前调用的通知。比如,记录方法调用即将发生的日志。 After returning adive:如果在连接点的方法正常返回时调用的通知。 AfterThrowing advice(在Spring1.x中叫做Throws通知):如果连接点的方法抛出一个特殊的异常时调用的通知。 After advice:在连接点之后调用的通知,无论结果是什么。特别像Java中的finally。 Around advice:能够完全控制是否执行连接点的通知。比如,用来在事务中封装某个方法调用,或者记录方法的执行时间。
切面是结合切入点和通知成一个模块方案,解决特殊的横切问题。
如果这有点抽象,请不要担心:代码示例会很快解释清楚的。
对在Spring 2.0和AspectJ的环境中关于AOP基础的更深讨论,请参考Adrian在InfoQ上很棒的文章,"Simplifying Enterprise Applications with Spring 2.0 and AspectJ."为什么会是AspectJ切入点表达式?
迄今为止,我们讨论过的概念都是基本的AOP概念,对于Spring AOP或者AspectJ而且这并不特别,在Spring1.x中已经是存在的。那么为什么我们选择在Spring 2.0中采用AspectJ呢?
如果我们需要一种切入点表达语言,那么选择就会很简单。AspectJ有个思路很好,严格定义和充足文档的切入点语言。它最近实现了一个当在Java 5上运行时,能对采用Java 5语法的编码全面检查。它不仅有很棒的参考材料,而且很多书籍和文章都对它进行了介绍。
我们不相信重新发明的轮子,而且定义我们自己的切入点表达语言是不合理的。进一步而言,自从AspectWerkz在2005年早期和冰岛AspectJ项目之后,很明显AspectJ是除了Spring 2.0之外唯一一个主流的AOP技术。因此关键的合并既是一种考虑也是一种技术优势。
新的XML语法
新的AOP命名空间允许Spring XML配置指定AspectJ切入点表达式,通过由切入点匹配的方法将通知指向任何Spring bean。
考虑一下我们上面看到的Person类。它有个age属性,以及一个增加age的birthday方法:
public void birthday() {
++age;
}
我们假设有这样的需求,任何时候调用birthday方法,我们都应该发送一张生日贺卡给过生日的人。这是一个经典的横切需求:它并不是我们主要的业务逻辑部分,而是个单独的关注点。理想的情况下,我们希望能够将那个功能模块化,而不影响Person对象。
让我们考虑一下通知。实际上,邮递发送一张生日贺卡,或者甚至发送一张电子贺卡,当然会是这个方法的主要工作。然而,由于这篇文章中我们的兴趣在于触发的基础结构,而不是发送生日贺卡的机制。因此我们就简单使用控制台输出。这个方法需要访问到过生日的这个Person,而且无论何时brithday方法被调用,它都应该被调用。下面是简单的通知实现:
public class BirthdayCardSender {
public void onBirthday(Person person) {
System.out.println("I will send a birthday card to " +
person.getName() + "; he has just turned "
person.getAge());
}
}
本质上我们在Person上想要一种Observer机制,但并不把Person修改成可被观察的。请同时注意在这个例子中,BirthdayCardSender对象被用作一个切面,但不需要实现任何特定于框架的接口。这样就使得使用已经存在的类作为切面成为可能,而且扩展Spring的非入侵编程模型到潜在的切面以及普通的类。
通过Spring 2.0,我们可以像下面这样把BirthdayCardSender当作一个切面来使用。首先,我们把BirthdayCardSender类定义成一个bean。这很简单:
我们可以依赖注入这个对象,或者应用任何其他的Spring组件模型服务,如果我们愿意的话。
下一步,我们添加AOP配置来告诉Spring,无论何时Spring管理的Person对象的birthday()方法被调用了,就调用BirthdayCardSenderbean的onBirthday()方法。这是通过使用新的和标签来实现的。首先,我们必须导入方便的AOP命名空间:

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-2.0.xsd">
接着我们使用新的AOP标签,就像下面这样。这是应用这个切面的完整的配置:
标签适用于after 通知。它指定方法调用onBirthday(),也就是通知。它指明了什么时候调用这个方法——切入点——以一个AspectJ切入点表达式的形式。
切入点表达式是关键。让我们进一步来研究它。
execution(void com.interface21..Person.birthday()) and this(person)
execution()前缀表明我们正在匹配某个方法的执行。也就是说,我们在改变方法的行为。
execution()一句的类型定义了匹配的方法。我们可以在这编写一个表达式来匹配许多类的方法的集合。(实际上,那是一种更普遍更有价值的用法:当通知只匹配一个方法,实际上也没什么意义)最后,我们把被调用的对象绑定到onBirthday()方法的参数。这样缩小了切入点,可以仅仅匹配到一个Person对象的执行——而且提供一种优雅的方式来获得被调用的对象,而不需要任何查找。我们可以使用参数绑定来绑定方法参数,返回类型和异常,以及我们希望的目标。去掉查找的代码应该听起来很熟悉:这是对被通知的类型的依赖有效的注入!在Spring的精神中,它去掉了一个API,无意间却也使单元测试通知方法更简单。
如果切入点表达式匹配一个或多个方法,任何定义在Spring上下文中的Person会被自动代理。那些没有包含对于这个切入点的匹配的类的Bean不会受到影响,匹配切入点的没有被Spring容器实例化的对象也一样(不会受到影响)。这个切面配置是设计用来添加到上面Ioc实例中已有的bean配置,或者在一个额外的XML文件中,或者在同一个文件中。提示一下,Person定义成如下这样:
p:name="Tony"
p:age="53"
p:house-ref="number10"
/>
不需要任何配置来使Person或者其他的bean定义符合通知。当我们调用某个配置在Spring上下文中的Person对象的birthday()方法时,我们看到下面这样的输出:
I will send a birthday card to Tony; he has just turned 54@AspectJ语法
通知总是包含在Java方法中。但到目前为止,我们看到的是定义在Spring XML中的切入点。
AspectJ 5也提供一种完美的解决方案,来定义切面,通知包含在方法中以及切入点在Java 5的注解中。这种切入点表达语言和AspectJ自己的语法一样,而且语义相同。但以这种风格——叫做@AspectJ模型——切面能使用javac进行编译。
通过@AspectJ语法,特定于框架的注解存在于切面中,而不是业务逻辑中。没有必要把注解引入到业务逻辑中来驱动切面。
这种注解驱动的编程模型最先由AspectWerkz提出,在2005年早期它合并入AspectJ项目。在AspectJ中,@AspectJ 切面被加载时织入(load time weaving)运用:类加载器钩子修改正在加载的类的字节码,来应用这些切面。AspectJ编译器也明白这些切面,因此有个实现策略的选择。
Spring 2.0为@AspectJ切面提供一种额外的选择:Spring可以使用它的基于代理的AOP运行时来将这样的切面应用到Spring beans。
让我们看一下,我们早先例子中同样的功能是如何使用这种风格实现的。
这个切面类包含和BirthdayCardSender类相同的通知方法体,但使用org.aspectj.lang.annotation.Aspect注解来把它识别为一个切面。在同一个包中更多的注解定义了通知方法。
@Aspect
public class AnnotatedBirthdayCardSender {
@After("execution(void com.interface21..Person.birthday()) and this(person)")
public void onBirthday(Person person) {
System.out.println("I will send a birthday card to " +
person.getName() + "; he has just turned " +
person.getAge());
}
}
这将切入点和通知方法结合在一起,使切面成为一个完整的模块。@AspectJ 切面,就像所有的AspectJ 切面,可以包含任意数目的切入点和通知方法。
在Spring XML中,我们再次把这个定义成一个bean,并添加一个额外的标签来引起自动应用切面。

class="com.interface21.spring2.aop.AnnotatedBirthdayCardSender" />
这个自动代理aspectj标签告诉Spring自动识别@AspectJ切面,并把它们应用到任何与它们的切入点相匹配的相同上下文中bean中。
在XML和@AspectJ风格之间选择
哪个方法更好呢——XML还是注解?首先,因为注解存在于切面中,而不是核心业务逻辑,所以转换的成本并不高。决定通常取决于使切入点描述完全从Java代码具体出来是否有意义。
如果你的切面是特定领域的,就考虑注解风格:也就是说,切入点和通知是紧密相联的,而且通知并不普通,不可能在不同的场景中重复使用。比如说,发送生日贺卡对于注解风格是个很好的选择 ,因为它是特定于一个特殊应用类(Person)的。然而,一个性能监测切面可能在不同的应用中有不同的使用,通知方法最好从切入点解耦出来,切入点存在于XML配置中更自然一些。
使用XML如果:
你不能使用Java 5,而且没有其他选择。Spring 2.0的AOP增强,除了能处理@AspectJ语法,也能工作在Java1.3,1.4以及Java 5上,虽然你不能编写切入点表达式匹配注解或者其他的Java 5结构。 你可能想要在不同的上下文中使用通知。 你想要使用已有代码作为通知,而且不想引入AspectJ注解:比如,引入一个Observer行为调用任意POJO的方法。
编程用法
你也可以以编程的方式创建AOP代理,像下面这样使用@AspectJ 切面:
Person tony = new Person();
tony.setName("Tony");
tony.setAge(53);
AspectJProxyFactory ajpf = new AspectJProxyFactory(tony);
ajpf.addAspect(new AnnotatedBirthdayCardSender());
Person proxy = ajpf.getProxy();
AnnotatedBirthdayCardSender会被自动识别为一个@AspectJ 切面,而且代理会使用它定义的行为来修饰目标。这种风格并不需要Spring Ioc容器。
编程式的代理创建在编写基础结构代码和测试时比较有用,但在通常的业务应用中并不经常使用。
通过AspectJ切入点你能做的很棒的事情
到目前为止我们看到的仅仅是表面的东西。
让我们来看一些AspectJ切入点表达式更高级的能力,在Spring XML中,以@AspectJ风格(或者当然,AspectJ语言本身):
参数,目标,异常以及返回值绑定。 从类型安全中受益,其中方法签名中的类型是在切入点中指定。 用切入点表达式的组合来构建复杂的表达式。 切入点表达式的重复使用。
我们已经见过了目标绑定。让我们举一个参数绑定的例子:
@Aspect
public class ParameterCaptureAspect {
@Before("execution(* *.*(String, ..)) && args(s)")
public void logStringArg(String s) {
System.out.println("String arg was '" + s + "'");
}
}
args()子句绑定到被匹配的方法中的参数,缩小范围到具有第一个参数类型是String的方法。因为切入点绑定了第一个参数,必须是String类型的参数,在通知方法中做强制转换就没有必要了。
这种机制很自然地提供了类型安全。这个切入点的通知目标永远不可能被错误调用,通过错误类型的参数,或者没有匹配的参数。
为了给一个这种机制的优越性的例子,下面是在Spring1.x MethodBefore通知中看起来的样子。因为通过一个AOP Alliance MethodInterceptor(在Spring1.x中最通用的接口),我们需要遍历一个参数数组来找出我们要寻找的参数:
public class ParameterCaptureInterceptor implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
if (args.length >= 1 && method.getParameterTypes()[0] == String.class) {
String s = (String) args[0];
System.out.println("String arg was '" + s + "'");
}
}
}
我们可以使用一个Spring AOP切入点来去除MethodBefore通知中的保护,但正如前面提到的,这样可能需要编写Java代码。在拦截器中有个保护比使用切入点要慢,因为它不允许AOP运行时优化出永远不能调用的通知。
在这里我们能够看到从拦截中去除AOP是多么的正确,以及为什么更简单和更强大。EJB3.0拦截显然比Spring的第一代AOP功能更加糟糕,因为它缺少一个真实的切入点机制,这意味着ClassCastException和ArrayIndexOutofBoundsException很可能是风险。同样有必要使用Around通知(拦截器)而不是Before通知,因为EJB3.0没有提供特殊的通知类型。而且,需要提供一个InvocationContext对象使得单元测试通知方法困难得多。
AspectJ切入点表达式语言的强大不仅仅是关于复杂的结构。它也在避免潜在的错误和应用程序更加智能化方面扮演重要的角色。它还能通过在通知方法中移除所需的保护代码,显著地减少所需代码的数量。
组合和重用是真实语言的特征。AspectJ的主要目标就是把它们提供给切入点表达式。
让我们看一下实际中切入点的重用。
@AspectJ语法(就像Spring 2.0的AOP XML格式)允许我们定义有名字的切入点。在@AspectJ语法,我们在一个void方法上使用@Pointcut注解,就像下面这样:
@Pointcut("execution(public !void get*())")
public void getter() {}
@Pointcut注解允许我们定义切入点表达式,而且必要时,还有被切入点绑定的参数的个数和类型。方法名被用作切入点的名称。
上面的切入点匹配JavaBean的getter方法。请注意这里匹配的优势:我们不仅仅处理通配符,而且处理语言语义。这方法会把getter方法看作任何名称以get开头的方法。这种切入点要健壮得多,因为它断言getter是公有的,有一个非void的返回(!void)以及没有参数(通过对参数的圆括号来指明)。
让我们添加一种切入点来匹配返回int的方法:
@Pointcut("execution(public int *())")
public void methodReturningInt() {}
现在我们可以根据这些切入点来表达通知。我们第一个例子简单引用了我们的第一个有名称的切入点,“getter”:
@After("getter()")
public void getterCalled(JoinPoint jp) {
System.out.println("Method " + jp.getSignature().getName() +
" is a getter");
}
然而,现在事情变得更有趣了。我们通过把两个切入点加到一个表达式,对一个返回in的getter应用通知:
@After("getter() and methodReturningInt()")
public void getterCalledThatReturnsInt(JoinPoint jp) {
System.out.println("ANDing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter that also returns int");
}
ANDing意味着两个切入点都必须应用。ORing意味着必须应用其中一个切入点。我们可以构建我们需要的任何复杂程度的表达式。
在下面这个完整的切面中演示了ANDing和ORing:
@Aspect public class PointcutReuse {
@Pointcut("execution(public !void get*())" )
public void getter() {}
@Pointcut("execution(public int *())" )
public void methodReturningInt() {}
@Pointcut("execution(public void *(..))" )
public void voidMethod() {}
@Pointcut("execution(public * *())" )
public void takesNoArgs() {}
@After("methodReturningInt()" )
public void returnedInt(JoinPoint jp) {
System.out .println("Method " + jp.getSignature().getName() +
" returned int" );
}
@After("getter()")
public void getterCalled(JoinPoint jp) {
System.out .println("Method " + jp.getSignature().getName() +
" is a getter" );
}
@After("getter() and methodReturningInt()" )
public void getterCalledThatReturnsInt(JoinPoint jp) {
System.out.println("ANDing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter that also returns int");
}
@After("getter() or voidMethod()" )
public void getterOrVoidMethodCalled(JoinPoint jp) {
System.out .println("ORing of pointcuts: Method " +
jp.getSignature().getName() +
" is a getter OR is void" );
}
}
这会产生下面的输出,显示切入点表达式的ORing和ANDing:
Method getName is a getter
ORing of pointcuts: Method getName is a getter OR is void
ORing of pointcuts: Method birthday is a getter OR is void
Method getName is a getter
ORing of pointcuts: Method getName is a getter OR is void
Method getAge returned int
Method getAge is a getter
ANDing of pointcuts: Method getAge is a getter that also returns int
ORing of pointcuts: Method getAge is a getter OR is void
I will send a birthday card to Tony; he has just turned 54
切入点组合同样可以在Spring AOP XML中完成。在那种情况下,使用“and”和“or”替代“&&”和“||”操作符来避免使用XML属性值的问题。
为高级用户重用AspectJ库切面
重用AspectJ语言编写的AspectJ切入点表达式,并编译成一个JAR文件是可能的。如果你使用Eclipse,你可以使用AJDT插件开发这样的切面。或者,如果你已经在使用AspectJ,你就已经拥有这样的切面而且想要重用它们。
作为演示,我会重写我们早先例子的一部分,把切入点放在一个AspectJ切面中:
public aspect LibraryAspect {
pointcut getter() :
execution(public !void get*());
...
}
这个切面用Aspect语言编写,因此需要由AspectJ ajc编译器来编译。请注意我们可以使用aspect和pointcut关键词。
我们可以像下面这样,在被用在Spring中的@AspectJ 切面中,引用这个切面。请注意我们使用了这个切面的FQN,它通常会被打包在某个位于类路径的JAR文件中:
@Aspect
public class PointcutReuse {
@After("mycompany.mypackage.LibraryAspect.getter()")
public void getterCalled(JoinPoint jp) {
System.out.println("Method " + jp.getSignature().getName() +
" is a getter");
}
这个类,在另一方面,可以使用javac进行编译并由Spring应用。
你也可以在Spring XML中引用AspectJ 切面。正如你能看到的,Spring 2.0 AOP和AspectJ集成得非常紧密,虽然Spring AOP提供了一个完整的运行时,而不需要使用AspectJ编译器或编织器。
如果你有非常复杂的切入点表达式,那使用AspectJ库的切面是最好的实践,因为那样Aspect语言和工具支持是非常引人注目的。
最佳实践
那么这对Spring用户意味着什么?
很希望你同意AOP解决了企业软件中很重要的问题,而且你意识到AspectJ编程模型相比较AOP或者任何可用于拦截的选择,是多么的强大和优雅。
你没有必要把你已有的Spring MethodInterceptors或者其他的通知实现迁移到新的编程模型:它们仍然能工作的很好。但再往后,应该采用新的编程模型,它会更加引人注目。
如果你在使用ProxyFactoryBean或者TransactionProxyFactoryBean来一次一个地配置代理,你会发现自动代理(它在Spring 2.0中变得更容易更自然)能够显著减少Spring配置的工作量,以及在团队成员间更好的分工。运行时看起来怎样?
虽然用法模型看起来不同,但记住概念很重要——连接点、切入点和通知——就跟Spring自2003年已经实现的Spring AOP和AOP Alliance API中的一模一样。Spring AOP对这些概念一直有对应的接口。
也许更令人惊讶的是,表面以下的实现实际上更加相同。新的编程模型,支持AspectJ结构,构建于已有的Spring AOP运行时之上。Spring AOP一直非常灵活,因此这不需要什么显著的改变。(org.springframework.aop.framework.Advised接口仍然能够用来查询和修改AOP代理的状态,就像Spring1.x中那样。)
这意味着你能够使用AspectJ风格的切面,混合和匹配AOP Alliance和Spring AOP 切面:如果你想要支持已有的切面这尤其重要。
让我们增加编程式创建代理的例子来演示这个。我们会增加一个传统的Spring AOP风格的MethodInterceptor:
Person tony = new Person();
tony.setName("Tony");
tony.setAge(53);
AspectJProxyFactory ajpf = new AspectJProxyFactory(tony);
ajpf.addAspect(new AnnotatedBirthdayCardSender());
Person proxy = ajpf.getProxy();
ajpf.addAdvice(new MethodInterceptor() {
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("MethodInterceptor: Call to " + mi.getMethod());
return mi.proceed();
}
});
这会产生下面的输出,来自于MethodInterceptor的输出(没有切入点,匹配所有的方法调用)和来自于@AspectJ风格编写的BirthdayCardSender的输出混杂在一起。
MethodInterceptor: Call to public void
com.interface21.spring2.ioc.Person.birthday()
MethodInterceptor: Call to public java.lang.String
com.interface21.spring2.ioc.Person.getName()
MethodInterceptor: Call to public int
com.interface21.spring2.ioc.Person.getAge()
I will send a birthday card to Tony; he has just turned 54向着AOP统一
Spring 2.0给AOP的世界带来一种新的受欢迎的统一。第一次,切面的实现独立于它的部署模型。我们看到的每一个@AspectJ例子都能使用AspectJ编译器编译,或者使用AspectJ加载时织入来使用,也能被Spring应用。在Spring的精神中,我们有一个能够跨越不同运行时场景的编程模型。
而且如果你想要采用AspectJ本身,这会有正常的进步,因为Spring把AspectJ切入点表达式概念带给了更广泛的受众。
你什么时候应该使用AspectJ呢?下面是一些指示:
你想要通知细粒度的对象,它们可能不会被Spring容器实例化。 除了公有方法的执行,你想要通知连接点,比如字段访问或者对象创建。 你想要以透明的方式通知自调用。 当一个对象需要被通知会被多次调用,而且不接受任何代理性能负载。(在这个基础上做决定前要小心基准:Spring AOP代理的负载在通常使用中是无法觉察到的。) 你想要使用AspectJ的能力来声明由编译器标记的警告或者错误。这对架构增强尤其有用。
没有必要有什么或者的选择。同时使用AspectJ和Spring AOP是可能的:它们并不冲突。
Java 5
Spring 2.0保持向后对Java1.3和1.4的兼容。然而,Java 5带来越来越多的新特性。
其中一些,比如已经讨论过的类型推断,可以随意使用。而其他的需要自己来选择。让我们快速预览其中的一些。
新的API
大量的新的API在核心功能上提供Java 5的功能,这些核心功能继续运行在Java的早些版本上。
尤其是:
SimpleJdbcTemplate:和熟悉的JdbcTemplate类似的新类,这使得JDBC使用更加简单。 AspectJProxyFactory:和ProxyFactory类似的新类,设计用来使用@AspectJ 切面以编程方式创建代理。
随着时间延续,这样的类的数目会越来越多。
SimpleJdbcTemplate是用来例证的。让我们看一下在调用一个计算总数功能时的效果。在Java 5之前使用JdbcTemplate,我们需要在一个数组中封装绑定参数,就像下面这样:
jdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
new Object[] { new Integer(13), "GBP" }
);
如果我们使用Java 5,自动装箱去除了一点困扰,因为我们不再需要原始封装器类型。这仅仅来自于语言特性,而不需要Spring提供任何新的东西:
jdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
new Object[] { 13, "GBP" }
);
然而,通过采用Java 5我们能够完全不再需要对象数组。下面的例子显示了SimpleJdbcTemplate是如何使用变参来绑定变量的,意味着开发者可以提供任意数目的变参,而不需要数组:
simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
13, "GBP"
);
作为增加的一个好处,我们不再需要区分带有绑定和没绑定参数的情况。虽然这需要JdbcTemplate上的两个方法,来避免需要给执行SQL的重载方法传入一个空的Object数组,但通过SimpleJdbcTemplate,框架代码可以检查变参的长度。这样下面的例子调用同一个方法:
simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT");
还有更显著的好处。泛型使得签名更加清晰,并去除强制转换。比如,JdbcTemplate的queryForMap()方法返回一个ResultSet中的从列名到列值的Map。当它是SimpleJdbcTemplate上方法签名的显式部分时,这就变得清楚得多:
public Map queryForMap(String sql, Object... args)
throws DataAccessException
如果是返回这种map的列表的方法,那还会更清楚:
public List> queryForList(String sql, Object ... args)
throws DataAccessException
增加SimpleJdbcTemplate的目标之一是,提供那些最经常使用的方法。JdbcTemplate是Spring中最大的类之一,有很多的方法,其中一些是用于非常深奥的用途。在这种高级的情况下,根据全部问题的复杂性,语言语法糖就可能不再重要。对于这样的案例,SimpleJdbcTemplate封装了一个JdbcTemplate实例,通过getJdbcOperations()方法可以访问到。
为了支持扩展DAO支持类的用法模型,Spring 2.0提供了SimpleJdbcDaoSupport类,提供一个预先配置的JdbcTemplate。SimpleJdbcTemplate也象JdbcTemplate一样很易于实例化或者直接注入,通过仅仅提供一个javax.sql.Datasource实现——这是Spring对所有关系型数据库访问的支持的开始点。就像JdbcTemplate,SimpleJdbcTemplate可以当作一个类库来使用,而无需使用Spring的其他部分。
注解
我们首先在Spring1.2中引入注解,作为一个可选的特性,而且我们逐渐增加更多。
我已经提到过来自于AspectJ的对AOP的注解的使用。它提供一种在单个代码模块中表达切入点和通知的优雅的方式。
Spring还提供许多它自己的某些领域的注解,比如事务管理。下面这些在1.2中引入:
@Transactional:标记一个类,接口或者方法为可事务的。 不同的注解,包括在org.springframework.jmx.export.annotation包中的@ManagedResource,识别操作、属性和对象来到处以便JMX管理。
下面是2.0中最重要的新的注解:
@Configurable:表明某个特殊的对象应该在构建后使用Spring依赖注入,虽然它不是由Spring实例化的。驱动一个AspectJ DI切面,这在本文的第二部分中描述。 @Required:指明所需的某个JavaBean 的setter方法。为了采用这个增强,在你的应用上下文中定义RequiredAnnotationBeanPostProcessor。在Spring的非入侵编程模型精神中,以及与已有代码一起共同工作的能力,Spring也能加强其他注解的使用来指明某个所需的属性,通过RequiredAnnotationBeanPostProcessor的配置。 @Repository:把一个DAO对象认作Repository模式(在领域驱动设计术语中)。Spring 2.0提供了一个切面(PersistenceExceptionTranslationAdvisor),能自动把来自于用@Repository注解的对象的特定技术异常转换成Spring的普通的DataAccessException。
对于JPA测试,Spring的集成测试创建了一些超类,比如新的AbstractJpaTest和泛型超类AbstractAnnotationAwareTransactionalTests,现在提供对于注解的支持,比如@Repeat(引起重复测试)和@ExceptedException(指明这个测试应该抛出一个特殊的异常,如果没有抛出就失败)。不幸的是,由于JUnit3的设计基于具体的继承,这些有用的注解对于使用Spring的其他测试不再有用。随着JUnit4的广泛使用,我们会提供我们集成测试的一个版本,它应该能够对其他的用户开放这个功能。
如果你想要解释自己的注解该怎么办呢?当然在这种情况下,Spring的许多扩展钩子会帮上忙。比如说,你能编写一个BeanPostProcessor来使用给定的注解来区别方法,就跟RequiredAnnotationBeanPostProcessor一样的工作方式。用于即将发布的WebLogic10的Pitchfork项目,使用这些扩展点在Spring之上实现了JSR-250注解和EJB3.0拦截注解。
同样值得注意的是,Spring 2.0中提供的AspectJ切入点表达式语言有非常强大的注解匹配。很容易编写切入点来匹配注解。比如,下面的切入点表达式会匹配任何用Spring框架中的注解进行注解的方法:
execution(@(org.springframework..*) * *(..))
下面的表达式会匹配任何用@Transaction注解的类:
@within(org.springframework.transaction.annotation.Transactional)
AspectJ 5是AspectJ语言的主要扩展,并且在保持随着基础语言的进化而更新方面给人印象深刻,切入点表达式也能够匹配其他的Java 5结构,比如泛型和变参。