Spring 2.0的新特性和应用实践

Spring 2.0的新特性和应用实践


主题
Web框架,
事务处理,
AOP

主题

Spring开源项目开始于2003年2月,现在框架正变得越来越强大。目前已经达到了超过一百万的下载量;在很多行业范围内成为事实上的标准;并且改变了企业Java应用的开发过程。

最重要的是,它发展了大量而且忠诚的用户,他们理解框架的关键价值并且共享反馈,来帮助框架高速发展。Spring的使命也一直很清晰:
  • 提供一种非入侵性编程模型。应用程序的代码应该尽可能地与框架解耦。
  • 为内部基础设施提供一种优秀的解决方案,以便开发者能将注意力放在交付业务价值,而不是解决普通的问题。
  • 使开发企业应用程序尽可能简单、增强,而不是减弱、机械。

在2006年10月份进入最终版的Spring 2.0,更加提升了这些价值。在核心团队在2005年佛罗里达Spring体验大会前研究发展特性时,我们发布了两个关键的主题——简易性和强大——突出作为Spring 2.0的主线,并且依旧忠实于Spring的使命。

某些决定很容易。从一开始,我们很清楚Spring 2.0将会完全向后兼容,或者说尽可能地完全向后兼容。尤其考虑到Spring在很多企业中作为一个事实上的标准这样一个定位,避免任何对用户体验的破坏 是非常重要的。幸运地是,Spring一直致力于非入侵,这样的目标就完全可以达到。

在十个月的Spring 2.0开发过程进行中,我们也需要考虑到一些Spring在2005到2006年的使用中越来越明显的趋势:

  • Spring越来越多地被一些非常大的组织来使用,从战略的角度而不是只从项目角度来采用。这不仅意味着关于向后兼容的责任,而且是与大量不同类别的用户相关的挑战。
  • 越来越多数目的优秀的第三方软件产品正在内部使用Spring,并需要容器的配置优化和灵活性。这样的例子很多,这里简单列举几个:
    • 即将发布的BEA WebLogic Server 10,使用了Spring和Pitchfork项目来执行注入和拦截。
    • BEA WebLogic Real Time(WLRT),来自于BEA的一种高端产品,致力于像前端办公交易这样的应用,需要很低的等待时间。
    • 大量广泛使用的开源产品,比如Mule、ServiceMix以及Apache JetSpeed门户容器。
    • 一些企业厂商使用Spring集成他们自己的产品,比如GigaSpaces,Terracotta和Tangosol等。尤其是网格空间的公司,正在逐步投入Spring作为编程模型的选择。
    • Oracle的SCA实现,以及不同的其他Oracle产品。

因此我们需要确保当Spring变得对企业应用开发者更加友好的同时,也要迎合这些苛刻的用户。

从35000英尺

Spring 2.0的最大愿景是什么?

Spring 2.0提供了很大范围内的增强,其中最显著的可能是:

  • 配置扩展:在Spring 2.0中,Spring支持可扩展的XML配置,使得使用自定义元素开发成为可能,它们为生成Spring bean的定义提供一种新层次的抽象。XML扩展机制同样提供了一些新的标签来简化许多普通的任务。
  • 在AOP框架中有重要增强,使得既强大又更易于使用。
  • 增强对Java 5的支持。
  • 提供以动态语言实现Spring bean的能力,比如Groovy、JRuby和Beanshell,同时保留Spring组件模型的所有服务,比如依赖注入,方便的声明性服务以及AOP。
  • 以及许多新的特征,包括一个Portlet MVC框架,“消息驱动POJO”,与新的API的集成,包括JAVA持久化API(JPA),以及一个异步任务执行框架。

有许多表面上不是很明显的特征,但仍然很重要:

  • 对Ioc容器更进一步的扩展,使得在Spring之上构建框架或产品更加容易。
  • 对Spring特有的集成测试支持的改善。
  • 提供AspectJ来暴露Spring核心功能给使用AspectJ和Spring的用户,比如事务管理和依赖注入。

重要的是,这些特性被设计以和谐的整体来一起运行。

概述

这篇文章分为两部分。在第一部分(就是本文),我们将会谈到核心容器,XML配置扩展,AOP增强,以及特定于Java 5的特征。

在第二部分,我们将会谈到消息,对动态语言的支持,Java持久化API以及Web层的增强。也会看一下表面以下的一些改进。

现在就让我们更深入地研究一些新的特性,并且使用一些代码范例来描述它们。

XML配置扩展

在Spring 2.0中最明显的增强就是XML配置。

Spring Ioc容器实际上是独立于元数据的表示的,比如XML。Spring以Java对象(BeanDefinition以及它的子接口)的形式拥有自己的内部元数据。有一个对XML配置补充的研究,比如使用注解的Java配置

然而在今天,XML是被最多用在配置Spring上的,这就是Spring核心中配置改进的焦点。

Spring 2.0中XML的增强巧妙概括了简易性和强大的主题:它们简化执行某些普通的任务,但是也使得一些额外的高级任务成为可能。

目标

传统上,在Spring的XML配置语法和Spring的内部元数据之间有一对一的关系。一个元素产生一个BeanDefinition。

这通常就是我们想要的,并且对于配置那些框架并不了解的应用程序类,也是理想的。

但是,如果框架应该了解一个可能被反复使用的特定的类呢,比如象JndiObjectFactory这样的普通的类,用来从JNDI寻找对象,并将其作为可注入的对象注入Spring容器,或者如果一些Bean定义只是在一起使用时才有意义呢?

这样,一种新的抽象形式就能带来重要的好处。

考虑一下下面这个例子,关于JNDI的lookup:

<bean class="org.springframework.jndi.JndiObjectFactoryBean" id="dataSource">
<property value="jdbc/jpetsore" name="jndiName">
</property></bean>

这当然要好过以前那些实现Service Locator的令人厌恶的日子,但它并不完美。我们会一直使用这个相同的类。并且(除非我们使用Java 5)没有一种机制来指明“jndiName”属性是必需的,而其他的属性都是可选的。

Spring 2.0添加了一个方便的“jee”命名空间,其中包括允许我们执行同样的JNDI lookup的标签,就像下面这样:

<jee:jndi-lookup jndi-name="jdbc/jpetstore" id="dataSource"></jee:jndi-lookup>

这个比较简单而且清楚。它清晰地表达了意图而且更加简洁。而且schema抓住了jndiName属性是必需的这样一个事实,并有方便的工具支持。其他可选的属性同样在这个schema中得以表达,就像下面这个例子:

<jee:jndi-lookup id="simple">
jndi-name="jdbc/MyDataSource"
cache="true"
resource-ref="true"
lookup-on-startup="false"
expected-type="com.myapp.DefaultFoo"
proxy-interface="com.myapp.Foo"/></jee:jndi-lookup>

请注意在这样一个相对简单的示例中,attribute的名称几乎和被配置的类中property名称一样,这不是必需的。没有必要做一个命名上的 对应,或者在attribute和property之间做一对一的对应。我们同样能够处理子元素内容。而且,正如我早先提到的,我们可以从一个扩展标签产 生我们想要的任意数目的bean定义。

现在让我们来考虑一个更加复杂的例子,其中我们要使用自定义标签的能力来产生不止一个bean定义。

从Spring 1.2开始,Spring就能够识别@Transactional注解,并通过代理来使受影响的bean变成事务。这导致了一种简单的部署模型——简单地 添加注解过的bean,它们就能自动变得可以处理事务——但是建立这样模型需要一些令人心烦的戏法。为了所需的合作对象,需要三个bean定义——一个 Spring AOP Advisor,一个TransactionInterceptor,以及一个DefaultAdvisorAutoProxyCreator来引发自动 的代理。这些bean定义组成了一个咒语,能够在不同的应用程序以不变的状态使用,但是暴露了多过用户不需要知道的细节:

<bean>
class="org.springframework...DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework...&amp;lt;b&amp;gt;TransactionAttributeSourceAdvisor&lt;/b&gt;">
<property transactioninterceptor="" name="transactionInterceptor&amp;lt;br /&gt; ref=">
</property>

<bean id="transactionInterceptor">
class="org.springframework...TransactionInterceptor">
<property name="transactionManager">
ref="transactionManager"/>
<property name="transactionAttributeSource">
<bean>
class="org.springframework...AnnotationsTransactionAttributeSource">
</bean>
</property>
</property> </bean></bean></bean>

这是错误的抽象层次。你并不需要看到Spring AOP框架如何控制事务管理这种层次的细节,本意不需要这样的清晰。Spring 2.0在新的“tx”命名空间中提供了一个标签,来替换所有这些定义,像下面这样:

<tx:annotation-driven></tx:annotation-driven>

这个简便的标签达到了相同的效果。这个标签清楚地表达了意图——自动识别事务注解。那三个bean定义仍然通过底层的扩展标签来创建,但那现在是框架的事情了,而不是用户的。

Spring 2.0扩展标签可以在必要时定义它自己的attribute以及子元素结构。定义命名空间的开发者完全可控。处理Spring 2.0扩展标签的NamespaceHandler可以从一个扩展标签创建任意数目的bean定义。

为了使用扩展标签,要使用XML schema而不是DTD,并且导入相关的命名空间。缺省的命名空间应该是beans schema。下面的“tx”命名空间的例子,允许使用<tx:annotation-driven>:</tx:annotation-driven>


<beans xmlns="http://www.springframework.org/schema/beans">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"></beans>
扩展标签可以和正规的bean定义混合使用,可以引入任意数目的命名空间,并在同一个文档中使用。

方便的命名空间

Spring 2.0提供一些方便的命名空间。其中最重要的是:

  • 事务管理(“tx”):在Spring 2.0中使Spring bean能够处理事务变得相当容易,就像我们看到的那样。它同样使定义“transaction attributes”来映射事务行为到方法上变得简单的多。
  • AOP(“aop”):Spring 2.0中专门的用来AOP配置的标签比以前更加简洁,Ioc容器不再需要依赖AOP框架。
  • Java EE(“jee”):这样简化了对JNDI和其他Java EE API的使用,正如我们看到的那样。EJB lookup比JNDI lookup获得的简单性还要多。
  • 动态语言(“lang”):以动态语言简化bean的定义——Spring 2.0的一个新特性。
  • Utils(util):简化加载java.util.Properties对象和其他通用的任务。

在Spring2.1以及往后版本中,将会加入更多的命名空间,来将简单性引入新的领域。简化Spring MVC和JPA使用的命名空间可能会最先加入到核心中去。

第三方配置扩展

作为一种扩展机制,Spring 2.0命名空间最重要的可能性是在Spring核心的外围。

许多产品构建于Spring基础之上,它们的配置可以使用命名空间来变得更加简单。一个很好的例子是Acegi Security for Spring(将在2007年早些时候改名为Spring Security),它需要配置一些协作bean的定义。Spring 2.0的命名空间会使这变得非常简单。再一次更加清楚地表达了简单性的意图。

许多产品和Spring紧密集成,这样的好处不言而喻。Tangosol对Coherence的集成就是现成的案例。

其他潜在的例子包括支持Spring配置的产品,比如IBM的ObjectGrid。虽然ObjectGrid目前没有在内部使用Spring,但 它被设计成通过Java来配置,使得能更加容易地集成到基于Spring的应用程序中。扩展schema会让这个变得相当简单。

一个XML文档使用某个扩展标签作为顶层的元素是可能的。这样避免需要通过命名空间给扩展schema元素加上前缀,意味着这样的配置看起来更自然一些,而非以Spring为中心的。(通常,元素是在缺省的命名空间,因此传统的Spring bean定义并不需要前缀。)

随着时间过去,和JSP自定义标签的发展,经验会通过实证明了的价值引出通用目的的标签。我们期望用户来创建命名空间的库,来让这个社区受益。

实现XML扩展

实现命名空间相对简单。它分三步:

  1. 定义你的XML schema。这是最困难的一步,需要有合适的工具。对于schema没有限制,当然你需要明白它是如何在运行时引导BeanDefinition的生成的。
  2. 实现NamespaceHandler接口,从你的schema中的元素和属性来产生BeanDefinition。
  3. 编辑一个专门的注册文件,spring.handlers,来让Spring知道新建的NamespaceHandler类。

Spring配备的spring.handlers文件显示了“标准”的命名空间是如何配置的:

http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

你可以在不同的/META-INF目录拥有多个spring.handlers文件。Spring会在运行时合并它们。

一旦遵循了这些步骤,就可以使用你的新的扩展。

NameSpaceHandler接口并不难实现。它采用W3C DOM元素,并通过处理它们来生成BeanDefinition元数据。Spring解析XML:你的代码仅仅需要遍历XML树。

public interface NamespaceHandler {
void init();
BeanDefinition parse(Element element, ParserContext parserContext);

BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}

parse()方法是最重要的,它负责将BeanDefinitions添加到提供的上下文中。

同样要注意decorate()方法。NamespaceHandler同样可以装饰一个包含的bean定义,就像下面这个创建作用域内的代理的语法所显示的:

 

<aop:scoped-proxy>标签用来修饰包含它的标准的<bean>元素;如果它能够访问BeanDefinition,它就能对BeanDefinition进行修改。<aop:scoped-proxy></aop:scoped-proxy></bean></aop:scoped-proxy>

为了简化BeanDefinition元数据的生成,Spring 2.0引入了一种方便的新的BeanDefinitionBuilder类,提供一种流畅的、构建器风格的API。开始实现 NamespaceHandlers的最佳指导是那些存在于Spring核心中的类。其中UtilNamespaceHandler是个相对简单的例子; 而AopNamespaceHandler是个比较高级的例子,它解析了一个复杂的子元素结构。

最佳实践:什么时候应该定义你自己的命名空间?

你有锤子并不意味着其他一切都是钉子。正如我们已经看到的,Spring 2.0的XML扩展机制在很多案例中交付了很大的价值。然而,如果没有很好的理由就不应该使用它。因为XML扩展标签是一种新的抽象,它同样提供了一些需 要学习的新的内容。Spring的正规的XML格式对成千上万的开发者来说已经很熟悉了,甚至对那些新接触Spring的人都是用直觉就可以判断的。 Spring XML文件提供了易于理解的某个应用程序结构的蓝图。如果过度配置使用了不熟悉的自定义标签,就没什么必要了。

让我们在这个领域内考虑一些相关的经验。JSP自定义标签是个很好的例子。最终它们通过设计得很棒的标签库,比如JSTL,Struts和 Spring MVC的标签库,产生了真实的价值。但在早些年,它们会引起厌恶,甚至是混乱的JSP页面。(我在这里可以根据经验来解释,因为我自己实现了一两个这样的 标签库)。

把命名空间处理器看作是一个重要的新的扩展点,以及对Spring很有价值的新的抽象。它们对于那些在Spring之上构建第三方产品的人来说非常 棒;它们对于非常大型的项目也很有用。很快就会发现,没有了它们,很难想象生活会变成什么样子。但是最终用户还是应该对实现它们持谨慎态度,但使用没有问 题。

当然,伴随Spring提供的方便的扩展标签,比如aop,tx以及jee命名空间,将很快成为Spring配置词表的核心部分,就跟元素一样被广泛了解。你当然应该优先使用这些,而不是传统的冗长的方式,来完成相同的任务。

语法糖

转向使用schema也允许一点点快捷方式,比如对property值使用attribute而不是子元素。这些attribute不会被验证,但 因为我们使用的是XML schema,而不是DTD,我们仍然可以保留所有其他的验证。因为attribute名称就是property名称,XML验证不会再添加任何东西;这 是基于Java的验证的问题,而不是XML结构的。

考虑一下下面这个Java对象,它有两个简单的property,以及对一个关联对象的依赖:

 

可以像下面这样使用XML进行配置:

 

请注意property是如何通过使用attribute来提供的,而不是元素。这样使用了特殊命名空间“p”的魔力。这个命名空间并没有验证,但允许使用attribute名称来和Java property名称进行匹配。

通过简单的几句,我们就简化了使用“p”命名空间中的property名称。就像“p:name”。当注入对其他Spring bean的引用时,使用“-ref”后缀,就像“p:house-ref”。

这种快捷的语法在你想要使用autowiring时尤其方便。比如,考虑下面的变量:

<bean class="&amp;lt;font color="></bean>

这里我们没有设置“house”property,因为autowiring会考虑它。你甚至可以使用在<beans>元素层次使用default-autowire来在整个文件使用autowiring。</beans>

下面这个来自于Spring 1.0或者 1.1的用法演示了Spring配置在最近两个主要的发布版本中(1.2和2.0)减少了多少数目的尖括号。

 

在Spring1.2中,我们引入了“value”和“ref”attribute,在大多数情况下不需要子元素,而在Spring 2.0中则可以更纯粹和简单地使用attribute。

当然,传统的XML格式可以继续工作。当property值很复杂,而且不合法或者不能读作一个attribute值时,就可以使用它们(传统的XML格式)。而且,当然,没有必要重写已经存在的配置文件。

除了XML配置扩展,在Spring Ioc容其中还有很多其他的新的特性。

其他Ioc容器增强

新的bean作用域

和XMl扩展一起,最重要的新的Ioc容器特性就是对于bean生命周期管理的新增的自定义作用域。

1.背景

Spring之前为bean提供了两种作用域:单例原型(或者叫非单例)。

Singleton bean是一个在所属容器上下文中的单例对象。在容器的生命周期中只会有一个实例存在,当容器关闭,它就会向所有需要知道容器关闭事件的单例bean发送事件通知——比如,关闭任何可以管理的资源,像连接池。

Prototype bean是任何时候通过注入到另外一个bean而被引用,或者是对所属容器上getBean()调用的响应时创建的。在这种情况下,bean定义与一个单 个对象没有关系,而是一个用来创建对象的配方。每个创建好的实例会有完全相同的配置,但会有不同的身份。Spring容器不会持有对原型的引用;它的生命 周期由获得它的那段代码来负责。

在Spring 2.0中,我们添加了自定义作用域的能力。可以给它们起任何名字。某个自定义作用域通常与能够管理对象实例的后端存储相对应。在这种情况下,Spring提供了它熟悉的编程模型,支持注入和查找,而且后端存储提供了作用域内对象的实例管理。

典型的后端存储有:

HTTP session Clustered cache 其他持久化存储

2.Web作用域

对于这个特性,最通常的需求是关于在web应用程序HTTP session中透明存储对象。这在Spring 2.0中得到很方便的支持。因为这种需求很普通,所以举例会很容易:

考虑下面这个bean的定义:

<bean scope="session" class="com.foo.UserPreferences" id="userPreferences"></bean>

这里我们指定了“session”作用域而不是缺省的“singleton”。作用域可以指定成任何名字,但在Web应用程序中提供“session”和“request”便于使用。

当我们通过名称“userPreferences”调用getBean()时,Spring会很明显地从当前HTTP Session中找出UserPreference对象。如果没找到UserPreferences对象,Spring就会创建一个。注入使得可以为用户 描绘一个可以自定义的预配置的UserPreferences。

为了让它工作起来,你需要在你的web.xml文件中像下面这样定义一个过滤器。它会在底层处理一个ThreadLocal绑定,以便Spring能够知道去哪个HTTP Session对象中去查找。

<web-app></web-app>

这样就解决了查找的问题。但通常我们宁愿使用API尽量少的注入风格。如果我们想要把“userPreferences”bean注入到其他 bean中去会发生什么呢,哪个会有更长的生命周期呢?比如,如果我们想在像下面这样的一个单例Spring MVC控制器中采用单独的UserPreferences对象时,又会发生什么呢:

 

在这个案例中,我们想要“非常及时”的注入,对短期存在的UserPreferences对象的引用是在被注入者使用的时候解析的。

通常有这样的误解,就是Spring注入是静态的,因此必然是无状态的。这并不正确。因为Spring有一个复杂的基于代理的AOP框架,它能在运行时通过提供这样“非常及时”的功能来隐藏查找的动作。因为Ioc容器控制着注入的内容,它能注入一个隐藏了需要查找的代理。

我们可以很容易地通知容器来执行这样的代理,像下面这样。我们使用一个子元素来修饰userPreferences bean定义,<aop:scoped-proxy>:</aop:scoped-proxy>

<bean class="com.foo.UserPreferences" id="userPreferences"></bean>

现在解析会如期动态地发生;UserController中的userPreferences实例变量就会是从HTTP Session中解析出正确UserPreferences对象的代理。我们不需要直接跟HTTP Session对象打交道,而且可以轻松地对UserController进行单元测试,而不需要一个mock HttpSession对象。

另外一种获得“非常及时”的注入的方式是使用一个查找方法。这是一个自从Spring 1.1就存在的技术,它使生命周期长的bean(通常是单例)能依赖一个潜在的生命周期短的bean。容器能够重载一个抽象(或者具体)的方法来在方法被 调用时返回执行getBean()调用的结果。

在这个例子中,我们没有在被注入者中定义一个实例变量,而是定义了一个返回所需对象的抽象方法。方法必须是公有的或者受保护的:

 

在这个例子中,没有必要使用一个作用域内的代理。我们改变了被注入对象的bean定义,而不是被注入的bean。XML配置看起来应该是这样:

<bean class="com.foo.UserPreferences" id="userPreferences"></bean>

这种机制需要类路径中包含CGLIB。

3.其他可能性

无疑,在真实的Spring风格中,底层的机制是可插拔的,而不是绑定到Web层的。比如,Tangosol Coherence作用域可以像下面这样使用,通过Tangosol和Interface21提供的“datagrid”命名空间。

 

在一个“datagrid”作用域内声明一个bean,意味着bean的状态管理是由Coherence来执行,在一个Clustered Cache中,而不是在本地的服务器上。当然,bean是由Spring来实例化和注入的,而且可以从Spring服务中受益。我们预见特定作用域内的 bean会在SOA和批处理的环境中非常有用,并且期望能在将来Spring的版本中添加更多方便的bean作用域。

4.自定义作用域

定义你自己的作用域很简单。参考手册详细解释了这个过程。你将需要一种策略来辨别如何在当前的作用域内解析出对象。典型的情况下,这会涉及到ThreadLocal,就像Web作用域在底层所做的那样。

类型推断

如果你运行的是Java 5,Spring 2.0会从它新的能力中获益,比如泛型。比如,考虑一下下面这个类:

 

“plainList”属性是个旧式风格的集合,而“flodatList”属性有个类型化的说明。

考虑下面的Spring配置:

<bean color="#0000ff" class="&amp;lt;font"></bean>

Spring会正确地为“floatList”属性填上浮点数,而不是字符串,因为它能非常聪明地意识到它需要执行类型转换。

下面的测试演示了这种情况:

 

输出看起来会像下面这样:

Value='1', class=java.lang.String
Value='2', class=java.lang.String
Value='3', class=java.lang.String
Value='1.0', class=java.lang.Float
Value='2.0', class=java.lang.Float
Value='3.0', class=java.lang.Float

新扩展点

在Ioc容器中有一些新的扩展点,包括:

额外的PostPostProcessor钩子,为像Pitchfork这样的项目提供更强大的能力,来处理自定义注解,或者在Spring bean实例化和配置时执行其他操作。 添加任意元数据到BeanDefinition元数据的能力。添加信息很有用,虽然对Spring本身没有意义,但可以被构建于Spring之上的框架,或者像集群产品这样跟Spring集成的产品来处理。

这些主题主要和高级用户,以及那些使用Spring编写产品的人有关,这同样超出本篇文章的范围。但是理解Spring 2.0并不仅仅是在表面上增强,这很重要;它在底层完成了相当多的有难度的工作。

同样也有大量的增强是支持与OSGi的集成,形成Spring OSGi集成项目,它将OSGi的动态模块管理的能力与Spring组件模型相集成。这个工作会在Spring2.1中继续进行,会把Spring的JAR文件打包成OSGi的bundle。

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方法:

 

我们假设有这样的需求,任何时候调用birthday方法,我们都应该发送一张生日贺卡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值