Spring2.5的新特性

第一部分

简介

从诞生之初,Spring框架就坚守它的宗旨:简化企业级应用开发,同时给复杂问题提供强大的、非侵入性解决方案。一年前发布的Spring2.0就把这些主题推到了一个新的高度。XML Schema的支持和自定义命名空间的使用大大减少了基于XML的配置。使用Java5及更新版本java的开发人员如今可以利用植入了像泛型(generic)和注解等新语言特性的Spring库。最近,和AspectJ表达式语言的紧密集成,使得以非侵入方式添加跨越定义良好的Spring管理对象分组的行为成为可能。

Spring支持JSR-250注解

Java EE5中引入了“Java平台的公共注解(Common Annotations for the Java Platform)”,而且该公共注解从Java SE 6一开始就被包含其中。 2006年5月,BEA系统宣布了他们在一个名为Pitchfork的项目上与Interface21的合作,该项目提供了基于Spring的Java EE 5编程模型的实现,包括支持用于注入(injection)、拦截( interception)和事务处理(transactions)的JSR-250注解和EJB 3注解(JSR-220)。 在2.5版本中,Spring框架的核心(core)现在支持以下JSR-250注解:

  • @Resource
  • @PostConstruct
  • @PreDestroy

结合Spring,这些注解在任何开发环境下都可以使用——无论是否有应用程序服务器——甚至是集成测试环境都可以。激活这样的支持仅仅是注册一个单独的Spring post-processor的事情:

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/> 

@Resource注解

@Resource 注解被用来激活一个命名资源(named resource)的依赖注入,在JavaEE应用程序中,该注解被典型地转换为绑定于JNDI context中的一个对象。 Spring确实支持使用@Resource通过JNDI lookup来解析对象,默认地,拥有与@Resource注解所提供名字相匹配的“bean name(bean名字)”的Spring管理对象会被注入。 在下面的例子中,Spring会向加了注解的setter方法传递bean名为“dataSource”的Spring管理对象的引用。

@Resource(name="dataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

直接使用@Resource注解一个域(field)同样是可能的。通过不暴露setter方法,代码愈发紧凑并且还提供了域不可修改的额外益处。正如下面将要证明的,@Resource注解甚至不需要一个显式的字符串值,在没有提供任何值的情况下,域名将被当作默认值。

@Resource
private DataSource dataSource; // inject the bean named 'dataSource'

该方式被应用到setter方法的时候,默认名是从相应的属性衍生出来,换句话说,命名为'setDataSource'的方法被用来处理名为'dataSource'的属性。

private DataSource dataSource;
@Resource
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

@Resource没有显式提供名字的时候,如果根据默认名字找不到对应的Spring管理对象,注入机制会回滚至类型匹配(type-match)。如果刚好只有一个Spring管理对象符合该依赖的类型,那么它会被注入。通过设置CommonAnnotationBeanPostProcessor‘fallbackToDefaultTypeMatch’属性为“false”(默认值是“true”)可以禁用这一特性。

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
<property name="fallbackToDefaultTypeMatch" value="false"/>
</bean>

正如上文所提到的,在解析标有@Resource注解的依赖时,Spring支持JNDI-lookup。如若要强制对所有使用@Resource注解的依赖进行JNDI lookup,那也只要将CommonAnnotationBeanPostProcessor'alwaysUseJndiLookup' 标识设置为true就可以了(默认值是false)。

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor">
<property name="alwaysUseJndiLookup" value="true"/>
</bean>

另一个选择是,激活指定为‘resource-ref-mappings’的依据全局JNDI名的查找,在@Resource注解内提供‘mappedName’属性。即使目标对象实际上是一个JNDI资源,仍然推荐引入一个Spring管理对象,这样可以提供一个间接层并且因此降低耦合程度。自Spring2.0开始添加命名空间以来,定义一个委托Spring处理JNDI lookup的bean也变得愈发简练:

<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/petclinic"/> 

这个方法的优点在于间接层带来了巨大的部署弹性。比如说,一个单独的系统测试环境应该不再需要JNDI注册。在这种情况下,在系统测试配置中可以提供如下的bean定义:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>

顺便提一下,上面的例子中,实际的JDBC连接属性从一个属性文件(properties file)解析而来,在这个属性文件里,关键字与提供的${占位符}互相对应,这需要注册一个名为PropertyPlaceholderConfigurerBeanFactoryPostProcessor实现来完成。这是具体化那些属性(通常是针对特定环境的属性)常用的技术,这些属性可能比其他配置修改得更为频繁。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"/>
</bean>

Srping2.5中新加入了‘context’命名空间,这个命名空间让我们能够得到更为简洁的方式来实现属性占位符(property placeholder)的配置:

<context:property-placeholder location="classpath:jdbc.properties"/>

生命周期注解:@PostConstruct和@PreDestroy

@PostConstruct@PreDestroy注解分别用来触发Spring的初始化和销毁回调。这个特性在原有基础上得到了扩展,但并没有替代在Spring2.5之前版本中提供的同样的回调的另两个选项。第一个选项是实现Spring的InitializingBeanDisposableBean 接口中的一个或两个。这两个接口都需要一个回调方法的实现(分别是afterPropertiesSet()destroy() )。这种基于接口的方法利用了Spring自动识别任何实现这些接口的Spring管理对象的能力,因而不再需要另外的配置。另一方面,Spring的一个关键目标是尽可能的非侵入。因此,许多Spring用户并不采用实现这些Spring特定接口的方法,而利用第二个选项,那就是提供他们自己的初始化和销毁方法。尽管入侵性小,但缺点在于使用这个方式的话就必须显式声明bean元素的init-methoddestroy-method属性。显式配置有时候是必须的,例如当回调需要在开发人员控制能力之外的代码上被调用的时候。PetClinic应用程序很好地说明了这个场景。当它和JDBC配置一起运行的时候,会用到一个第三方DataSource,并且它显式声明了一个destroy-method。另外要注意到的是,单独的连接池数据源是dataSource的另一个部署选项,并且不需要修改任何代码。

<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>

在使用Spring2.5的过程中,如果一个对象需要调用一个初始化的回调方法的话,这个回调方法可以采用@PostConstruct来注解。例如一个假想的例子,一个后台任务需要在启动的时候就开始对一个文件目录进行轮询:

public class FilePoller {

@PostConstruct
public void startPolling() {
...
}
...
}

类似地,一个在Spring管理对象上用@PreDestroy注解的方法会在这个对象寄宿的应用程序上下文(application context)关闭的时候被调用。

public class FilePoller {

@PreDestroy
public void stopPolling() {
...
}
...
}

在添加了对JSR-250注解的支持以后,现在的Spring2.5结合前面提到的两种生命周期方法的长处。将@PostConstruct@PreDestroy作为方法层注解加入,足可以实现在受Spring管理的上下文(context)中触发回调。换句话说,不需要另外基于XML的配置。同时,这两个注解是Java语言本身的一部分(甚至被包括在Java SE 版本6中),所以无需引入特定Spring包。这两个注解拥有在其他环境中也能理解的标识语义的优点,随着时间的推移,Java开发人员可能会发现这些注解在第三方开发库中被越来越多的运用到。最后,基于注解生命周期回调的其中一个有趣的结果是,不止一个方法可以带有这两个注解中的任何一个,并且所有注解了的方法会被调用。

激活刚刚描述的关于@Resource@PostConstruct@PreDestroy注解的所有行为,正如上文提到的,需要为Spring的CommonAnnotationBeanPostProcessor提供一个bean定义。但另一个更简练的方法则可能是使用2.5中的新的context命名空间:

<context:annotation-config/>

引入这个单个元素将不单单注册一个CommonAnnotationBeanPostProcessor,也会像下文将叙述的那样激活自动装配(autowire)行为。CommonAnnotationBeanPostProcessor也为@WebServiceRef@EJB注解提供支持。这些将在本文系列的第三篇中和Spring2.5为企业集成提供的其他新特性一起讨论。

利用注解来优化细粒度自动装配

涵盖Spring对自动装配支持的文档中常常会提到由于自动装配机制的粗粒度而伴随有很多限制性。Spring2.5之前,自动装配可以通过很多不同的方式来配置:构造器,类型setter,名字setter,或者自动侦测(在该方式中Spring选择自动装配一个构造器或者类型setter)。这些不同的选择确实提供了很大程度的灵活性,但它们中没有一个方法能够提供细粒度控制。换句话说,Spring2.5之前还不可能自动装配某个对象setter方法的特定子集,或者通过类型或名字来自动装配它的一些属性。结果,许多Spring用户意识到将自动装配应用到构建原型和测试中的好处,但当提到在产品中维护和支持系统时,大部分人认为,加入冗长的显式配置对于澄清它所担负的职责是非常值得的。

然而,Spring2.5大幅度地改变了布局。如上文所述,自动配置选项现在已经被扩展,支持JSR-250 @Resource注解来激活在每个方法或域基础上被命名资源的自动装配。然而,@Resource注解若单独使用的话有很多限制。因此,Sring2.5引进了一个名为@Autowired的注解进一步提高控制级别。为激活这里所讲的行为需要注册一个单独的bean定义:

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

另外如上文提到的,context命名空间提供了一个更简明的方法。它将激活本文所讨论的两个post-processor(AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor)和我们在Spring2.0中引入的基于注解的post-processor:RequiredAnnotationBeanPostProcessorPersistenceAnnotationBeanPostProcessor

<context:annotation-config/>

利用@Autowired 注解可以对相应类型注入依赖。域、构造器和方法都可以激活此行为。实际上,aotowired方法并不一定要是setter方法,且可以接受多个参数。下面这个例子是完整的可接受的用法:

@Autowired
public void setup(DataSource dataSource, AnotherObject o) { ... }

默认地,标有@Autowired注解的依赖被认为是必须的。然而,也可以将required属性值设置为false来声明它们中的任何一个。在下面这个例子中,DefaultStrategy只有在context命名空间中没有SomeStrategy类型的Spring管理对象时才能被使用。

@Autowired(required=false)
private SomeStrategy strategy = new DefaultStrategy();

通过类型进行的自动装配明显地在Spring context包含多于一个期望类型的对象的时候造成歧义。默认地,如果一个必须的依赖没不是恰好一个bean与之对应的话,自动装配机制就会失败。同样的,对于任何一个可选属性,如果它拥有一个以上的候选,也都会失败(如果属性可选且没有任何候选可用的话,该属性则会被简单地跳过)。有很多不同的配置选项可以避免这些冲突。

若Context中拥有一个指定类型的一个主关键实例,对这个类型定义的bean定义应该包含‘primary’属性。当Context中含有其他可用实例的时候这个方法就很适用,但那些非主关键实例总是显式配置的。

<bean id="dataSource" primary="true" ... /> 

在需要更多控制的时候,任何autowired的域、构造参数、或者方法参数可以进一步加注@Qualifier注解。qualifier可以包含一个字符串值,在这种情况下,Spring会试图通过名字来找到对应的对象。

@Autowired
@Qualifier("primaryDataSource")
private DataSource dataSource;

@Qualifier作为一个独立注解存在的主要原因是它可以被应用在构造器参数或方法参数上,但上文提到的@Autowired注解只能运用在构造器或方法本身。

@Autowired
public void setup(@Qualifier("primaryDataSource") DataSource dataSource, AnotherObject o) { ... }

事实上,@Qualifier作为一个单独的注解在定制化方面提供了更多的好处。用户自定义的注解在自动装配过程中也可以起到qualifier的作用,最简单的实现方式是在运用自定义注解的同时将@Qualifier作为它的元注解。

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface VetSpecialty { ... }

自定义注解可以选择包含一个值来提供通过名字匹配的功能,但更普遍的用法是将它作为“标记”注解或定义一个对qualifier过程提供一些更多含义的值。例如,下面这个摘录则描绘了一个域,它应该和通过名字匹配得到的结果中合格的对象进行自动装配。

@Autowired
@VetSpecialty("dentistry")
private Clinic dentistryClinic;

在使用XML配置来达到依赖解析的目标时,'qualifier' 子元素可以被加注到bean定义中。在下文的组件扫描部分,我们将呈现一个可供选择的非XML方法。

<bean id="dentistryClinic" class="samples.DentistryClinic">
<qualifier type="example.VetSpecialty" value="dentistry"/>
</bean>

为了避免对@Qualifier注解的任何依赖性,可以在Spring context中提供一个CustomAutowireConfigurer的bean定义并直接注册所有自定义注解类型:

<bean class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.VetSpecialty</value>
</set>
</property>
</bean>

现在,自定义修饰符被显式声明了,就不再需要@Qualifier这个元注解符了。

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface VetSpecialty { ... }

其实,在配置AutowiredAnnotationBeanPostProcessor的时候,取代@Autowired注解都是有可能的。

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor">
<property name="autowiredAnnotationType" value="example.Injected"/>
</bean>

大部分情况下,定义自定义‘标记’注解的能力结合通过名字或其他文法值进行匹配选项,足以完成自动装配过程的细粒度控制。但Spring还支持在qualifier注解上任意数目的任意属性。比如,下面是一个极为细粒度修饰的例子。

@SpecializedClinic(species="dog", breed="poodle")
private Clinic poodleClinic;

自定义修饰符的实现应该定义这些属性:

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface SpecializedClinic {

String species();

String breed();

}

自定义修饰符属性可以匹配那些XML中bean定义的qualifier注解的属性子元素。这些元素通常以键/值对方式提供。

<bean id="poodleClinic" class="example.PoodleClinic">
<qualifier type="example.SpecializedClinic">
<attribute key="species" value="dog"/>
<attribute key="breed" value="poodle"/>
</qualifier>
</bean>

目前为止,关于autowire的描述都只是针对单独的实例,其实也支持集合。在任何需要得到所有context中某种特定类型的Spring管理对象的时候,只需要简单地在一个强类型(strongly-typed)集合上加注@Autowired 注解。

@Autowired
private List<Clinic> allClinics;

本章节最后一个值得指出的特性是自动装配的使用替代了Spring的Aware接口。在Spring2.5之前,如果某个对象需要一个Spring context的ResourceLoader的引用,它可以通过实现ResourceLoaderAware的方式使得Spring通过setResourceLoader(ResourceLoader resourceLoader)方法来提供该依赖。借助同样的方法可以得到Spring管理的MessageSource的引用,甚至可以得到ApplicationContext本身。对于Spring2.5用户而言,这个行为现在通过autowiring得到全面支持(需要指出的是包含这些Spring特定依赖的时候应该考虑周到,特别是它们只能用于从业务逻辑清楚地分割出来的基础构架代码中)。

@Autowired
private MessageSource messageSource;

@Autowired
private ResourceLoader resourceLoader;

@Autowired
private ApplicationContext applicationContext;

自动侦测Spring组件

从2.0版本开始,Spring引入了构造型(stereotype)注解的概念以及将@Repository注解作为数据访问代码的标记的方法。在此基础上,Spring2.5又加入了两个新的注解 —— @Service@Controller 来完成为通常的三层架构(数据访问对象、服务、web控制器)角色委任。Spring2.5也引入了泛型@Component注解,其他构造型可从逻辑上对其进行扩展。通过清晰地指明应用程序的角色,这些构造型方便了Spring AOP和post-processor的使用,这些post-processor给基于这些角色的加了注解的对象提供了附加行为。比如,Spring2.0引入了PersistenceExceptionTranslationPostProcessor对任何带有@Repository 注解的对象自动激活其数据访问异常转换。

这些注解同样可以结合Spring2.5其他一些新性能来使用:自动侦测classpath上的组件。尽管XML已经成为最常见的Spring元数据的格式,但它决不是唯一选择。实际上,Spring容器内的元数据是由纯Java来表示的,当XML被用来定义Spring管理对象时,在实例化过程之前,那些定义会被解析并转化成Java对象。Spring2.5的一个巨大的新功能是支持从源码层注解读取元数据。因而,上文描述的自动装配机制使用注解的元数据来注入依赖,但它仍然需要注册至少一个bean定义以便提供每个Spring管理对象的实现类。组件扫描功能则使得这个XML中最起码的bean定义都不再存在需求性。

正如上面所示,Spring注解驱动的自动装配可以在不牺牲细粒度控制的前提下极大程度地减少XML的使用。组件侦测机制将这个优点更发扬光大。全面替代XML中的配置不再必要,组件扫描反而可以处理XML元数据来简化整体配置。结合XML和注解驱动技术可以得到一个平衡优化的方法,这在2.5版本的PetClinic范例中有详细阐述。在该范例中,基础构架组件(数据源、事务管理等)结合上文提到的外化属性在XML中定义。数据访问层对象也有部分在XML中定义,它们的配置也都利用了@Autowired注解来简化依赖注入。最后,web层控制器完全不在XML中显式定义,相反,下面提供的这段配置被用来触发所有web控制器的自动侦测:

<context:component-scan base-package="org.springframework.samples.petclinic.web"/>

需要注意到的是这段示例中使用到了base-package属性。组件扫描的默认匹配规则会递归侦测该包(多个包可以以逗号分隔的list方式提供)内的所有类的所有Spring构造型注解。正因为如此,PetClinic应用程序范例中的各类控制器的实现都采用了@Controller注解(Spring的内置构造型之一)。请看下面这个例子:

@Controller
public class ClinicController {

private final Clinic clinic;

@Autowired
public ClinicController(Clinic clinic) {
this.clinic = clinic;
}
...

自动侦测组件在Spring容器中注册,就像它们在XML中被定义一样。如上所示,那些对象可以轮流利用注解驱动的自动装配。

组件扫描的匹配规则可以通过过滤器(filter)来自定义,以根据类型、AspectJ表达式、或针对命名模式的正则表达式来决定包含或不包含哪些组件。默认的构造型也可以被禁用。比如这里有一个配置的例子,这个配置会忽略默认的构造型,但会自动侦测名字以Stub打头或者包含@Mock注解的所有类:

<context:component-scan base-package="example" use-default-filters="false">
<context:include-filter type="aspectj" expression="example..Stub*"/>
<context:include-filter type="annotation" expression="example.Mock"/>
</context:component-scan>

类型匹配的限制性也可以用排他的过滤器控制。例如,除了@Repository注解外其他都依赖于默认过滤器,那么就需要加入一个排他过滤器(exclude-filter)。

<context:component-scan base-package="example">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

很明显,有很多方法可以扩展组件扫描来注册自定义的类型。构造型注解是最简单的选择,所以构造型概念本身也是可扩展的。像先前提到的,@Component泛型模型,@Repository@Service,和@Controller注解都从该构造型逻辑扩展而得。正因为如此,@Component可被用来作为元注解(也就是说,在另外的注解上声明的注解),所有具有@Component元注解的自定义注解都会被默认扫描匹配规则自动侦测到。一个例子就有希望让你领会到其实它根本没有听起来那么难。

让我们回想一下在讲@PostConstruct@PreDestroy生命周期注解的时候的假想的后台任务。也许一个应用程序有很多很多这样的后台任务,这些任务实例需要XML bean定义以便在Spring context里注册并使它们自己的生命周期方法在正确时候被调用。利用组件扫描就不再需要这些显式的XML bean定义。如果这些后台任务都实现一个相同的接口或者都沿用同样的命名惯例,那么可以用include-filters。然而,更简单的方法是为这些任务对象创建一个注解并提供@Component元注解。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface BackgroundTask {
String value() default "";
}

然后在所有后台任务的类定义中提供自定义构造型注解。

@BackgroundTask
public class FilePoller {

@PostConstruct
public void startPolling() {
...
}

@PreDestroy
public void stopPolling() {
...
}
...
}

泛型@Component注解可以像例子中提供的那样简单使用,自定义注解技术则提供了一个使用更具涵义的、领域特定的名字的机会。这些领域特定注解提供更深入的机会,比如使用AspectJ切点表达式来识别所有后台任务,以便增加advice来监控这些任务的活动性。

默认的,组件被侦测到的时候,Spring会自动生成一个没有修饰符的类名作为bean名字。上一个例子中,生成的bean名字会是filePoller。但是,任何加注了Spring构造型注解(@Component@Repository@Service@Controller)或是加注了其他的以@Component作为元注解的注解(比如上面例子中的@BackgroundTask )的类,构造型注解的value属性可以被显式指定,实例将该值作为它的bean名字注册到context中。接下来的例子里,实例名应该是petClinic而不是默认生成的名字simpleJdbcClinic。

@Service("petClinic")
public class SimpleJdbcClinic {
...
}

同样的,在下面修正版的FilePoller例子里,生成的bean名字应该是poller而不是filePoller。

@BackgroundTask("poller")
public class FilePoller {
...
}

虽然所有Spring管理对象都被默认地当作单例实例来处理,但有些时候还是有必要为某个对象指明一个备用的范围(scope)。举个例子来说,在web层,一个Spring管理对象可能捆绑到request或session的范围。对于2.0版本,Spring的scope机制更具延展性,这样一来,自定义scope可以被注册到应用程序上下文(application context)。在XML配置中,仅仅是简单地包含进scope属性及该scope的名字就可以了。

<bean id="shoppingCart" class="example.ShoppingCart" scope="session">
...
</bean>

Spring2.5中,为被扫描的组件提供@Scope注解可以起到同样的作用。

@Component
@Scope("session")
public class ShoppingCart {
...
}

这里要指出的最后一点是使用组件扫描时qualifier注解应用是多么的简单。在上一节,下面这个对象曾被作为使用自定义qualifier注解进行自动装配的例子:

@VetSpecialty("dentistry")
private Clinic dentistryClinic;

同样的例子接着展现了在XML内使用‘qualifier’元素为依赖提供指定目标bean定义。在使用组件扫描时,XML元数据不是必须的。但自定义修饰符也许在目标类定义中被作为类型层注解而引入。另一个将被扫描的@Repository实例作为依赖的例子如下:

@Repository
@VetSpecialty("dentistry")
public class DentistryClinic implements Clinic {
...
}

最终,因为前面的例子展现了自定义注解及其属性的例子,相等同的非XML表示依赖目标的方法如下:

@Repository
@SpecializedClinic(species="dog", breed="poodle")
public class PoodleClinic implements Clinic {
...
}

小结

Spring2.5在很多方面都提供了很有意义的新功能。本文主要关注于怎样通过掌控Java注解的力量将配置简化。就如在JSR-250中定义的那样,Spring支持公共注解(Common Annotations),同时为自动装配过程的更细粒度的控制提供了额外注解。Spring2.5也扩展了从Spring2.0的@Repository就开始的构造型(stereotype)注解,并且所有这些构造型注解都可以和新的组件扫描功能结合使用。Spring2.5仍然全面支持基于XML的配置,同时它又引进了一个新的context命名空间对常见配置场景提供更精要的文法。实际上,支持XML和基于注解配置的无缝结合最终产生一个更为平衡的全面的方法。基本构架的复杂配置可以在模块XML文件中定义,而应用程序栈日益增多地更高层配置可以更多的从基于注解的技术中获益——前提是都在同一个Spring2.5应用程序context内。

第二部分

Spring 框架从创建伊始就致力于为复杂问题提供强大的、非侵入性的解决方案。Spring 2.0当中为缩减XML配置文件数量引入定制命名空间功能,从此它便深深植根于核心Spring框架(aop、context、jee、jms、 lang、tx和util命名空间)、Spring Portfolio项目(例如Spring Security)和非Spring项目中(例如CXF)。

Spring 2.5推出了一整套注解,作为基于XML的配置的替换方案。注解可用于Spring管理对象的自动发现、依赖注入、生命周期方法、Web层配置和单元/集成测试。

探索Spring 2.5中引入的注解技术系列文章由三部分组成,本文是其中的第二篇,它主要讲述了Web层中的注解支持。最后一篇文章将着重介绍可用于集成和测试的其它特性。

这个系列文章的第一部分论述了Java注解(annotation)是如何代替XML来配置Spring管理对象和依赖注入的。我们再用一个例子回顾一下:

Java代码 复制代码
  1. @Controller
  2. publicclassClinicController{
  3. privatefinalClinicclinic;
  4. @Autowired
  5. publicClinicController(Clinicclinic){
  6. this.clinic=clinic;
  7. }
  8. ...
@Controller
 public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
       this.clinic = clinic;
    }
    ...



@Controller表明ClinicController是Web层组件,@Autowired请求一个被依赖注入的Clinic实例。这个例子只需要少量的XML语句就能使容器识别两个注解,并限定组件的扫描范围:

Java代码 复制代码
  1. <context:component-scanbase-package="org.springframework.samples.petclinic"/>
<context:component-scan base-package="org.springframework.samples.petclinic"/>



这对Web层可谓是个福音,因为在这层Spring的XML配置文件已日益臃肿,甚至可能还不如层下的配置来得有用。控制器掌握着许多属性,例如视图名称、表单对象名称和验证器类型,这些多是关乎配置的,甚少关于依赖注入的。通过bean定义继承,或者避免配置变化不是很频繁的属性,也可以有效的管理类似的配置。不过以我的经验,很多开发人员都不会这样做,结果就是XML文件总比实际需要的要庞大。不过 @Controller和@Autowired对Web层的配置会产生积极的作用。

在系列文章的第二部分我们将继续讨论这个问题,并浏览Spring 2.5在Web层的注解技术。这些注解被非正式的称为@MVC,它涉及到了Spring MVC和Spring Porlet MVC,实际上本文讨论的大部分功能都可以应用在这两个框架上。
从Controller到@Controller

与第一部分讨论的注解相比,@MVC已不只是作为配置的一种替换方案这样简单了,考虑下面这个著名的Spring MVC控制器签名:

Java代码 复制代码
  1. publicinterfaceController{
  2. ModelAndViewhandleRequest(HttpServletRequestrequest,HttpServletResponse
  3. response)throwsException;
  4. }
public interface Controller {
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse 
response) throws Exception;
 }



所有的Spring MVC控制器要么直接实现Controller接口,要么就得扩展类似AbstractController、 SimpleFormController、 MultiActionController或AbstractWizardFormController这样的基类实现。正是Controller接口允许Spring MVC的DispatcherServlet把所有上述对象都看作是“处理器(handlers)”,并在一个名为 SimpleControllerHandlerAdapter的适配器的帮助下调用它们。

@MVC从三个重要的方面改变了这个程序设计模型:

1. 不需要任何接口或者基类。
2. 允许有任意数量的请求处理方法。
3. 在方法签名上具有高度的灵活性。

考虑到以上三个要点,就可以说很公平的说@MVC不仅仅是个替换方案了,它将会是Spring MVC的控制器技术演变过程中下一个重要步骤。

DispatcherServlet在名为AnnotationMethodHandlerAdapter的适配器帮助下调用被注解的控制器。正是这个适配器做了大量工作支持我们此后将会讨论的注解,同时也是它有效的取代了对于控制器基类的需求。
@RequestMapping简介

我们还是从一个类似于传统的Spring MVC Controller控制器开始:

Java代码 复制代码
  1. @Controller
  2. publicclassAccountsController{
  3. privateAccountRepositoryaccountRepository;
  4. @Autowired
  5. publicAccountsController(AccountRepositoryaccountRepository){
  6. this.accountRepository=accountRepository;
  7. }
  8. @RequestMapping("/accounts/show")
  9. publicModelAndViewshow(HttpServletRequestrequest,
  10. HttpServletResponseresponse)throwsException{
  11. Stringnumber=ServletRequestUtils.getStringParameter(request,"number");
  12. ModelAndViewmav=newModelAndView("/WEB-INF/views/accounts/show.jsp");
  13. mav.addObject("account",accountRepository.findAccount(number));
  14. returnmav;
  15. }
@Controller
 public class AccountsController {

    private AccountRepository accountRepository;

    @Autowired
    public AccountsController(AccountRepository accountRepository) {
       this.accountRepository = accountRepository;
    }

    @RequestMapping("/accounts/show")
    public ModelAndView show(HttpServletRequest request,
                             HttpServletResponse response) throws Exception {
       String number = ServletRequestUtils.getStringParameter(request, "number");
       ModelAndView mav = new ModelAndView("/WEB-INF/views/accounts/show.jsp");
       mav.addObject("account", accountRepository.findAccount(number));
       return mav;
    }


}

此处与以往的不同在于,这个控制器并没有扩展Controller接口,并且它用@RequestMapping注解指明show()是映射到 URI路径 “/accounts/show”的请求处理方法。除此以外,其余代码都是一个典型的Spring MVC控制器应有的内容。

在将上述的方法完全转化到@MVC后,我们会再回过头来看@RequestMapping,但是在此之前还有一点需要提请注意,上面的请求映射URI也可匹配带有任意扩展名的URI路径,例如:

Java代码 复制代码
  1. /accounts/show.htm
  2. /accounts/show.xls
  3. /accounts/show.pdf
  4. ...
/accounts/show.htm
 /accounts/show.xls
 /accounts/show.pdf
 ...



灵活的请求处理方法签名

我们曾经承诺过要提供灵活的方法签名,现在来看一下成果。输入的参数中移除了响应对象,增加了一个代表模型的Map;返回的不再是ModelAndView,而是一个字符串,指明呈现响应时要用的视图名字:

Java代码 复制代码
  1. @RequestMapping("/accounts/show")
  2. publicStringshow(HttpServletRequestrequest,Map<String,Object>model)
  3. throwsException{
  4. Stringnumber=ServletRequestUtils.getStringParameter(request,"number");
  5. model.put("account",accountRepository.findAccount(number));
  6. return"/WEB-INF/views/accounts/show.jsp";
  7. }
@RequestMapping("/accounts/show")
 public String show(HttpServletRequest request, Map<String, Object> model)
 throws Exception {
    String number = ServletRequestUtils.getStringParameter(request, "number");
    model.put("account", accountRepository.findAccount(number));
    return "/WEB-INF/views/accounts/show.jsp";
 }



Map输入参数是一个“隐式的”模型,对于我们来说在调用方法前创建它很方便,其中添加的键—值对数据便于在视图中解析应用。本例视图为show.jsp页面。

@MVC可以接受多种类型的输入参数,例如 HttpServletRequest/HttpServletResponse、HttpSession、Locale、InputStream、 OutputStream、File[]等等,它们的顺序不受任何限制;同样它也允许多种返回类型,例如ModelAndView、Map、 String,或者什么都不返回。你可以查看@RequestMapping的JavaDoc以了解它支持的所有输入和返回参数类型。

有种令人感兴趣的情形是当方法没有指定视图时(例如返回类型为void)会有什么事情发生,按照惯例DispatcherServlet要再使用请求URI的路径信息,不过要移去前面的斜杠和扩展名。让我们把返回类型改为void:

Java代码 复制代码
  1. @RequestMapping("/accounts/show")
  2. publicvoidshow(HttpServletRequestrequest,Map<String,Object>model)throwsException{
  3. Stringnumber=ServletRequestUtils.getStringParameter(request,"number");
  4. model.put("account",accountRepository.findAccount(number));
  5. }
@RequestMapping("/accounts/show")
 public void show(HttpServletRequest request, Map<String, Object> model) throws Exception {
    String number = ServletRequestUtils.getStringParameter(request, "number");
    model.put("account", accountRepository.findAccount(number));
 }



对于给定的请求处理方法和“/accounts/show”的请求映射,我们可以期望DispatcherServlet能够获得“accounts/show”的默认视图名称,当它与如下适当的视图解析器结合共同作用时,会产生与前面指明返回视图名同样的结果:

Java代码 复制代码
  1. <beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver">
  2. <propertyname="prefix"value="/WEB-INF/views/"/>
  3. <propertyname="suffix"value=".jsp"/>
  4. </bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
 </bean>



强烈推荐视图名称依赖惯例的方式,因为这样可以从控制器代码中消除硬编码的视图名称。如果你想定制 DispatcherServlet获取默认视图名的方式,就在servlet上下文环境中配置一个你自己的 RequestToViewNameTranslator实现,并为其bean id赋名为“viewNameTranslator”。
用@RequestParam提取和解析参数

@MVC另外一个特性是其提取和解析请求参数的能力。让我们继续重构上面的方法,并在其中添加@RequestParam注解:

Java代码 复制代码
  1. @RequestMapping("/accounts/show")
  2. publicvoidshow(@RequestParam("number")Stringnumber,Map<String,Object>model){
  3. model.put("account",accountRepository.findAccount(number));
  4. }
@RequestMapping("/accounts/show")
 public void show(@RequestParam("number") String number, Map<String, Object> model) {
    model.put("account", accountRepository.findAccount(number));
 }


这里@RequestParam注解可以用来提取名为“number”的String类型的参数,并将之作为输入参数传入。 @RequestParam支持类型转换,还有必需和可选参数。类型转换目前支持所有的基本Java类型,你可通过定制的PropertyEditors 来扩展它的范围。下面是一些例子,其中包括了必需和可选参数:

Java代码 复制代码
  1. @RequestParam(value="number",required=false)Stringnumber
  2. @RequestParam("id")Longid
  3. @RequestParam("balance")doublebalance
  4. @RequestParamdoubleamount
@RequestParam(value="number", required=false) String number
 @RequestParam("id") Long id
 @RequestParam("balance") double balance
 @RequestParam double amount



注意,最后一个例子没有提供清晰的参数名。当且仅当代码带调试符号编译时,结果会提取名为“amount ”的参数,否则,将抛出IllegalStateException异常,因为当前的信息不足以从请求中提取参数。由于这个原因,在编码时最好显式的指定参数名。
继续@RequestMapping的讨论

把@RequestMapping放在类级别上是合法的,这可令它与方法级别上的@RequestMapping注解协同工作,取得缩小选择范围的效果,下面是一些例子。

类级别:

Java代码 复制代码
  1. RequestMapping("/accounts/*")
  2. 方法级别:
  3. @RequestMapping(value="delete",method=RequestMethod.POST)
  4. @RequestMapping(value="index",method=RequestMethod.GET,params="type=checking")
  5. @RequestMapping
RequestMapping("/accounts/*")

方法级别:
@RequestMapping(value="delete", method=RequestMethod.POST)
@RequestMapping(value="index", method=RequestMethod.GET, params="type=checking")
@RequestMapping



第一个方法级的请求映射和类级别的映射结合,当HTTP方法是POST时与路径“/accounts/delete”匹配;第二个添加了一个要求,就是名为“type”的请求参数和其值“checking”都需要在请求中出现;第三个根本就没有指定路径,这个方法匹配所有的 HTTP方法,如果有必要的话可以用它的方法名。下面改写我们的方法,使它可以依靠方法名进行匹配,程序如下:

Java代码 复制代码
  1. @Controller
  2. @RequestMapping("/accounts/*")
  3. publicclassAccountsController{
  4. @RequestMapping(method=RequestMethod.GET)
  5. publicvoidshow(@RequestParam("number")Stringnumber,Map<String,Object&
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值