25.6. 使用属性来减少MVC web层配置

Spring Framework

25.6. 使用属性来减少MVC web层配置

Spring元数据从1.0开始的另一个主要用处就是提供简化Spring MVC web层配置的方案。

Spring MVC提供了处理类映射的灵活性:将输入的请求映射到控制器(或者其它处理类)的实例上。 通常处理类映射是在相关的SpringDispatcherServletxxxx-servlet.xml 文件中配置的。

将这些映射保存在DispatcherServlet配置文件中通常是一个好主意。它提供了最大的灵活性,特别是:

  • 控制器实例通过XML bean定义由Spring IoC显式管理。

  • 因为映射在控制器之外,所以同一个控制器实例可以在一个DispatcherServlet上下文中获得多个映射,或者在不同的配置中重用。

  • Spring MVC可以支持基于任何标准的映射,而不仅仅是其它很多框架中支持的请求URL到控制器的映射。

然而,这的确意味着对于每一个控制器,我们都同时需要一个控制器映射(通常在一个控制器映射XML的bean定义中)和一个控制器自己的XML映射。

Spring提供了一种基于源码级属性的简单方式,这在很简单的场景中是很引人注目的选择。

本节描述的方式最适合简单的MVC相关场景。这也牺牲了一些Spring MVC的能力,比如在不同的映射中使用相同的控制器的能力,和基于请求URL之外的其它映射的能力。

这种方式中,控制器标识了一个或多个类级别的元数据属性,每一个都指定一个它们会被映射到的URL。

下面的例子展示了这种方式。在每个例子中,我们都会有一个依赖于Cruncher 类型的业务对象的控制器。 同样,这个依赖通过依赖注入解决。这个Cruncher需要通过相关的DispatcherServlet XML文件或上级上下文的bean定义中获取。

我们给这个控制器类绑定了一个指定需要映射的URL的属性。我们将这种依赖通过一个JavaBean属性或构造器参数来传递。 这个依赖一定要能够通过自动配置来解决:也就是说,在上下文中一定正好有一个Cruncher类型的业务对象。

/**
 * Normal comments here
 *
 * @@org.springframework.web.servlet.handler.metadata.PathMap("/bar.cgi")
 */
public class BarController extends AbstractController {

    private Cruncher cruncher;

    public void setCruncher(Cruncher cruncher) {
        this.cruncher = cruncher;
    }

    protected ModelAndView handleRequestInternal (
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("Bar Crunching c and d =" + cruncher.concatenate("c", "d"));
        return new ModelAndView("test");
    }
}

要让这个自动映射能生效,我们需要将下面的内容加到相关的 xxxx-servlet.xml 文件中,以指定属性处理器的映射关系。 这个特定的处理器映射能够处理任意多的带有上文的属性的控制器。这个bean的id("commonsAttributesHandlerMapping")并不重要,类型才是关键:

<bean id="commonsAttributesHandlerMapping"      
    class="org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping"/>

在上面的例子中,我们现在不需要一个Attributes bean定义。这是因为这个类直接通过Commons Attributes API运行,而不是通过Spring的元数据抽象。

我们现在不需要为每个控制器指定XML配置。控制器会被自动映射到指定的URL。它们通过Spring的自动匹配能力从IoC中获益。 例如,上面展示的简单控制器的"cruncher" bean属性中的依赖,就是在当前的web应用上下文中自动获取的。Setter和Constructor依赖注入都可以实现零配置。

一个支持多个URL路径的构造器注入的例子:

/**
 * Normal comments here
 *
 * @@org.springframework.web.servlet.handler.metadata.PathMap("/foo.cgi")
 * @@org.springframework.web.servlet.handler.metadata.PathMap("/baz.cgi")
 */
public class FooController extends AbstractController {

    private Cruncher cruncher;

    public FooController(Cruncher cruncher) {
        this.cruncher = cruncher;
    }

    protected ModelAndView handleRequestInternal (
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        return new ModelAndView("test");
    }
}

这个方式有下面一些好处:

  • 显著的减少了配置工作量。每次增加一个控制器,我们需要增加XML配置。通过属性驱动的事务管理,一旦建立了基本的基础设施,就非常容易增加更多的应用类。

  • 我们保留了很多Spring IoC的能力来配置控制器。

这个方式有以下一些局限性:

  • 一个更复杂的构建进程中的一次性开销。我们需要一个属性编译步骤和一个属性索引步骤。然而,一旦构建好了,这就不应该成为问题。

  • 现在只支持Commons Attributes,虽然将来可能会增加对其它属性提供者的支持。

  • 这种控制器只支持"根据类型自动匹配"的依赖注入。即使这样,它们也比Struts Action(框架中没有IoC支持)和WebWork Action(只有原始的IoC支持)要先进得多。

  • 依靠IoC自动解析可能容易引起混乱。

因为根据类型自动匹配意味着必须要有一个依赖于特定类型的bean,如果我们使用AOP一定要小心。例如,在使用TransactionProxyFactoryBean的常见情形中, 我们碰到两个Cruncher这样的业务接口的实现:原始的POJO定义和事务AOP代理。因为应用上下文不能清晰地解析依赖的类型, 这肯定不能运行。解决方案是使用AOP自动代理,构建好自动代理基础设施保证只定义了一个Cruncher 的实现,这个实现被自动通知。从而这个 方式能够象上文描述的那样与声明式面向属性的服务良好协作。这样也很容易构建,因为属性编译步骤必须恰当的去管理web控制器目标。

与其它的元数据功能不同的是,目前只有一个可用的Commons Attributes实现:org.springframework.web.servlet.handler.metadata.CommonsPathMapHandlerMapping。 这个局限是因为我们不仅仅需要属性编译,也需要属性索引:从属性API获得所有带有PathMap属性的类。 org.springframework.metadata.Attributes抽象接口目前还没有提供 索引功能, 也许将来会提供。(如果你希望增加对另外的属性实现的支持 - 它一定要支持索引 - 你 可以方便地扩展CommonsPathMapHandlerMapping类的父类 AbstractPathMapHandlerMapping,然后实现2个protected abstract方法,以使用你感兴趣的属性API)

总的来说,我们在构建过程中需要两个额外的步骤:属性编译和属性索引。前面已经讲解了属性索引任务的使用。请注意,Commons Attributes目前需要一个jar文件作为索引的输入。