用Spring 2.0和AspectJ简化企业应用程序

作者 Adrian Colyer译者 俞黎敏 发布于 2007年10月3日 上午4时10分

社区
Java
主题
AOP

Spring:简单强大

Spring的目标是使企业应用程序开发尽可能地简单和高效。这一理论的实例可以从Spring的JDBC、ORM、JMX、依赖注入等方法,以及企业应用程序开发的其他许多重要领域中见到。Spring还区分了使事情简单化和过分单纯化之间的差异。最不可思议的是同时提供了简单化和强大的功能。企业应用程序中复杂性的一个根源来自影响应用程序多个部分的特性和需求的实现。相关于这些特性的代码最终散布在应用程序代码中,使得它更难以添加、维护和理解。Spring 2.0使得以模块化的方式实现这些特性变得更加简单,极大地简化了整体的应用程序代码,并且有时使得在实现没有它的情况下十分痛苦的编码需求变得易如反掌。

事务管理是影响应用程序多个部分的一个特性实例:一般来说所有的操作都在服务层。在Spring中解决这种需求的方式是通过使用AOP。Spring 2.0在它对AOP的支持中提供了一个明显的简化,同时还提供了比Spring 1.x所提供的更多富有表现力的功能。这些改善之处主要来自两个主要的领域:通过使用XML schema极大地简化了配置,以及与AspectJ的整合带来了更好的富有表现力的功能和更简单的advice模型。

在本文中,我将首先介绍在典型的企业应用程序中,Spring AOP和AspectJ适用于什么地方,之后介绍在2.0中新的Spring AOP支持。大部分篇幅用来讲解企业应用程序中AOP的采用路线图,通过大量可以只用AOP实现的特性实例,但是用任何其他的方法进行实现都将非常困难。

简化企业应用程序

典型的企业应用程序——比如一个Web应用程序——由许多层构成。一个包含视图和控制器的Web层,一个表现系统业务接口的服务层,一个负责保存和获取持久化领域对象的数据访问或者存储层,与所有这些层共事的,还有一个核心业务逻辑所在的领域模型。

Web层、服务层和数据访问层有着许多相同的重要特征:它们应该尽可能地瘦,它们不应该包含业务逻辑,并且它们一般通过Spring组装在一起。在这些层中,Spring负责创建对象和配置。领域模型则有些不同:领域对象由程序员利用新的操作器创建(或者利用从数据库中获取的ORM工具进行扩建)。领域对象有许多唯一的实例,它们(可以)有丰富的行为。

服务层可以包含特定于应用程序用例的逻辑,但是所有领域相关的逻辑都应该放在领域模型本身里面。

服务层一般是使用声明式企业服务(例如事务)的地方。声明式的企业服务,例如事务和安全是影响应用程序中多个点的很好的需求实例。事实上,即使你想让(比如)事务划分只在单个地方,将这项功能与你的应用程序逻辑分开,使得代码更加简单,避免不必要的耦合,这也仍然很好。

由于服务对象是Spring管理的bean,Spring AOP天生适合于在这个层中处理需求。事实上,任何人在使用Spring的声明式事务支持时,就已经是在使用Spring AOP了,无论他们是否意识到这一点。Spring AOP很成熟,得到了广泛的应用。它非常适合于Web、服务和数据访问层中受Spring管理的bean,只要你的需求可以通过advice bean方法执行得到处理(且这些层的许多用例都属于这一类)。

当提到影响你领域模型中多个点的需求时,你应用程序的最重要部分——Spring AOP——的帮助就小多了。你可以编程式地使用Spring AOP,但是这样会很难使用,并且还要你自己负责创建代理和管理同一性。AspectJ天生适合于实现影响领域对象的特性。AspectJ方面不需要任何特殊的代理创建,并且可以很恰当地通知运行时在你的应用程序代码中,或者通过你可能使用的框架所创建的对象。当你想要模块化影响你应用程序的所有不同层的行为,或者模块化性能以任何方式感知的行为时,AspectJ也是一种非常好的解决方案。

因此,我们最想要的是一种一致的Spring AOP和AspectJ方法,以便我们可以很容易地一起使用这两种工具,以便如果需求发生变化,你用(比如)Spring AOP开发的能力就可以转移到AspectJ上。无论我们正在使用哪种组合,我们仍然喜欢依赖注入和Spring所提供的配置的所有益处。Spring 2.0中新的AOP支持正好带来了这一点。

底层的技术:AspectJ和Spring AOP简介

AOP使得实现在应用程序中影响多个点的特性变得更加简单。这主要因为AOP提供了对名为通知(advice)的这个东西的支持。通知不同于必须显式调用的方法,每当发生匹配的触发事件时,它就自动地执行。继续事务主题,触发事件是服务层中一个方法的执行,并且通知逻辑提供所需的事务划分。用AOP的话来说,触发事件被称作连接点(join point),而切入点表达式(pointcut expression)则用来选择通知要在那里运行的连接点。这个简单的倒置意味着不用将调用散布到你全部应用程序代码中的事务管理器,而是只要编写一个切入点表达式,定义你需要事务管理器在什么地方完成某事的所有点,并将它与适当的通知关联起来。AspectJ和Spring AOP提供对这个模型的支持,事实上,它们有着完全相同的切入点表达语言。

在接下来的讨论中,注意Spring和AspectJ保持为独立的工程,这很重要。Spring只使用反射和由AspectJ 5作为一个库所暴露的工具API。Spring 2.0仍然是一个运行时基于代理的框架,且AspectJ织入器(weaver)不用于Spring方面。

我相信你们中大多数人都知道,AspectJ是一种包含完整编译器的语言(构建为Eclipse JDT Java编译器的一个扩展),对离线或者在运行时将(与)二进制的class文件(链接的方面)作为类织入的支持,被加载到了虚拟机中。AspectJ的最新发布版本是AspectJ 5,它为Java 5语言提供完整的支持。

AspectJ 5也引入了方面声明的第二种风格,我们称之为“@AspectJ”,它允许你将一个方面编写为一个包含注解的Java类。这种方面可以通过一般的Java 5编译器进行编译。例如,传统的“HelloWorld”方面在AspectJ编程语言中看起来像这样:

public aspect HelloFromAspectJ {
 

pointcut mainMethod() : execution(* main(..));

after() returning : mainMethod() {
System.out.println("Hello from AspectJ!);
}

}

与传统的HelloWorld类共同编译这个方面,当你运行应用程序时,会看到这样的输出:

Hello World!
Hello from AspectJ!

我们可以用@Aspect风格编写相同的方面如下:

@Aspect
public class HelloFromAspectJ {

@Pointcut("execution(* main(..))")
public void mainMethod() {}

@AfterReturning("mainMethod()")
public void sayHello() {
System.out.println("Hello from AspectJ!");
}

}

就本文而言,AspectJ 5中另一项重要的新特性是一个完全AspectJ感知的反射API(你可以在运行时为它的通知和切入点成员等等请求一个方面),和让第三方使用AspectJ的切入点解析和匹配引擎的工具API。这些API的第一大用户,就像你很快会见到的,是Spring AOP。

与AspectJ相反,Spring AOP是一个基于代理的运行时框架。在使用Spring AOP时,并没有特殊的工具或者构建需求,因而Spring AOP是一种很容易开始的方法。作为一种基于代理的框架,它既有优点也有缺点。除了已经提到过的容易使用的因素之外,基于代理的框架还能够独立地通知相同类型的不同实例。将这一点与AspectJ基于类型的语义相比,在这里,类型的每一个实例都有着相同的行为。对于像Spring这样的框架而言,能够独立地通知独立的对象(Spring beans)是一个重要的必要条件。另一方面,Spring AOP只支持AspectJ功能的一个子集:有可能在Spring beans中通知方法的执行,但是其他没什么。

基于代理的框架一般会有同一性的问题:有两个对象(代理和目标)都表示应用程序中的同一个实体。必须始终小心地传递适当的引用,确保给实例化过的任何新的目标对象创建代理。Spring AOP通过管理bean实例化(以便代理可以被透明地创建)和通过依赖注入(以便Spring始终可以注入适当的引用),巧妙地解决了这些问题。

Spring 2.0中新的AOP支持

2.0中的Spring AOP可以完全向后与Spring 1.x应用程序和配置兼容。它还提供了比Spring 1.x更简单且更强大的配置。新的AOP支持是基于schema的,因此在你的Spring beans配置文件中将需要相关的命名空间和schema定位属性。它看起来像这样:

<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">

...

</beans>

与使用DTD时所需要的更简单的xml配置相比,那么目前为止我们还没有超越——但这是标准的xml配置,并且可以在你的IDE中的一个模板里创建,并且只在每当你需要创建一个Spring配置时才被重用。当我们开始将一些内容添加到配置中时,你会领略到这一好处。

Spring 2.0默认使用AspectJ 切入点语言(受执行连接点种类的限制)。如果它看到一个AspectJ 切入点表达式,它就调出AspectJ对它进行解析和匹配。这意味着你用Spring AOP编写的任何切入点表达式都将以与AspectJ完全相同的方式进行工作。此外,Spring实际上能理解@AspectJ方面,因此有可能共用Spring和AspectJ之间完整的方面定义。激活这项功能很容易,只要将<aop:aspectj-autoproxy>元素包括在你的配置中。如果AspectJ自动代理以这种方式激活,那么在你的应用程序上下文中定义的、包含@AspectJ方面的任何bean,都将被Spring AOP视为一个方面,并将相应地通知上下文中的bean。

下面是当你以这种方式使用Spring AOP时的Hello World程序。首先,应用程序上下文文件中bean元素的内容:

	<bean id="helloService"
class="org.aspectprogrammer.hello.spring.HelloService"/>

<aop:aspectj-autoproxy/>

<bean id="helloFromAspectJ"

class="org.aspectprogrammer.hello.aspectj.HelloFromAspectJ"/>

HelloService是一个简单的Java类:

public class HelloService {
 

public void main() {
System.out.println("Hello World!");
}

}

HelloFromAspectJ与你在本文前面见过的被注解的Java类(@AspectJ方面)完全相同。以下是启动Spring容器的一个小主类,获得一个对helloService bean的引用,并在它上面调用’main’方法:

public class SpringBoot {
 

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"org/aspectprogrammer/hello/spring/application-context.xml");
HelloService service = (HelloService) context.getBean("helloService");
service.main();
}

}

运行这个程序产生下面的输出:

Hello World!<
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值