`

spring2.0和AOP

阅读更多

转帖

在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。这很简单:

<bean class="com.interface21.spring2.aop.BirthdayCardSender" id="birthdayCardSenderAspect"></bean>

我们可以依赖注入这个对象,或者应用任何其他的Spring组件模型服务,如果我们愿意的话。

下一步,我们添加AOP配置来告诉Spring,无论何时Spring管理的Person对象的birthday()方法被调用了,就调用BirthdayCardSenderbean的onBirthday()方法。这是通过使用新的<aop:config>和<aop:aspect>标签来实现的。首先,我们必须导入方便的AOP命名空间:</aop:aspect></aop:config>

<?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-2.0.xsd">

接着我们使用新的AOP标签,就像下面这样。这是应用这个切面的完整的配置:

<aop:after>标签适用于after 通知。它指定方法调用onBirthday(),也就是通知。它指明了什么时候调用这个方法——切入点——以一个AspectJ切入点表达式的形式。</aop:after>

切入点表达式是关键。让我们进一步来研究它。

execution(void com.interface21..Person.birthday()) and this(person)

execution()前缀表明我们正在匹配某个方法的执行。也就是说,我们在改变方法的行为。

execution()一句的类型定义了匹配的方法。我们可以在这编写一个表达式来匹配许多类的方法的集合。(实际上,那是一种更普遍更有价值的用法:当通知只匹配一个方法,实际上也没什么意义)最后,我们把被调用的对象绑定到onBirthday()方法的参数。这样缩小了切入点,可以仅仅匹配到一个Person对象的执行——而且提供一种优雅的方式来获得被调用的对象,而不需要任何查找。我们可以使用参数绑定来绑定方法参数,返回类型和异常,以及我们希望的目标。去掉查找的代码应该听起来很熟悉:这是对被通知的类型的依赖有效的注入!在Spring的精神中,它去掉了一个API,无意间却也使单元测试通知方法更简单。

如果切入点表达式匹配一个或多个方法,任何定义在Spring上下文中的Person会被自动代理。那些没有包含对于这个切入点的匹配的类的Bean不会受到影响,匹配切入点的没有被Spring容器实例化的对象也一样(不会受到影响)。这个切面配置是设计用来添加到上面Ioc实例中已有的bean配置,或者在一个额外的XML文件中,或者在同一个文件中。提示一下,Person定义成如下这样:

<bean class="com.interface21.spring2.ioc.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,并添加一个额外的标签来引起自动应用切面。

<aop:aspectj-autoproxy />

<bean id="birthdayCardSenderAspect"
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编译器来编译。请注意我们可以使用aspectpointcut关键词。

我们可以像下面这样,在被用在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<String, Object> queryForMap(String sql, Object... args)
throws DataAccessException

如果是返回这种map的列表的方法,那还会更清楚:

public List<Map<String, Object>> 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结构,比如泛型和变参。

分享到:
评论

相关推荐

    spring 2.0使用AOP实例(基于Annotation的配置方式)

    以上就是Spring 2.0中使用AOP的一个基本实例,基于注解的配置方式使得AOP的使用更加直观和简洁。在实际开发中,我们可以根据需求灵活地定义切面和通知,以实现各种横切关注点的功能。 通过阅读提供的压缩包中的`src...

    spring 2.0使用AOP实例(基于XML的配置方式)

    在IT行业中,Spring框架是Java企业级应用开发的首选,其强大的功能和灵活性深受...通过对这些内容的分析和实践,你可以深入理解Spring 2.0中AOP的概念及其XML配置方式,从而在实际项目中更加灵活地应用面向切面编程。

    SPRING2.0中文文档

    Spring 2.0 是一个里程碑式的版本,它在Java企业级开发中扮演着核心角色,为开发者提供了丰富的功能和灵活性。这份全中文的Spring 2.0技术文档是学习和理解这一版本的重要参考资料,旨在帮助中国开发者更好地掌握...

    spring2.0中文手册及使用指南 chm

    Spring 2.0 是一个非常重要的Java框架,它在企业级应用开发中占据了核心地位,尤其是在基于Java的轻量级应用程序上下文(IoC)和面向切面编程(AOP)方面。本手册和使用指南提供了全面的Spring 2.0相关知识,包括其...

    Spring2.0宝典源代码

    这份源代码集合是配合书籍《Spring2.0宝典》使用的,读者可以通过实际操作代码来理解和掌握Spring 2.0的核心特性和应用场景。光盘中包含的“codes”文件夹,极有可能包含了书中各个章节的示例代码,覆盖了Spring 2.0...

    Spring2.0中文教程

    Spring 2.0是Spring框架的一个重要版本,它在Java企业级应用开发中扮演着核心角色。本教程将深入探讨...文档`spring2.0-reference_final_zh_cn.chm`将详细阐述这些概念和技术,帮助你成为一名熟练的Spring开发者。

    spring2.0 jar包

    Spring 2.0加强了AOP支持,允许开发者定义和执行横切关注点,如日志记录、事务管理等。@Aspect注解用于定义切面,@Before、@After、@Around等用于指定通知类型。此外,Spring还支持自定义注解作为切入点表达式,提高...

    详尽的Spring2.0学习提纲

    Spring 2.0是Java开发中的一个里程碑,它在企业级应用开发中扮演着至关重要的角色,特别是对于依赖注入(IoC)和面向切面编程(AOP)的支持。本学习提纲旨在为初学者提供一份详尽的Spring 2.0学习指南,帮助他们系统...

    spring2.0 中文教程

    9. **测试支持**:Spring 2.0提供了丰富的测试框架支持,包括单元测试和集成测试,如Spring Test和JUnit集成,使得开发者可以编写易于维护的测试代码。 10. **容器增强**:Spring 2.0容器引入了更多高级特性,如...

    Spring2.0

    9. **AOP代理**:Spring 2.0支持JDK动态代理和CGLIB代理,允许用户选择最适合其需求的代理实现。 10. **更多扩展点**:Spring 2.0增加了许多新的API和扩展点,如事件监听、应用上下文、消息源等,为开发者提供了更...

    spring2.0完整开发包

    Spring 2.0是该框架的一个重要版本,它引入了许多关键特性,为开发者提供了更强大的工具和更好的灵活性。 首先,Spring的核心是依赖注入(Dependency Injection,DI),这允许对象之间的依赖关系在运行时被管理,而...

    spring2.0中文参考手册.rar

    1. **AOP(面向切面编程)增强**:Spring 2.0 提供了更强大的面向切面编程支持,允许开发者定义更复杂的切面,包括基于注解的切点表达式和更多的通知类型。这使得代码更加整洁,业务逻辑与系统服务(如事务管理)...

    spring2.0技术手册.pdf 高清版

    2. **AOP(Aspect-Oriented Programming,面向切面编程)**:Spring 2.0在AOP方面进行了扩展,提供了更强大的面向切面的编程能力。开发者可以定义切面,实现如日志、事务管理等跨切面关注点的统一处理,提高了代码的...

    Spring 2.0 源代码

    2. **AOP(面向切面编程)**:Spring 2.0提供了更强大的面向切面编程支持,允许开发者定义切面、通知(advisors)和切点(pointcuts),并将其应用于业务代码,实现如日志、事务管理等功能。切面可以是接口、类或...

    spring2.0学习源码

    Spring作为Java领域的主流框架,其2.0版本是一个重要的里程碑,引入了许多创新特性,提升了框架的灵活性和可扩展性。 在Spring 2.0中,最重要的更新之一是AOP(面向切面编程)的增强。这一版本引入了基于注解的AOP...

    spring2.0技术手册_源代码(全十章)

    其次,Spring 2.0强化了依赖注入(DI)和面向切面编程(AOP)的支持。DI是Spring的核心特性,通过源码,我们可以学习到如何定义bean的配置,如何使用`@Autowired`和`@Qualifier`注解实现自动装配,以及如何自定义...

    SPRING2.0开发详解

    ### SPRING2.0开发详解 #### 一、Spring框架简介 Spring框架是一个开源的Java平台,用于构建企业级应用程序和服务。它最初由Rod Johnson在2004年创建,并随着时间的发展不断壮大和完善。Spring 2.0版本是Spring...

    Spring2.0技术手册_林信良PDF

    总的来说,《Spring2.0技术手册_林信良》是一本全面而深入的教程,涵盖了Spring 2.0的主要特性和用法。通过阅读本书,开发者不仅可以学习到Spring的基础知识,还能了解到如何在实际项目中有效地运用Spring框架,提升...

Global site tag (gtag.js) - Google Analytics