Spring 2.0:新功能及其重要性

主题

自从开源项目于2003年2月开始以来,Spring框架变得越来越强大。 它已支持超过100万次下载; 成为各行各业的事实上的标准; 并改变了企业Java应用程序的开发。
  • 提供非侵入性编程模型。 应尽可能将应用程序代码与框架分离。
  • 为内部基础架构提供出色的解决方案,以便开发人员可以专注于提供业务价值,而不是解决一般性问题。
  • 使开发企业应用程序尽可能简单,但增强而不是牺牲功能。

2006年10月发行的Spring 2.0进一步提高了这些价值。 当核心团队研究2005年12月在佛罗里达举行的Spring Experience Conference之前的开发功能集时,我们意识到两个关键主题-Simplicity和Power-在Spring 2.0中脱颖而出,并始终忠于Spring的成立。

有些决定很容易。 从一开始,我们很清楚Spring 2.0将完全向后兼容,或者尽可能接近完全向后兼容。 尤其是考虑到Spring在许多企业中已成为事实上的标准,因此避免用户体验受到干扰至关重要。 幸运的是,由于Spring一直竭尽全力以实现非侵入性的发展,因此这一目标是可以实现的。

随着有关Spring 2.0的工作经过10个月的开发,我们还需要考虑到2005-2006年Spring使用中显而易见的几种趋势:

  • 大型组织越来越多地使用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在为业务应用程序开发人员提供更好的服务的同时,还满足了这些苛刻用户的需求。

从35,000英尺

Spring 2.0的概况如何?

Spring 2.0提供了广泛的增强功能,其中最明显的可能是:

  • 配置扩展:在Spring 2.0中,Spring支持可扩展的XML配置,从而允许开发自定义元素,这些元素为生成Spring bean定义提供了新的抽象层次。 XML扩展机制还允许提供新标签来简化许多常见任务。
  • AOP框架的重大增强,使其更强大且更易于使用。
  • 增强了对Java 5的支持。
  • 能够以动态语言(例如Groovy,JRuby和Beanshell)实现Spring Bean的能力,同时保留了Spring组件模型的所有服务(例如依赖注入),开箱即用的声明服务和AOP。
  • 许多新功能包括Portlet MVC框架,“消息驱动的POJO”以及与新API的集成,包括Java Persistence API(JPA)和异步任务执行框架。

表面以下的许多特征不太明显,但仍然很重要:

  • 更多的IoC容器扩展点,可轻松在Spring之上构建框架或产品。
  • 改进了Spring独特的集成测试支持。
  • 向AspectJ提供的方面向使用AspectJ和Spring的用户公开了Spring的核心功能,例如事务管理和依赖注入。

重要的是,这些功能被设计为在一个和谐的整体中协同工作。

大纲

本文分为两部分。 在第一部分(本文)中,我们将介绍核心容器,XML配置扩展,AOP增强和Java 5特定的功能。

在第二部分(在接下来的几个月中发布)中,我们将讨论消息传递,对动态语言的支持,Java Persistence API和Web层增强。 我们还将研究一些幕后改进。

现在,让我们更详细地了解一些新功能,并通过代码示例进行说明。

XML配置扩展

Spring 2.0中最明显的增强功能之一就是XML配置。

Spring IoC容器实际上独立于元数据(例如XML)的表示。 Spring具有自己的内部元数据,以Java对象(BeanDefinition和子接口)的形式出现。 对于补充XML配置的替代方法,例如使用注解的Java配置( http://blog.interface21.com/main/2006/11/28/a-java-configuration-option-for-spring/ ),正在积极研究替代方法。

但是,如今,XML最常用于配置Spring,因此是Spring核心中配置改进的主要重点。

Spring 2.0中的XML增强巧妙地概括了简单性和强大性的主题:它们简化了一些常见任务的执行,但也使其他高级任务成为可能。

目标

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

这通常是我们想要的,并且是配置框架不知道的应用程序类的理想选择。

但是,如果框架应该更多地了解可能重复使用的特定类,例如JndiObjectFactory这样的通用类,该类用于从JNDI查找对象并将它们作为可注入对象带入Spring容器中,该怎么办? 。 如果几个bean定义只有一起使用才有意义呢?

因此,一种新的抽象形式增加了重要的好处。

考虑下面的JNDI查找示例:

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

这肯定比实现Service Locators的烦人的过去要好,但是它并不完美。 我们将始终使用相同的bean类。 并且(至少除非我们使用Java 5),没有机制表明“ jndiName”属性是必需的,而其他属性是可选的。

Spring 2.0开箱即用地添加了一个“ jee”名称空间,包括 标签允许我们执行相同的JNDI查找,如下所示:

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

这更简单明了。 它清楚地表达了意图。 更简洁。 该模式捕获了jndiName属性是强制性的事实,从而促进了工具支持。 其他可选属性也在模式中表示,如以下示例所示:

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

注意,尽管在这种相对简单的情况下,属性名称与正在配置的类中的属性名称几乎相同,但这不是必需的。 在属性和属性之间不必存在命名对应关系或1:1对应关系。 我们还可以处理子元素的内容。 而且,正如我前面提到的,我们可以从扩展标记生成任何数量的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... TransactionAttributeSourceAdvisor ">
<property name="transactionInterceptor
ref="transactionInterceptor"/>
</bean>

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

这是错误的抽象级别。 您不需要查看有关Spring AOP框架来控制事务管理的工作的详细程度,其意图也不太清楚。 Spring 2.0在新的“ tx”命名空间中提供了一个标签,该标签替换了所有这些定义,如下所示:

<tx:annotation-driven />

此开箱即用的标签可获得相同的结果。 标签清楚地表达了意图-自动识别交易注释。 这三个bean定义仍将由幕后的扩展标签创建,但这(正确)现在是框架的事,而不是用户的事。

Spring 2.0扩展标签可以根据需要定义自己的属性和子元素结构。 定义名称空间的开发人员完全可以控制。 处理Spring 2.0扩展标签的NamespaceHandler可以从扩展标签创建任意数量的bean定义。

为了使用扩展标签,必须使用XML模式而不是DTD,并导入相关的名称空间。 默认名称空间应为bean模式。 以下示例为“ tx”命名空间,允许使用<tx:annotation-driven>

<?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: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 ">
扩展标记可以与常规bean定义混合使用,并且可以在同一文档中导入和使用任意数量的名称空间。

开箱即用的名称空间

Spring 2.0提供了一些现成的名称空间。 最重要的是:

  • 事务管理(“ tx”):如我们所见,在Spring 2.0中,使Spring bean具有事务性变得非常容易。 定义将交易行为映射到方法的“交易属性”也变得更加容易。
  • AOP(“ aop”):专用标签使Spring 2.0中的AOP配置比以前更加简洁,而IoC容器无需依赖AOP框架。
  • Java EE(“ jee”):如我们所见,这简化了JNDI和其他Java EE API的使用。 EJB查找比JNDI查找获得更多的收益。
  • 动态语言(“ lang”):简化了动态语言中bean的定义,这是Spring 2.0中的一项新功能。
  • Utils(“ util”):简化了java.util.Properties对象和其他常见任务的加载。

在Spring 2.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,但它设计为通过JavaBeans进行配置,以使其易于集成到基于Spring的应用程序中。 扩展架构将使这一过程变得更加简单。

XML文档可能会将扩展标签用作顶层元素。 这样就避免了在扩展模式元素前面加上名称空间的需要,这意味着配置可以看起来是“本机”而不是以Spring为中心的。 (通常 元素位于默认名称空间中,因此传统的Spring bean定义不需要前缀。)

随着时间的流逝,与JSP自定义标签一样,经验将指向具有可靠价值的通用标签。 我们希望用户创建名称空间库以使社区受益。

实施XML扩展

实现名称空间相对容易。 这是一个三步过程:

  1. 定义您的XML模式。 这是最难的一步,需要适当的工具。 架构没有任何限制,尽管您当然需要能够看到它如何在运行时指导BeanDefinition元数据的生成。
  2. 实现NamespaceHandler接口,以从架构中的元素和属性生成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:您的代码只需要走树即可。

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

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

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

另请注意decorate()方法。 NamespaceHandler也可以装饰一个封闭的bean定义,如以下用于创建作用域代理的语法所示:

<bean id= "scopedList" class= "java.util.ArrayList" scope= "request">
<aop:scoped-proxy/>
</bean>

<aop:scoped-proxy>标签装饰包含它的常规<bean>元素; 如果可以访问BeanDefinition,则可以对其进行修改。

为了简化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配置词汇表的核心部分,众所周知, 元件。 当然,您应该优先使用这些,而不是使用更冗长的传统方法来完成同一件事。

语法糖

迁移到模式还允许使用属性而不是子元素的一些快捷方式。 这些属性没有经过验证,但是因为我们使用的是XML模式而不是DTD,所以我们仍然可以保留所有其他验证。 由于属性名称是属性名称,因此XML验证不会添加任何内容。 这是基于Java而不是XML结构的验证问题。

想象下面的Java对象,它具有两个简单的属性以及对关联对象的依赖关系:

public class Person {
private int age;
private String name;
private House house;
public void setAge(int age) {
this .age = age;
}

public void setName(String name) {
this. name = name;
}

public void setHouse(House house) {
this . house = house;
}

}

可以使用XML进行如下配置:

<?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:p =" http://www.springframework.org/schema/p "
xsi:schemaLocation= "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean class= "com.interface21.spring2.ioc.Person"
p:name= "Tony"
p:age= "53"
p:house-ref= "number10"
/>

<bean class= "com.interface21.spring2.ioc.House"
id= "number10"
p:name= "10 Downing Street"
/>

</beans>

注意如何使用属性而不是元素来提供属性。 这是通过特殊命名空间“ p”的魔力实现的。 该名称空间未经验证,但允许使用名称与Java属性名称匹配的属性。

对于简单类型,我们只需在“ p”名称空间中使用属性名称,就像在“ p:name”中一样。 在注入对其他Spring bean的引用时,请使用“ -ref”后缀,如“ p:house-ref”中所示。

当您要使用自动装配时,此快捷语法特别引人注目。 例如,考虑以下变体:

<bean class="com.interface21.spring2.ioc.Person" 
autowire= "byType"
p:name= "Tony"
p:age= "53"
/>

这里我们没有设置“ house”属性,因为自动装配可以解决这个问题。 您甚至可以在<beans>元素级别使用default-autowire以在整个文件中进行自动装配。

以下是Spring 1.0或1.1用法的摘录,说明了在过去的两个主要发行版(1.2和2.0)中,Spring的配置已减少了尖括号的最小数量:

<bean class= "com.interface21.spring2.ioc.Person">
<property name="name"><value>"Tony"</value></property>
<property name="age"><value>"53"</value></property>
<property name="house"><ref local= "number10" /></property>
</bean>

在Spring 1.2中,我们引入了“ value”和“ ref”属性,而不是要求 在大多数情况下,在Spring 2.0中,可以使用纯属性和简单属性。

当然,传统的XML形式继续起作用。 当属性值复杂且不合法或不可读为属性值时,请使用它们。 并且,当然,无需重写现有的配置文件。

除了XML配置扩展,Spring IoC容器中还有许多其他新功能。

其他IoC容器增强功能

新bean范围

与XML扩展一起,最重要的新IoC容器功能是为bean生命周期管理添加了自定义范围。

1.背景

传统上,Spring为bean提供了两个作用域: SingletonPrototype (或非Singleton)。

单例bean是拥有容器上下文中的单例。 在容器的生存期内,将恰好有一个实例,当容器关闭时,它将向对破坏事件感兴趣的任何单例bean发出事件,例如,关闭诸如池连接之类的任何托管资源。

每当通过注入另一个bean或响应拥有容器上的getBean()调用来引用它时,都会创建一个Prototype bean。 在这种情况下,bean定义不对应于单个对象,而是创建对象的方法。 每个创建的实例将具有相同的配置,但具有不同的标识。 Spring容器将不会保留对原型的引用; 它的生命周期是获得它的代码的责任。

在Spring 2.0中,我们添加了自定义范围的功能。 可以使用任何名称。 定制作用域通常对应于可以管理对象实例的后备存储。 在这种情况下,Spring提供了它熟悉的编程模型,可以进行注入和查找,而后备存储则提供了范围对象的实例管理。

典型的后备商店是:

  • HTTP会话
  • 集群缓存
  • 其他持久性存储

2.网络范围

此功能最常见的要求涉及在Web应用程序的HTTP会话中透明存储对象。 在Spring 2.0中是开箱即用的。 由于此要求很常见,因此可以作为示例的良好基础。

考虑以下bean定义:

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

在这里,我们指定“会话”范围,而不是默认的“单身”。 范围可以指定任何名称,但是开箱即用地提供了“会话”和“请求”以供在Web应用程序中使用。

当我们使用名称“ userPreferences”调用getBean()时,Spring将透明地从当前HTTP会话中解析UserPreferences对象。 如果没有找到UserPreferences对象,Spring将创建一个。 注入为预先配置的UserPreferences启用了蓝图,然后可以为有问题的用户自定义该首选项。

为了使此工作正常进行,您需要在web.xml文件中定义一个过滤器,如下所示。 这将在后台处理ThreadLocal绑定,以便Spring知道要查看的HTTP Session对象:

<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener></listener-class>
</listener>
...
</web-app>

这解决了查找问题。 但是我们通常首选无API注入样式。 如果我们想将“ userPreferences” bean注入生命周期更长的其他bean,会发生什么? 例如,如果我们想像这样在Singleton Spring MVC Controller中使用单个UserPreferences对象,该怎么办:

public class UserController extends AbstractController {
private UserPreferences userPreferences;
public void setUserPreferences(UserPreferences userPreferences) { this .userPreferences = userPreferences;
}

@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// do work with this.userPreferences
// Will relate to current user
}
}

在这种情况下,我们实际上希望进行“及时”注入,其中对寿命较短的UserPreferences对象的引用是在被注入者使用时解析的。

常见的误解是,Spring注入是静态的,因此必然是无状态的。 这是不正确的。 由于Spring具有复杂的基于代理的AOP框架,因此它可以在运行时隐藏查找,从而提供“及时”功能。 因为IoC容器控制注入的内容,所以它可以注入隐藏必要查找的代理。

我们可以指示容器轻松执行此类代理,如下所示。 我们用子元素<aop:scoped-proxy>装饰userPreferences Bean定义。

<bean id="userPreferences" class="com.foo.UserPreferences"scope="session ">
<aop:scoped-proxy/>
</bean>

<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userController" class="com.mycompany.web.UserController">
<!-- a reference to the proxied 'userPreferences' bean -->
<property name="userPreferences" ref="userPreferences"/>

</bean>
</beans>

现在,解决方案将按预期动态进行; UserController中的userPreferences实例变量将是一个代理,用于从HTTP会话解析正确的目标UserPreferences。 我们不需要直接与HTTP会话进行交互,并且可以轻松地对UserController进行单元测试,而无需模拟HttpSession对象。

获得“及时”注入的另一种方法是使用查找方法。 从Spring 1.1开始,这是一种可用的技术,它可以使寿命长(通常为单例)的Bean依赖于寿命可能较短的Bean。 容器可以重写抽象(或具体)方法,以在调用该方法时返回执行getBean()调用的结果。

在这种情况下,我们不在注入对象中定义实例变量,而是返回所需对象的抽象方法。 该方法可以是公开的或受保护的:

public abstract class UserController extends AbstractController {
protected abstract UserPreferences getUserPreferences();

@Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// do work with object returned by getUserPreferences()
// Will relate to current user
}
}

在这种情况下,无需使用作用域代理。 我们更改被注入者的bean定义,而不是被注入的bean。 XML配置如下所示:

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

<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userController" class="com.mycompany.web.UserController">
<!-- a reference to the proxied 'userPreferences' bean -->
<lookup-method name="getUserPreferences" bean="userPreferences" />

</bean>
</beans>

该机制要求在类路径上使用CGLIB。

3.其他可能性

自然地,以真正的Spring方式,底层机制是可插拔的,并且与Web层无关。 例如,可以使用Tangosol Coherence范围,并使用Tangosol和Interface21提供的“ datagrid”命名空间:

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

xmlns:datagrid="http://schemas.tangosol.com/schema/datagrid-for-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://schemas.tangosol.com/schema/datagrid-for-spring
http://schemas.tangosol.com/schema/datagrid-for-spring/datagrid-for-spring.xsd">

<datagrid:member/>

<bean id="brian" class="Person" scope="datagrid">
<aop:scoped-proxy/>

<property name="firstName" value="brian" />
<property name="lastName" value="oliver" />
<property name="age" value="21" />
</bean>
</beans>

声明将要在“数据网格”内作用域的bean意味着该bean的状态管理是由Coherence在群集缓存中而不是在本地服务器上执行的。 当然,bean是由Spring实例化和注入的,并且可以从Spring服务中受益。 我们希望范围内的bean在SOA和批处理的上下文中将很有用,并希望在将来的Spring版本中立即增加更多范围。

4.自定义范围

定义自己的自定义范围很简单。 参考手册详细介绍了该过程( http://static.springframework.org/spring/docs/2.0.x/reference/beans.html#beans-factory-scopes )。 您将需要一种策略来识别如何在当前范围内解析对象。 通常,这将涉及ThreadLocal,因为Web作用域在后台进行。

类型推断

如果您正在运行Java 5,则Spring 2.0可以受益于泛型等新功能。 例如,想象下面的类:

public class DependsOnLists {
private List plainList;

private List<Float> floatList;

public List<Float> getFloatList() {

return floatList;
}

public void setFloatList(List<Float> floatList) {
this. floatList = floatList;
}
public List getPlainList() {
return plainList;
}

public void setPlainList(List plainList) {
this. plainList = plainList;
}

}

“ plainList”属性是旧样式集合,而“ floatList”属性具有指示的类型。

考虑以下Spring配置:

<bean class="com.interface21.spring2.ioc.DependsOnLists"> 
<property name= "plainList">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name= "floatList">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
</bean>

Spring会聪明地认识到它需要执行类型转换,因此它将正确地使用浮点而不是字符串填充“ floatList”属性。

以下测试说明了这一点:

public class GenericListTest extends 
AbstractDependencyInjectionSpringContextTests {

private DependsOnLists dependsOnLists;

public void setDependsOnLists(DependsOnLists dependsOnLists) {
this. dependsOnLists = dependsOnLists;
}

@Override
protected String[] getConfigLocations() {
return new String[] { "/com/interface21/spring2/ioc/inference.xml" };
}

public void testLists() {
List plainList = dependsOnLists .getPlainList();
List<Float> floatList = dependsOnLists .getFloatList();
for (Object o : plainList) {
assertTrue (o instanceof String);
System. out .println( "Value='" + o + "', class=" +
o.getClass().getName());
}
for (Float f : floatList) {
System. out .println( "Value='" + f + "', class=" +
f.getClass().getName());
}
}

}

输出将如下所示:

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

新的扩展点

IoC容器中有许多新的扩展点,包括:

  • 附加的PostPostProcessor挂钩,为诸如Pitchfork之类的项目提供了更多功能,以在Spring bean的实例化和配置期间的任何时候处理自定义注释或执行其他操作。
  • 向BeanDefinition元数据添加任意元数据的能力。 这对于添加对Spring本身没有意义但可以由基于Spring的框架或与Spring集成的产品(例如集群产品)进行处理的信息很有用。

这些主题主要涉及高级用户和使用Spring编写产品的用户,因此不在本文讨论范围之内。 但重要的是要了解,Spring 2.0增强功能并非全部浮出水面。 下面做了很多艰苦的工作。

为了支持与OSGi的集成,还进行了各种增强,从而实现了Spring OSGi集成项目,该项目将带有OSGi的动态模块管理功能与Spring组件模型集成在一起。 这项工作将在Spring 2.1中继续进行,并将Spring的JAR文件打包为OSGi捆绑包。

AOP增强

Spring 2.0中最令人兴奋的增强功能之一就是Spring AOP,它不仅变得更易于使用而且功能更强大,主要是通过利用来自成熟成熟的AspectJ语言的功能,同时又保留了纯Java代理运行时的能力。

我们一直认为AOP(面向方面​​的编程)很重要。 为什么? 因为它为我们提供了一种有关程序结构的新思维方式,它解决了许多重要问题,而这些问题并不是纯OOP所无法解决的,这使我们能够以模块化而不是分散的方式实现某些要求。

为了了解其好处,让我们考虑一些我们可以在需求中表达但不能直接用纯OO代码实现的东西。 企业开发人员使用通用词汇表,使他们能够清晰地交流。 例如,诸如服务层,DAO层,Web层或Web控制器之类的术语无需解释。

根据该词汇表表达了许多要求。 例如:

  • 服务层应该是事务性的
  • 当DAO操作失败时,应转换SQLException或其他特定于持久性技术的异常,以确保DA​​O接口不会提供泄漏抽象
  • 服务层对象不应调用Web层,因为层应仅取决于它们下面的层
  • 可以重试失败的并发相关故障的幂等业务服务

尽管所有这些要求都是现实的,并且是从经验中汲取的,但是使用纯OOP不能完美地解决这些要求。 为什么? 主要有两个原因:

  • 我们词汇表中的这些术语是有意义的,但它们不是抽象的 。 我们不能使用术语进行编程; 我们需要抽象。
  • 所有这些都是所谓的横切关注点的示例。 当以传统的OO方法实现时,横切关注点跨越许多类和方法。 例如,想象一下在整个DAO层遇到特定异常的情况下应用重试逻辑。 这种关注跨越了许多DAO方法,并且需要进行许多单独的修改才能以传统方式实现。

AOP是一种技术,通过将横切关注点模块化并使我们能够将常见词汇表中的术语表示为可以进行编程的抽象,从而解决了此类问题。 这些抽象称为切入点(pointcuts) ,稍后我将更详细地解释它们。 这种方法产生了很多好处,例如:

  • 由于消除了剪切和粘贴样式的重复,因此减少了代码行数。 这在try / catch / finally习语(例如异常转换和性能监视)中特别有用。
  • 在单个代码模块中捕获此类需求的能力,从而提高了可追溯性。
  • 能够在单个位置修复此类功能中的错误的能力,而不需要重新访问应用程序中的许多要点。
  • 确保横切关注点不会混淆主线业务逻辑-这是真正的危险,因为随着开发的进行,不同的关注点会加在一起
  • 更好地区分开发人员和团队之间的职责。 例如,重试功能可以由单个开发人员或团队进行编码,而不需要许多开发人员跨多个子系统进行编码。

因此,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模式与DTD不同,允许扩展。 我们可以提供一个AOP名称空间,该名称空间似乎使IoC容器了解AOP构造,但又不影响模块性。

AOP术语101:了解切入点和建议

让我们简要地修改一些AOP术语。 如果您使用过Spring AOP,这些概念应该为您所熟悉-这些概念是相同的,只是表达它们的一种不同,更优雅,更强大的方式。

切入点是一个匹配规则。 它标识了程序执行中应该应用方面的一组要点。 这些点称为连接点。 在应用程序运行时,连接点会飞过,例如对象的实例化和方法的调用。 对于Spring AOP(所有版本),唯一支持的连接点是公共方法的执行。

忠告是可以由方面应用到联接点的行为。 建议可以在连接点之前或之后应用。 完整的建议类型为:

  • 在建议之前:在连接点之前调用的建议。 例如,记录该方法调用即将发生。
  • 返回建议后:如果连接点处的方法正常返回,则调用建议。
  • AfterThrowing建议(在Spring 1.x中称为ThrowsAdvice):如果连接点处的方法引发特定异常,则调用建议。
  • 之后建议:在连接点之后调用建议,无论结果如何。 就像最终在Java中一样。
  • 围绕建议:可以完全控制是否调用联接点的建议。 例如,用于将方法调用包装在事务中或定时执行方法。

方面将切入点和建议组合到解决特定横切关注点的模块化解决方案中。

现在不要担心这似乎有点抽象:代码示例很快就会使它们变得更加清晰。

有关在Spring 2.0和AspectJ的上下文中对AOP基础的进一步讨论,请参阅有关infoq的Adrian精彩文章“使用Spring 2.0和AspectJ简化企业应用程序”。

为什么使用AspectJ切入点表达式?

到目前为止,我们讨论的概念是基本的AOP概念,不特定于Spring AOP或AspectJ,并且已经在Spring 1.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属性,以及一个生日方法,可以增加该属性:

public void birthday() {
++ age;
}

假设我们有一个要求,无论何时调用生日方法,都应向有关人员发送生日贺卡。 这是横切需求的经典示例:它不是我们主线业务逻辑的一部分,而是一个单独的问题。 理想情况下,我们希望能够在不影响Person对象的情况下对该功能进行模块化。

现在让我们考虑一下建议。 实际上,实际上发布一张生日贺卡,甚至发送一张电子贺卡,将是这种方法的主要工作。 但是,出于本文的目的,我们对触发基础结构感兴趣,而不是对发送生日贺卡的机制感兴趣。 因此,我们将仅使用控制台输出。 该方法需要访问生日是其Person的Person,并且我们希望在调用Birthday方法时调用它。 我们的简单建议实施如下:

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 id="birthdayCardSenderAspect"
class="com.interface21.spring2.aop.BirthdayCardSender" />

如果需要,我们可以依赖注入此对象或应用任何其他Spring组件模型服务。

接下来,添加AOP配置,以告诉Spring在Spring管理的Person上调用Birthday()方法时,调用BirthdayCardSender bean的onBirthday()方法。 这是使用新的<aop:config>和<aop:aspect>标记完成的。 首先,我们必须导入现成的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-2.0.xsd">

接下来,我们使用新的AOP标签,如下所示。 这是应用此方面的完整配置:

<aop:config>
<aop:aspect id= "sendBirthdayCard" ref= "birthdayCardSenderAspect">
<aop:after
method= "onBirthday"
pointcut= "execution(void com.interface21..Person.birthday()) and
this(person)"

/>
</aop:aspect>
</aop:config>

<aop:after>标记应用一个after建议。 它指定了调用onBirthday()的方法,这是建议。 它以AspectJ切入点表达式的形式指定何时调用方法(切入点)。

切入点表达式是关键。 让我们仔细看看。

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

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

execute()子句的内容定义了要匹配的方法。 我们可以在这里在许多类上编写与一组方法匹配的表达式。 (实际上,这是一种更常见且更有价值的用法:当通知仅与一种方法匹配时,将其外部化就没有必要。)最后,我们被调用的对象绑定到onBirthday()方法的参数上。 这缩小了切入点的范围,该切入点只能匹配在Person对象上的执行,并且提供了一种优雅的方式来获取被调用的对象,而无需任何查找。 如果需要,我们可以使用参数绑定来绑定方法参数,返回类型和异常以及目标。 摆脱查找代码听起来应该很熟悉:这实际上是注入对建议类型的依赖! 秉承Spring的精神,它删除了一个API,顺便说一句,它使得对通知方法进行单元测试变得容易。

现在, 如果切入点表达式匹配一个或多个方法,则将自动代理在Spring上下文中定义的任何Person。 类不包含与此切入点匹配的Bean不会受到影响,与切入点匹配的对象(不会被Spring容器实例化)将不会受到影响。 该方面配置旨在被添加到以上IoC示例中所示的现有Bean配置中,可以是其他XML文件,也可以是同一文件。 提醒一下,此人的定义如下:

<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方面是通过加载时间编织来应用的:类加载器挂钩修改了要加载的类的字节码,以根据需要应用方面。 AspectJ编译器也了解这些方面,因此可以选择一种实现策略。

Spring 2.0为@AspectJ方面提供了一个附加选项:Spring可以使用其基于代理的AOP运行时将这些方面应用于Spring Bean。

让我们看看如何使用这种样式来实现我们先前示例中的相同功能。

Aspect类包含与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-autoproxy标记告诉Spring自动识别@AspectJ方面,并将它们应用于与它们的切入点匹配的上下文中的任何bean。

在XML和@AspectJ样式之间进行选择

哪种方法(XML或注释)是最好的? 首先,由于注释在各个方面而不是核心业务逻辑中,因此切换的成本并不高。 决定通常取决于完全从Java代码外部化切入点表达式是否有意义。

如果您的方面是特定于领域的,请考虑注释样式:也就是说,切入点和建议是紧密联系的,并且建议不是通用的,并且可能在不同的情况下重复使用。 例如,生日贺卡发送是注释样式的理想选择,因为它特定于特定的应用程序类(人)。 但是,性能监视方面可能会在不同的应用程序中以不同的方式使用,并且建议方法最好与切入点分离,切入点更自然地存在于XML配置中。

在以下情况下使用XML:

  • 您将无法使用Java 5,并且别无选择。 除了处理@AspectJ语法外,Spring 2.0的AOP增强功能还可以在Java 1.3和1.4以及Java 5上运行,尽管您将无法编写与注释或其他Java 5构造匹配的切入点表达式。
  • 您可能想在不同的上下文中使用建议。
  • 您想使用现有代码作为建议,而又不想在其中引入AspectJ注释:例如,引入一种Observer行为,该行为在任意POJO上调用方法。

程序用法

您还可以使用@AspectJ方面以编程方式创建AOP代理,如下所示:

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切入点可以做的很酷的事情

到目前为止,我们所看到的只是表面。

让我们看看以Spring XML @AspectJ样式(或者,当然是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类型,所以在advice方法中不需要强制转换。

类型安全性自然源于此机制。 切勿针对此切入点定向的建议使用错误类型的参数或不匹配的参数来不适当地调用。

为了举例说明该机制的优越性,以下是在Spring 1.x MethodBeforeAdvice中的外观。 与AOP Alliance MethodInterceptor(在Spring 1.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切入点来消除MethodBeforeAdvice中的防护,但是如前所述,这可能需要编写Java代码。 在拦截器中使用防护比使用切入点慢,因为它不允许AOP运行时优化永远无法调用的建议。

在这里,我们可以看到真正的AOP从拦截中去除了多少,以及为什么它既简单又强大。 EJB 3.0的拦截能力比Spring的第一代AOP功能差得多,因为它缺乏真正的切入点机制,这意味着ClassCastException和ArrayIndexOutOfBoundsException都可能是风险。 也有必要使用“周围”建议(一个拦截器)而不是“之前”建议,因为EJB 3.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是公共的,具有非空的return(!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" );
}

但是,现在事情变得有趣了。 在这里,我们将建议应用于通过将两个切入点与单个表达式相加并返回int的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:

@Aspectpublic 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编译器或weaver。

如果您有非常复杂的切入点表达式,则使用AspectJ库方面是最佳实践,因为在这种情况下,AspectJ语言和工具支持非常有吸引力。

最佳实践

那么这对Spring用户意味着什么呢?

希望您同意AOP解决了企业软件中的重要问题,并且您已经意识到AspectJ编程模型与拦截或AOP替代方案相比具有更强大,更优雅的功能。

您不需要将现有的Spring MethodInterceptor或其他建议实现迁移到新的编程模型:它们仍然可以正常工作。 但是,展望未来,应该首选新的编程模型,并且更具吸引力。

如果您使用ProxyFactoryBean或TransactionProxyFactoryBean一次配置一个代理,您会发现自动代理(在Spring 2.0中变得更容易,更自然)可以显着减少Spring配置的数量,并更好地在团队成员之间分配工作。

运行时是什么样的?

尽管使用模型看起来有所不同,但重要的是要记住,概念(连接点,切入点和建议)与Spring自2003年以来实现的Spring AOP和AOP Alliance API完全相同。SpringAOP始终具有用于这些概念的接口。

也许更令人惊讶的是,其实现实际上与幕后几乎相同。 利用AspectJ构造的新编程模型是在现有Spring AOP运行时之上构建的。 Spring AOP一直非常灵活,因此不需要任何重大更改。 (与Spring 1.x一样,org.springframework.aop.framework.Advised接口仍可用于查询和修改AOP代理的状态。)

这意味着您可以将AOP Alliance和Spring AOP方面与AspectJ样式的方面混合使用和匹配:如果要利用现有方面,则尤其重要。

让我们在程序化代理创建示例中进行说明。 我们将添加传统的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仍与Java 1.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如何将varargs用于绑定变量,这意味着开发人员可以在不使用数组的情况下提供任意数量的变量:

simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT WHERE TYPE=? AND CURRENCY=?",
13, "GBP"
);

另外一个好处是,我们不再需要区分具有绑定变量的情况和没有绑定变量的情况。 尽管这在JdbcTemplate上需要两个方法,但是为了避免需要将空的Object数组传递给采用文字SQL的重载方法,而使用SimpleJdbcTemplate,框架代码可以检查varargs的长度。 因此,以下示例调用相同的方法:

simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM T_CLIENT");

还有更重要的好处。 泛型使签名更清晰并消除强制转换。 例如,JdbcTemplate上的queryForMap()方法返回ResultSet中从列名到列值的Map。 当它是SimpleJdbcTemplate上方法签名的显式部分时,这将变得更加清晰:

public Map<String, Object> queryForMap(String sql, Object... args)
throws DataAccessException

使用返回此类地图列表的方法,这仍然更加清楚:

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。

注解

在Spring 1.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还可以通过配置RequiredAnnotationBeanPostProcessor来强制使用其他注释来指示必需的属性。
  • @Repository:将DAO对象标识为代表存储库模式(以域驱动设计术语)。 Spring 2.0提供了一个方面(PersistenceExceptionTranslationAdvisor),可以自动将使用@Repository注释的对象中的技术特定异常转换为Spring的通用DataAccessException层次结构。

用于JPA测试的Spring集成测试超类,例如新的AbstractJpaTest和通用超类AbstractAnnotationAwareTransactionalTests,现在提供对注解的支持,例如@Repeat(导致重复测试)和@ExpectedException(指示测试应抛出特定异常) ,如果没有,则失败)。 不幸的是,由于JUnit 3的设计基于具体的继承,因此这些有用的注释不适用于使用Spring的其他测试。 随着对JUnit 4的使用增加,我们将提供集成测试的一个版本,该版本应该能够向其他用户开放此功能。

如果要解释自己的注释怎么办? 当然,在这种情况下,Spring的许多扩展挂钩将有所帮助。 例如,您可以编写BeanPostProcessor来标识具有给定批注的方法,其方式与RequiredAnnotationBeanPostProcessor的工作方式相同。 即将发布的WebLogic 10中使用的Pitchfork项目( http://www.interface21.com/pitchfork )使用这些扩展点在Spring顶部实现了JSR-250注释和EJB 3.0拦截注释。

还值得注意的是,Spring 2.0中提供的AspectJ切入点表达语言具有极其强大的注释匹配。 编写针对注释的切入点很容易。 例如,以下切入点表达式将匹配Spring框架包中带有注释的任何方法:

execution(@(org.springframework..*) * *(..))

以下表达式将匹配任何使用@Transactional注释进行注释的类:

@within(org.springframework.transaction.annotation.Transactional)

AspectJ 5是AspectJ语言的主要扩展,并且是为了跟上基础语言的发展而付出的巨大努力,并且切入点表达式还可以匹配其他Java 5构造,例如泛型和varargs。

了解更多

在本系列的下一篇文章中,我将讨论:

  • 动态语言支持,以及Spring组件模型如何在Spring 2.0中变成跨语言。
  • 消息和异步调用支持。
  • 数据访问以及与新的Java Persistence API(JPA)的集成。
  • Spring MVC增强功能主要集中在易用性上。
  • 新的Spring Portlet MVC框架。
  • 域对象依赖注入的新可能性。

同时,要阅读有关我今天讨论的主题的更多信息,我建议使用以下资源:

  • Spring参考手册,一直很好,但是在Spring 2.0中得到了极大的改进。 http://static.springframework.org/spring/docs/2.0.x/reference/index.html
  • 为了更好地理解AspectJ的功能,有几本不错的AspectJ书。 我推荐Ramnivas Laddad的《 AspectJ in Action》(Manning,2003年)和Adrian Colyer,Andy Clement,George Harley和Matthew Webster的Eclipse AspectJ(Addison-Wesley,2005年)。
  • 为了理解AspectJ的变化,有许多书籍正在编写中,但是与此同时,AspectJ开发工具包开发人员的笔记本非常有用,尤其是在“基于注释的开发风格”一章中。 参见http://www.eclipse.org/aspectj/doc/released/adk15notebook/index.html

可以在此处下载本文随附的示例代码。

翻译自: https://www.infoq.com/articles/spring-2-intro/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值