25. Portlet MVC 框架

25.1 Introduction

JSR-286 The Java Portlet Specification

有关 portlet 开发的更多常规信息,请查看JSR-286 Specification本身。

除了支持常规的(基于 servlet 的)Web 开发之外,Spring 还支持 JSR-286 Portlet 开发。 Portlet MVC 框架尽可能地是 Web MVC 框架的镜像,并且还使用相同的基础视图抽象和集成技术。因此,在 continue 阅读本章之前,请务必阅读标题为第 22 章,Web MVC 框架第 23 章,查看技术的章节。

Note

请记住,虽然 Spring MVC 的概念在 Spring Portlet MVC 中是相同的,但 JSR-286 Portlet 的独特工作流却产生了一些显着差异。

Portlet 工作流与 Servlet 工作流不同的主要方式是,对 Portlet 的请求可以具有两个不同的阶段:操作阶段和呈现阶段。操作阶段仅执行一次,并且在此阶段发生任何“后端”更改或操作,例如在数据库中进行更改。然后,渲染阶段会在每次刷新显示时生成显示给用户的内容。这里的关键点是,对于单个整体请求,动作阶段仅执行一次,但是渲染阶段可以执行多次。这在修改系统持久状态的活动与生成向用户显示的内容的活动之间提供了(并要求)明确区分。

Spring Web Flow

Spring Web Flow(SWF)旨在成为 ManagementWeb 应用程序页面流的最佳解决方案。

SWF 与 Servlet 和 Portlet 环境中的现有框架(如 Spring MVC 和 JSF)集成。如果您的业务流程(一个或多个流程)将从会话模型(而不是纯粹的请求模型)中受益,那么 SWF 可能是解决方案。

SWF 允许您将逻辑页面流捕获为可在不同情况下重用的自包含模块,因此非常适合构建 Web 应用程序模块,以通过驱动业务流程的受控导航来指导用户。

有关 SWF 的更多信息,请访问 Spring Web Flow 网站。

Portlet 请求的两个阶段是 JSR-286 规范的 true 优势之一。例如,动态搜索结果可以在显示器上例行更新,而无需用户明确地重新运行搜索。大多数其他 Portlet MVC 框架都试图完全向开发人员隐藏这两个阶段,并使其看起来尽可能地类似于传统的 Servlet 开发-我们认为这种方法消除了使用 Portlet 的主要好处之一。因此,在整个 Spring Portlet MVC 框架中保留了两个阶段的分离。这种方法的主要体现是,在 MVC 类的 Servlet 版本将具有一种处理请求的方法的情况下,MVC 类的 Portlet 版本将具有两种处理请求的方法的方法:一种用于操作阶段,一种用于处理请求。用于渲染阶段。例如,如果AbstractController的 servlet 版本具有handleRequestInternal(..)方法,则AbstractController的 portlet 版本具有handleActionRequestInternal(..)handleRenderRequestInternal(..)方法。

该框架围绕DispatcherPortlet进行设计,该DispatcherPortlet像 Web 框架中的DispatcherServlet一样,使用可配置的处理程序 Map 和视图分辨率将请求分发给处理程序。同样,也支持文件上传。

Portlet MVC 不支持语言环境解析和主题解析-这些区域在门户网站/ Portlet 容器的权限范围内,不适用于 Spring 级别。但是,Spring 中所有依赖于语言环境的机制(例如消息的国际化)仍将正常运行,因为DispatcherPortlet以与DispatcherServlet相同的方式公开当前语言环境。

25.1.1 控制器-MVC 中的 C

默认处理程序仍然是一个非常简单的Controller接口,仅提供两种方法:

  • void handleActionRequest(request,response)

  • ModelAndView handleRenderRequest(request,response)

该框架还包括大多数相同的控制器实现层次结构,例如AbstractControllerSimpleFormController等。数据绑定,命令对象使用,模型处理和视图解析与 servlet 框架中的相同。

25.1.2 视图-MVC 中的 V

Servlet 框架的所有视图呈现功能都可以通过名为ViewRendererServlet的特殊 bridgeServlet 直接使用。通过使用此 Servlet,Portlet 请求将转换为 Servlet 请求,并且可以使用整个常规 Servlet 基础结构来呈现视图。这意味着所有现有的渲染器,例如 JSP,Velocity 等,仍可以在 portlet 中使用。

25.1.3 Web 范围的 bean

Spring Portlet MVC 支持其生命周期范围为当前 HTTP 请求或 HTTP Session(常规和全局)的 bean。这不是 Spring Portlet MVC 本身的特定功能,而是 Spring Portlet MVC 使用的WebApplicationContext容器。 第 7.5.4 节“请求,会话,全局会话,应用程序和 WebSocket 范围”中详细描述了这些 bean 范围。

25.2 DispatcherPortlet

Portlet MVC 是一个请求驱动的 Web MVC 框架,围绕 Portlet 设计,该 Portlet 将请求分派给控制器,并提供其他功能来促进 Portlet 应用程序的开发。 Spring 的DispatcherPortlet不仅能做到这一点。它与 Spring ApplicationContext完全集成在一起,并允许您使用 Spring 具有的所有其他功能。

像普通的 portlet 一样,DispatcherPortlet在 Web 应用程序的portlet.xml文件中声明:

<portlet>
    <portlet-name>sample</portlet-name>
    <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
    <supports>
        <mime-type>text/html</mime-type>
        <portlet-mode>view</portlet-mode>
    </supports>
    <portlet-info>
        <title>Sample Portlet</title>
    </portlet-info>
</portlet>

现在需要配置DispatcherPortlet

在 Portlet MVC 框架中,每个DispatcherPortlet都有自己的WebApplicationContext,它们继承了 Root WebApplicationContext中已经定义的所有 bean。可以在特定于 Portlet 的范围中覆盖这些继承的 Bean,并且可以在给定 Portlet 实例本地定义新的特定于范围的 Bean。

初始化DispatcherPortlet时,框架将在 Web 应用程序的WEB-INF目录中查找名为[portlet-name]-portlet.xml的文件,并在其中创建定义的 bean(覆盖在全局范围内使用相同名称定义的任何 bean 的定义)。

DispatcherPortlet所使用的配置位置可以通过 Portlet 初始化参数进行修改(有关详细信息,请参见下文)。

Spring DispatcherPortlet使用了一些特殊的 bean,以便能够处理请求并呈现适当的视图。这些 Bean 包含在 Spring 框架中,可以在WebApplicationContext中进行配置,就像配置其他任何 Bean 一样。这些 bean 的每一个将在下面更详细地描述。现在,我们仅提及它们,只是为了让您知道它们的存在,并使我们能够 continue 讨论DispatcherPortlet。对于大多数 bean,都提供了默认值,因此您不必担心对其进行配置。

表 25.1 WebApplicationContext 中的特殊 bean

ExpressionExplanation
handler mapping(s)(第 25.5 节“处理程序 Map”)如果符合特定条件(例如,由控制器指定的匹配 portlet 模式)将执行的预处理器和后处理器以及控制器的列表。
controller(s)(第 25.4 节“控制器”)作为 MVC 三 Tuples 的一部分提供实际功能(或至少访问该功能)的 bean
view resolver(第 25.6 节“视图和解决方法”)能够解析视图名称以查看定义
multipart resolver(第 25.7 节“Multipart(文件上传)支持”)提供了处理从 HTML 表单上传文件的功能
处理程序异常解析器(第 25.8 节“处理异常”)提供了将异常 Map 到视图或实现其他更复杂的异常处理代码的功能

设置DispatcherPortlet以供使用并且针对该特定DispatcherPortlet发出请求时,它将开始处理该请求。以下列表描述了由DispatcherPortlet处理的请求所经过的完整过程:

  • PortletRequest.getLocale()返回的语言环境已绑定到请求,以使流程中的元素解析在处理请求(渲染视图,准备数据等)时要使用的语言环境。

  • 如果指定了 Multipart 解析器且它是ActionRequest,则检查请求中是否有 Multipart,如果找到了 Multipart,则将其包装在MultipartActionRequest中,以供流程中的其他元素进一步处理。 (有关 Multipart 处理的更多信息,请参见第 25.7 节“Multipart(文件上传)支持”)。

  • 搜索适当的处理程序。如果找到处理程序,则将执行与该处理程序关联的执行链(预处理器,后处理器,控制器)以准备模型。

  • 如果返回模型,则使用已使用WebApplicationContext配置的视图解析器呈现视图。如果未返回任何模型(例如,出于安全原因,这可能是由于预处理程序或后处理程序拦截了该请求),则不会呈现任何视图,因为该请求可能已经完成。

WebApplicationContext中声明的任何处理程序异常解析器都会处理在处理请求期间引发的异常。使用这些异常解析器,您可以定义自定义行为,以防引发此类异常。

您可以通过在portlet.xml文件或 portlet 初始化参数中添加上下文参数来自定义 Spring 的DispatcherPortlet。可能性在下面列出。

表 25.2. DispatcherPortlet 初始化参数

ParameterExplanation
contextClass实现WebApplicationContext的类,该类将用于实例化此 Portlet 使用的上下文。如果未指定此参数,则将使用XmlPortletApplicationContext
contextConfigLocation传递给上下文实例(由contextClass指定)以指示可以在哪里找到上下文的字符串。该字符串可能会拆分为多个字符串(使用逗号作为分隔符)以支持多个上下文(在多个上下文位置的情况下,对于两次定义的 bean,以最新的为准)。
namespaceWebApplicationContext的名称空间。默认为[portlet-name]-portlet
viewRendererUrlDispatcherPortlet可以访问ViewRendererServlet的实例的 URL(请参阅第 25.3 节“ ViewRendererServlet”)。

25.3 ViewRendererServlet

Portlet MVC 中的呈现过程比 Web MVC 中的呈现过程复杂一些。为了重用 Spring Web MVC 中的所有view technologies,我们必须将PortletRequest/PortletResponse转换为HttpServletRequest/HttpServletResponse,然后调用Viewrender方法。为此,DispatcherPortlet为此使用了一个特殊的 servlet:ViewRendererServlet

为了使DispatcherPortlet渲染起作用,必须在web.xml文件中为 Web 应用程序声明ViewRendererServlet的实例,如下所示:

<servlet>
    <servlet-name>ViewRendererServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>ViewRendererServlet</servlet-name>
    <url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>

要执行实际的渲染,DispatcherPortlet执行以下操作:

  • DispatcherServlet使用的同一WEB_APPLICATION_CONTEXT_ATTRIBUTE键下,将WebApplicationContext作为属性绑定到请求。

  • ModelView对象绑定到请求,以使它们可用于ViewRendererServlet

  • 使用 Map 到ViewRendererServlet/WEB- INF/servlet/view URL 构造PortletRequestDispatcher并执行include

ViewRendererServlet然后可以使用适当的参数调用View上的render方法。

可以使用DispatcherPortlet's `viewRendererUrl配置参数更改ViewRendererServlet的实际 URL。

25.4 Controllers

Portlet MVC 中的控制器与 Web MVC 控制器非常相似,并且将代码从一个移植到另一个应该很简单。

Portlet MVC 控制器体系结构的基础是org.springframework.web.portlet.mvc.Controller接口,下面列出了该接口。

public interface Controller {

    /**
     * Process the render request and return a ModelAndView object which the
     * DispatcherPortlet will render.
     */
    ModelAndView handleRenderRequest(RenderRequest request,
            RenderResponse response) throws Exception;

    /**
     * Process the action request. There is nothing to return.
     */
    void handleActionRequest(ActionRequest request,
            ActionResponse response) throws Exception;

}

如您所见,Portlet Controller接口需要两个方法来处理 Portlet 请求的两个阶段:动作请求和呈现请求。动作阶段应该能够处理动作请求,渲染阶段应该能够处理渲染请求并返回适当的模型和视图。虽然Controller接口非常抽象,但是 Spring Portlet MVC 提供了几个控制器,这些控制器已经包含了您可能需要的许多功能。其中大多数与 Spring Web MVC 的控制器非常相似。 Controller接口仅定义每个控制器所需的最常见功能:处理动作请求,处理渲染请求以及返回模型和视图。

25.4.1 AbstractController 和 PortletContentGenerator

当然,仅Controller接口是不够的。为了提供基本的基础架构,所有 Spring Portlet MVC 的Controller都继承自AbstractController,该类提供对 Spring 的ApplicationContext的访问并控制缓存。

表 25.3. AbstractController 提供的功能

ParameterExplanation
requireSession指示此Controller是否需要会话才能完成其工作。此功能提供给所有控制器。如果在此类控制器收到请求时不存在会话,则使用SessionRequiredException通知用户。
synchronizeSession如果要在用户会话上同步此控制器的处理,请使用此选项。更具体地说,扩展控制器将覆盖handleRenderRequestInternal(..)handleActionRequestInternal(..)方法,如果您指定此变量,它们将在用户会话上同步。
renderWhenMinimized如果您希望控制器在 portlet 处于最小化状态时实际呈现视图,请将其设置为 true。缺省情况下,此属性设置为 false,以使处于最小化状态的 portlet 不显示任何内容。
cacheSeconds当您希望控制器覆盖为 Portlet 定义的缺省高速缓存到期时,请在此处指定一个正整数。默认情况下,它设置为-1,这不会更改默认缓存。将其设置为0将确保永远不会缓存结果。

requireSessioncacheSeconds属性在PortletContentGenerator类(是AbstractController的超类)上声明,但出于完整性考虑,此处将其包括在内。

当使用AbstractController作为控制器的 Base Class 时(不建议这样做,因为有许多其他的控制器可能已经为您完成工作了),您只需覆盖handleActionRequestInternal(ActionRequest, ActionResponse)方法或handleRenderRequestInternal(RenderRequest, RenderResponse)方法(或两者) ,实现您的逻辑,并返回ModelAndView对象(对于handleRenderRequestInternal)。

handleActionRequestInternal(..)handleRenderRequestInternal(..)的默认实现都抛出PortletException。这与 JSR-168 规范 API 中GenericPortlet的行为一致。因此,您只需要重写控制器打算处理的方法。

这是在 Web 应用程序上下文中由类和声明组成的简短示例。

package samples;

import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.springframework.web.portlet.mvc.AbstractController;
import org.springframework.web.portlet.ModelAndView;

public class SampleController extends AbstractController {

    public ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response) {
        ModelAndView mav = new ModelAndView("foo");
        mav.addObject("message", "Hello World!");
        return mav;
    }

}
<bean id="sampleController" class="samples.SampleController">
    <property name="cacheSeconds" value="120"/>
</bean>

除了设置处理程序 Map(请参见第 25.5 节“处理程序 Map”)以使此非常简单的控制器正常工作之外,您还需要上面的类和 Web 应用程序上下文中的声明。

25.4.2 其他简单控制器

尽管您可以扩展AbstractController,但是 Spring Portlet MVC 提供了许多具体的实现,这些实现提供了简单 MVC 应用程序中常用的功能。

ParameterizableViewController与上面的示例基本相同,不同之处在于您可以指定将在 Web 应用程序上下文中返回的视图名称(无需对视图名称进行硬编码)。

PortletModeNameViewController使用 portlet 的当前模式作为视图名称。因此,如果您的 Portlet 处于“查看”模式(即PortletMode.VIEW),那么它将使用“ view”作为视图名称。

25.4.3 命令控制器

Spring Portlet MVC 具有与 Spring Web MVC 完全相同的“命令控制器”层次结构。它们提供了一种与数据对象进行交互并将参数从PortletRequest动态绑定到指定的数据对象的方法。您的数据对象不必实现特定于框架的接口,因此您可以根据需要直接操作持久对象。让我们检查一下可用的命令控制器,以概述可以使用它们的功能:

  • AbstractCommandController-可用于创建自己的命令控制器的命令控制器,该命令控制器能够将请求参数绑定到您指定的数据对象。该类不提供表单功能,但是提供验证功能,并允许您在控制器本身中指定如何处理已被请求中的参数填充的命令对象。

  • AbstractFormController-提供表单提交支持的抽象控制器。使用此控制器,您可以对表单建模,并使用在控制器中检索到的命令对象填充表单。用户填写表单后,AbstractFormController绑定字段,验证并将对象交给控制器以采取适当的措施。支持的功能包括:无效的表单提交(重新提交),验证和正常的表单工作流程。您可以实现确定哪些视图用于表单表示和成功的方法。如果需要表单,但不想指定要在应用程序上下文中向用户显示哪些视图,请使用此控制器。

  • SimpleFormController-具体的AbstractFormController,在使用相应的命令对象创建表单时会提供更多支持。 SimpleFormController允许您指定命令对象,表单的视图名,表单提交成功后要向用户显示的页面的视图名等。

  • AbstractWizardFormController —具体的AbstractFormController,它提供了一个向导样式的界面,用于在多个显示页面上编辑命令对象的内容。支持多种用户操作:完成,取消或页面更改,所有这些操作都可以在视图的请求参数中轻松指定。

这些命令控制器功能非常强大,但确实需要详细了解它们的操作方式才能有效地使用它们。在开始使用它们之前,请仔细阅读有关整个层次结构的 javadocs,然后查看一些示例实现。

25.4.4 PortletWrappingController

代替开发新的控制器,可以使用现有的 portlet 并从DispatcherPortletMap 请求到它们。使用PortletWrappingController,您可以将现有的Portlet实例化为Controller,如下所示:

<bean id="myPortlet" class="org.springframework.web.portlet.mvc.PortletWrappingController">
    <property name="portletClass" value="sample.MyPortlet"/>
    <property name="portletName" value="my-portlet"/>
    <property name="initParameters">
        <value>config=/WEB-INF/my-portlet-config.xml</value>
    </property>
</bean>

这非常有价值,因为您可以使用拦截器对进入这些 Portlet 的请求进行预处理和后处理。由于 JSR-286 不支持任何类型的过滤器机制,因此非常方便。例如,这可以用于将 Hibernate OpenSessionInViewInterceptor包裹在 MyFaces JSF Portlet 周围。

25.5 处理程序 Map

使用处理程序 Map,可以将传入的 Portlet 请求 Map 到适当的处理程序。您可以使用一些开箱即用的处理程序 Map,例如PortletModeHandlerMapping,但首先让我们研究HandlerMapping的一般概念。

注意:我们在此故意使用术语“处理程序”,而不是“控制器”。 DispatcherPortlet旨在与其他方式一起使用,以处理请求,而不仅仅是 Spring Portlet MVC 自己的控制器。处理程序是可以处理 portlet 请求的任何对象。控制器是处理程序的一个示例,它们当然是默认的。要将其他框架与DispatcherPortlet一起使用,就需要HandlerAdapter的相应实现。

基本HandlerMapping提供的功能是HandlerExecutionChain的传递,该HandlerExecutionChain必须包含与传入请求匹配的处理程序,并且还可能包含应用于该请求的处理程序拦截器列表。当请求进入时,DispatcherPortlet会将其移交给处理程序 Map,以使其检查请求并提出适当的HandlerExecutionChain。然后DispatcherPortlet将执行链中的处理程序和拦截器(如果有)。这些概念与 Spring Web MVC 中的完全相同。

可配置的处理程序 Map 的概念非常强大,它可以可选地包含拦截器(在执行实际的处理程序之前或之后执行,或两者都执行)。自定义HandlerMapping可以内置许多支持功能。考虑一个自定义处理程序 Map,该 Map 不仅根据传入的请求的 Portlet 模式来选择处理程序,而且还根据与该请求相关联的会话的特定状态来选择处理程序。

在 Spring Web MVC 中,处理程序 Map 通常基于 URL。由于 Portlet 中确实没有 URL 之类的东西,因此我们必须使用其他机制来控制 Map。最常见的两种是 portlet 模式和 request 参数,但是可用于 portlet 请求的任何东西都可以在定制处理程序 Map 中使用。

本节的其余部分描述了 Spring Portlet MVC 最常用的三个处理程序 Map。它们都扩展了AbstractHandlerMapping并共享以下属性:

  • interceptors:要使用的拦截器列表。 HandlerInterceptor第 25.5.4 节“添加 HandlerInterceptors”中讨论。

  • defaultHandler:当此处理程序 Map 未导致匹配的处理程序时使用的默认处理程序。

  • order:基于 order 属性的值(请参见org.springframework.core.Ordered接口),Spring 将对上下文中可用的所有处理程序 Map 进行排序,并应用第一个匹配的处理程序。

  • lazyInitHandlers:允许单例处理程序的延迟初始化(原型处理程序总是延迟初始化)。默认值为 false。此属性直接在三个具体的处理程序中实现。

25.5.1 PortletModeHandlerMapping

这是一个简单的处理程序 Map,它根据 Portlet 的当前模式(例如“视图”,“编辑”,“帮助”)Map 传入的请求。一个例子:

<bean class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
    <property name="portletModeMap">
        <map>
            <entry key="view" value-ref="viewHandler"/>
            <entry key="edit" value-ref="editHandler"/>
            <entry key="help" value-ref="helpHandler"/>
        </map>
    </property>
</bean>

25.5.2 ParameterHandlerMapping

如果我们需要在不更改 Portlet 模式的情况下导航至多个控制器,则最简单的方法是使用 request 参数作为控制 Map 的键。

ParameterHandlerMapping使用特定请求参数的值来控制 Map。参数的默认名称为'action',但可以使用'parameterName'属性进行更改。

此 Map 的 Bean 配置如下所示:

<bean class="org.springframework.web.portlet.handler.ParameterHandlerMapping">
    <property name="parameterMap">
        <map>
            <entry key="add" value-ref="addItemHandler"/>
            <entry key="edit" value-ref="editItemHandler"/>
            <entry key="delete" value-ref="deleteItemHandler"/>
        </map>
    </property>
</bean>

25.5.3 PortletModeParameterHandlerMapping

最强大的内置处理程序 MapPortletModeParameterHandlerMapping结合了前两个的功能,以允许在每种 portlet 模式下进行不同的导航。

同样,该参数的默认名称是“ action”,但可以使用parameterName属性进行更改。

缺省情况下,在两个不同的 Portlet 模式下可能不会使用相同的参数值。这样一来,如果门户网站本身更改了 Portlet 模式,则该请求在 Map 中将不再有效。可以通过将allowDupParameters属性设置为 true 来更改此行为。但是,不建议这样做。

此 Map 的 Bean 配置如下所示:

<bean class="org.springframework.web.portlet.handler.PortletModeParameterHandlerMapping">
    <property name="portletModeParameterMap">
        <map>
            <entry key="view"> <!-- 'view' portlet mode -->
                <map>
                    <entry key="add" value-ref="addItemHandler"/>
                    <entry key="edit" value-ref="editItemHandler"/>
                    <entry key="delete" value-ref="deleteItemHandler"/>
                </map>
            </entry>
            <entry key="edit"> <!-- 'edit' portlet mode -->
                <map>
                    <entry key="prefs" value-ref="prefsHandler"/>
                    <entry key="resetPrefs" value-ref="resetPrefsHandler"/>
                </map>
            </entry>
        </map>
    </property>
</bean>

可以在PortletModeHandlerMapping之前链接此 Map,然后可以为每种模式提供默认值,也可以提供整体默认值。

25.5.4 添加 HandlerInterceptor

Spring 的处理程序 Map 机制具有处理程序拦截器的概念,当您要将特定功能应用于某些请求(例如,检查委托人)时,该方法非常有用。 Spring Portlet MVC 再次以与 Web MVC 相同的方式实现这些概念。

位于处理程序 Map 中的拦截器必须实现org.springframework.web.portlet包中的HandlerInterceptor。就像 servlet 版本一样,此接口定义了三种方法:一种将在执行实际处理程序之前被调用(preHandle),一种将在处理程序执行之后被调用(postHandle),而另一种则在完成处理程序之后被调用。请求已完成(afterCompletion)。这三种方法应提供足够的灵 Active 来执行各种预处理和后处理。

preHandle方法返回布尔值。您可以使用此方法来中断或 continue 执行链的处理。当此方法返回true时,处理程序执行链将 continue。当它返回false时,DispatcherPortlet假定拦截器本身已经处理了请求(例如,提供了适当的视图),并且不再 continue 执行执行链中的其他拦截器和实际处理程序。

postHandle方法仅在RenderRequest上调用。 preHandleafterCompletion方法同时在ActionRequestRenderRequest上调用。如果您仅需要针对一种类型的请求在这些方法中执行逻辑,请确保在处理该请求之前先检查其类型。

25.5.5 HandlerInterceptorAdapter

与 servlet 包一样,portlet 包具有HandlerInterceptor的具体实现,称为HandlerInterceptorAdapter。此类具有所有方法的空版本,因此您可以从该类继承,并在需要时仅实现一个或两个方法。

25.5.6 ParameterMappingInterceptor

Portlet 软件包还具有一个名为ParameterMappingInterceptor的具体拦截器,该拦截器应直接与ParameterHandlerMappingPortletModeParameterHandlerMapping一起使用。此拦截器将使用于控制 Map 的参数从ActionRequest转发到后续的RenderRequest。这将有助于确保RenderRequestMap 到与ActionRequest相同的处理程序。这是在拦截器的preHandle方法中完成的,因此您仍然可以在处理程序中修改参数值以更改RenderRequest的 Map 位置。

请注意,此拦截器正在ActionResponse上调用setRenderParameter,这意味着使用此拦截器时无法在处理程序中调用sendRedirect。如果需要进行外部重定向,则需要手动转发 Map 参数,或者编写其他拦截器来为您处理。

25.6 视图并解决

如前所述,Spring Portlet MVC 直接重用 Spring Web MVC 中的所有视图技术。这不仅包括各种View实现本身,还包括ViewResolver实现。有关更多信息,请分别参考第 23 章,查看技术第 22.5 节“解析视图”

值得一提的是使用现有的ViewViewResolver实现的一些项目:

  • 大多数门户网站希望将 Portlet 呈现的结果是 HTML 片段。因此,诸如 JSP/JSTL,Velocity,FreeMarker 和 XSLT 之类的东西都是有意义的。但是在 portlet 上下文中返回其他文档类型的视图不太可能具有任何意义。

  • 没有从 portlet 内部进行 HTTP 重定向的事情(ActionResponsesendRedirect(..)方法不能用于保留在门户中)。因此,RedirectView和使用'redirect:'前缀在 Portlet MVC 中将无法正常工作。

  • 可以在 Portlet MVC 中使用'forward:'前缀。但是,请记住,由于您位于 portlet 中,因此您不知道当前 URL 的外观。这意味着您不能使用相对 URL 来访问 Web 应用程序中的其他资源,而必须使用绝对 URL。

同样,对于 JSP 开发,新的 Spring Taglib 和新的 Spring Form Taglib 都在 Portlet 视图中工作,与在 Servlet 视图中工作的方式完全相同。

25.7Multipart(文件上传)支持

与 Web MVC 一样,Spring Portlet MVC 具有内置的 Multipart 支持以处理 Portlet 应用程序中的文件上传。Multipart 支持的设计是通过org.springframework.web.portlet.multipart包中定义的可插入PortletMultipartResolver对象完成的。 Spring 提供了PortletMultipartResolver以便与Commons FileUpload一起使用。本节其余部分将介绍如何支持上传文件。

默认情况下,Spring Portlet MVC 不会进行 Multipart 处理,因为某些开发人员会希望自己处理 Multipart。您必须通过将 Multipart 解析器添加到 Web 应用程序的上下文中来启用它。完成此操作后,DispatcherPortlet将检查每个请求以查看其是否包含 Multipart。如果未找到 Multipart,则请求将按预期 continue。但是,如果在请求中找到 Multipart,则将使用在上下文中声明的PortletMultipartResolver。之后,您请求中的 multipart 属性将被视为其他任何属性。

Note

任何已配置的PortletMultipartResolver bean 必须具有以下 ID(或名称):“portletMultipartResolver`". If you have defined your `PortletMultipartResolver具有任何其他名称,然后DispatcherPortlet不会找到您的PortletMultipartResolver,因此,没有 Multipart 支持有效。

25.7.1 使用 PortletMultipartResolver

以下示例显示了如何使用CommonsPortletMultipartResolver

<bean id="portletMultipartResolver"
        class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver">
    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize" value="100000"/>
</bean>

当然,您还需要在 Classpath 中放入适当的 jar,以使 Multipart 解析器正常工作。如果是CommonsMultipartResolver,则需要使用commons-fileupload.jar。请确保至少使用 Commons FileUpload 的 1.1 版,因为以前的版本不支持 JSR-286 Portlet 应用程序。

既然您已经了解了如何设置 Portlet MVC 来处理 Multipart 请求,那么让我们讨论一下如何实际使用它。 DispatcherPortlet检测到 Multipart 请求时,它将激活在您的上下文中声明的解析器并移交请求。然后,解析程序执行的操作是将当前的ActionRequest封装在支持分段文件上传的MultipartActionRequest中。使用MultipartActionRequest,您可以获得有关此请求包含的 Multipart 的信息,并且实际上可以访问控制器中的 Multipart 文件本身。

请注意,您只能作为ActionRequest的一部分而不是RenderRequest的一部分来接收分段文件上传。

25.7.2 处理表单中的文件

PortletMultipartResolver完成工作后,将像处理其他请求一样处理该请求。要使用PortletMultipartResolver,请创建一个带有上载字段的表单(请参见下面的示例),然后让 Spring 将文件绑定到您的表单(后备对象)上。为了让用户实际上传文件,我们必须创建一个(JSP/HTML)表单:

<h1>Please upload a file</h1>
<form method="post" action="<portlet:actionURL/>" enctype="multipart/form-data">
    <input type="file" name="file"/>
    <input type="submit"/>
</form>

如您所见,我们已经创建了一个名为“ file”的字段,该字段与保存byte[]数组的 bean 的属性匹配。此外,我们添加了 encoding 属性(enctype="multipart/form-data"),该属性对于让浏览器知道如何对 Multipart 字段进行编码是必要的(请不要忘记这一点!)。

与无法自动转换为字符串或原始类型的任何其他属性一样,要能够将二进制数据放入对象中,您必须向PortletRequestDataBinder注册自定义编辑器。有几个编辑器可用于处理文件和在对象上设置结果。有一个StringMultipartFileEditor能够将文件转换为字符串(使用用户定义的字符集),还有一个ByteArrayMultipartFileEditor可以将文件转换为字节数组。它们的功能类似于CustomDateEditor

因此,为了能够使用表单上传文件,请声明解析器,到将处理 Bean 的控制器的 Map 以及控制器本身。

<bean id="portletMultipartResolver"
        class="org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver"/>

<bean class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
    <property name="portletModeMap">
        <map>
            <entry key="view" value-ref="fileUploadController"/>
        </map>
    </property>
</bean>

<bean id="fileUploadController" class="examples.FileUploadController">
    <property name="commandClass" value="examples.FileUploadBean"/>
    <property name="formView" value="fileuploadform"/>
    <property name="successView" value="confirmation"/>
</bean>

之后,创建控制器和实际的类以保存 file 属性。

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(ActionRequest request, ActionResponse response,
            Object command, BindException errors) throws Exception {

        // cast the bean
        FileUploadBean bean = (FileUploadBean) command;

        // let's see if there's content there
        byte[] file = bean.getFile();
        if (file == null) {
            // hmm, that's strange, the user did not upload anything
        }

        // do something with the file here
    }

    protected void initBinder(PortletRequest request,
            PortletRequestDataBinder binder) throws Exception {
        // to actually be able to convert Multipart instance to byte[]
        // we have to register a custom editor
        binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
        // now Spring knows how to handle multipart object and convert
    }

}

public class FileUploadBean {

    private byte[] file;

    public void setFile(byte[] file) {
        this.file = file;
    }

    public byte[] getFile() {
        return file;
    }

}

如您所见,FileUploadBean具有类型byte[]的属性来保存文件。控制器注册了一个自定义编辑器,以使 Spring 知道如何将解析器找到的 Multipart 对象实际转换为 Bean 指定的属性。在此示例中,bean 本身的byte[]属性没有做任何事情,但是实际上您可以做任何您想做的事情(将其保存在数据库中,将其邮寄给其他人,等等)。

一个等效的示例,其中文件直接绑定到表单支持对象上的 String 类型的属性,可能看起来像这样:

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(ActionRequest request, ActionResponse response,
            Object command, BindException errors) throws Exception {

        // cast the bean
        FileUploadBean bean = (FileUploadBean) command;

        // let's see if there's content there
        String file = bean.getFile();
        if (file == null) {
            // hmm, that's strange, the user did not upload anything
        }

        // do something with the file here
    }

    protected void initBinder(PortletRequest request,
            PortletRequestDataBinder binder) throws Exception {

        // to actually be able to convert Multipart instance to a String
        // we have to register a custom editor
        binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
        // now Spring knows how to handle multipart objects and convert
    }
}

public class FileUploadBean {

    private String file;

    public void setFile(String file) {
        this.file = file;
    }

    public String getFile() {
        return file;
    }
}

当然,最后一个示例仅在上载纯文本文件的上下文中才有意义(在逻辑上)(在上载图像文件的情况下效果不佳)。

第三个(也是最后一个)选项是直接绑定到在(表单支持)对象的类上声明的MultipartFile属性的位置。在这种情况下,不需要注册任何自定义属性编辑器,因为没有类型转换要执行。

public class FileUploadController extends SimpleFormController {

    public void onSubmitAction(ActionRequest request, ActionResponse response,
            Object command, BindException errors) throws Exception {

        // cast the bean
        FileUploadBean bean = (FileUploadBean) command;

        // let's see if there's content there
        MultipartFile file = bean.getFile();
        if (file == null) {
            // hmm, that's strange, the user did not upload anything
        }

        // do something with the file here
    }
}

public class FileUploadBean {

    private MultipartFile file;

    public void setFile(MultipartFile file) {
        this.file = file;
    }

    public MultipartFile getFile() {
        return file;
    }

}

25.8 处理异常

与 Servlet MVC 一样,Portlet MVC 提供HandlerExceptionResolver来减轻由匹配请求的处理程序处理您的请求时发生的意外异常的麻烦。 Portlet MVC 还提供了特定于 Portlet 的具体SimpleMappingExceptionResolver,使您能够获取可能引发的任何异常的类名称,并将其 Map 到视图名称。

25.9 基于 Comments 的控制器配置

Spring 2.5 为 MVC 控制器引入了基于 Comments 的编程模型,使用了诸如@RequestMapping@RequestParam@ModelAttribute等 Comments。此 Comments 支持可用于 Servlet MVC 和 Portlet MVC。以这种方式实现的控制器不必扩展特定的 Base Class 或实现特定的接口。而且,它们通常不直接依赖 Servlet 或 Portlet API,尽管如果需要,它们可以轻松访问 Servlet 或 Portlet 工具。

以下各节记录了这些注解以及它们在 Portlet 环境中的最常用用法。

25.9.1 设置调度程序以支持 Comments

*只有在分派器中存在相应的HandlerMapping(用于类型级别的 Comments)和/或HandlerAdapter(用于方法级别的 Comments)时,才会处理@ RequestMapping+304++305+`都是这种情况。

但是,如果要定义自定义HandlerMappingsHandlerAdapters,则需要确保也定义了相应的自定义DefaultAnnotationHandlerMapping和/或AnnotationMethodHandlerAdapter-如果您打算使用@RequestMapping

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>

    <bean class="org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

    // ... (controller bean definitions) ...

</beans>

如果您想自定义 Map 策略,例如,明确定义DefaultAnnotationHandlerMapping和/或AnnotationMethodHandlerAdapter也很有意义。指定自定义WebBindingInitializer(请参见下文)。

25.9.2 使用@Controller 定义控制器

@ControllerComments 指示特定的类充当* controller *的角色。无需扩展任何控制器 Base Class 或引用 Portlet API。当然,如果需要,您仍然可以引用特定于 Portlet 的功能。

@Controller注解的基本目的是充当带 Comments 的类的构造型,以指示其作用。调度程序将扫描此类带 Comments 的类以查找 Map 的方法,并检测@RequestMapping个 Comments(请参阅下一节)。

可以在调度程序的上下文中使用标准的 Spring bean 定义来显式定义带 Comments 的控制器 bean。但是,@Controller原型也允许自动检测,与 Spring 2.5 对在 Classpath 中检测组件类并为其自动注册 Bean 定义的常规支持保持一致。

要启用对带 Comments 的控制器的自动检测,必须将组件扫描添加到配置中。如下面的 XML 代码片段所示,可以通过使用* spring-context *模式轻松实现此目的:

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

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

    // ...

</beans>

25.9.3 使用@RequestMappingMap 请求

@RequestMappingComments 用于将 portlet 模式(如“ VIEW” /“ EDIT”)Map 到整个类或特定的处理程序方法。通常,类型级别的 Comments 将特定的模式(或模式加上参数条件)Map 到表单控制器上,而其他方法级别的 Comments 则“缩小”特定 portlet 请求参数的主 Map。

Tip

类型级别的@RequestMapping也可以用于Controller接口的简单实现。在这种情况下,请求处理代码将遵循传统的handle(Action|Render)Request签名,而控制器的 Map 将通过@RequestMappingComments 表示。这也适用于预先构建的ControllerBase Class,例如SimpleFormController

在下面的讨论中,我们将重点介绍基于带 Comments 的处理程序方法的控制器。

以下是使用此注解的 PetPortal 示例应用程序中的表单控制器的示例:

@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {

    private Properties petSites;

    public void setPetSites(Properties petSites) {
        this.petSites = petSites;
    }

    @ModelAttribute("petSites")
    public Properties getPetSites() {
        return this.petSites;
    }

    @RequestMapping // default (action=list)
    public String showPetSites() {
        return "petSitesEdit";
    }

    @RequestMapping(params = "action=add") // render phase
    public String showSiteForm(Model model) {
        // Used for the initial form as well as for redisplaying with errors.
        if (!model.containsAttribute("site")) {
            model.addAttribute("site", new PetSite());
        }

        return "petSitesAdd";
    }

    @RequestMapping(params = "action=add") // action phase
    public void populateSite(@ModelAttribute("site") PetSite petSite,
            BindingResult result, SessionStatus status, ActionResponse response) {
        new PetSiteValidator().validate(petSite, result);
        if (!result.hasErrors()) {
            this.petSites.put(petSite.getName(), petSite.getUrl());
            status.setComplete();
            response.setRenderParameter("action", "list");
        }
    }

    @RequestMapping(params = "action=delete")
    public void removeSite(@RequestParam("site") String site, ActionResponse response) {
        this.petSites.remove(site);
        response.setRenderParameter("action", "list");
    }
}

从 Spring 3.0 开始,有专用的@ActionMapping@RenderMapping(以及@ResourceMapping@EventMapping)注解可以代替:

@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {

    private Properties petSites;

    public void setPetSites(Properties petSites) {
        this.petSites = petSites;
    }

    @ModelAttribute("petSites")
    public Properties getPetSites() {
        return this.petSites;
    }

    @RenderMapping // default (action=list)
    public String showPetSites() {
        return "petSitesEdit";
    }

    @RenderMapping(params = "action=add")
    public String showSiteForm(Model model) {
        // Used for the initial form as well as for redisplaying with errors.
        if (!model.containsAttribute("site")) {
            model.addAttribute("site", new PetSite());
        }

        return "petSitesAdd";
    }

    @ActionMapping(params = "action=add")
    public void populateSite(@ModelAttribute("site") PetSite petSite,
            BindingResult result, SessionStatus status, ActionResponse response) {
        new PetSiteValidator().validate(petSite, result);
        if (!result.hasErrors()) {
            this.petSites.put(petSite.getName(), petSite.getUrl());
            status.setComplete();
            response.setRenderParameter("action", "list");
        }
    }

    @ActionMapping(params = "action=delete")
    public void removeSite(@RequestParam("site") String site, ActionResponse response) {
        this.petSites.remove(site);
        response.setRenderParameter("action", "list");
    }
}

25.9.4 支持的处理程序方法参数

@RequestMappingComments 的处理程序方法具有非常灵活的签名。它们可以具有任意类型的以下类型的参数(验证结果除外,如果需要,验证结果需要紧跟在相应命令对象之后):

  • 请求和/或响应对象(Portlet API)。您可以选择任何特定的请求/响应类型,例如 PortletRequest/ActionRequest/RenderRequest。显式声明的 action/render 参数还用于将特定的请求类型 Map 到处理程序方法上(在没有提供其他区分操作请求和呈现请求的信息的情况下)。

  • 会话对象(Portlet API):类型为 PortletSession。这种类型的参数将强制相应会话的存在。结果,这样的参数永远不会是null

  • org.springframework.web.context.request.WebRequestorg.springframework.web.context.request.NativeWebRequest。允许通用请求参数访问以及请求/会话属性访问,而无需与本机 Servlet/Portlet API 绑定。

  • 当前请求语言环境(Portlet 环境中的门户语言环境)的java.util.Locale

  • java.util.TimeZone/java.time.ZoneId为当前请求时区。

  • java.io.InputStream/java.io.Reader用于访问请求的内容。这将是 Portlet API 公开的原始 InputStream/Reader。

  • java.io.OutputStream/java.io.Writer用于生成响应的内容。这将是 Portlet API 公开的原始 OutputStream/Writer。

  • @RequestParam带 Comments 的参数,用于访问特定的 Portlet 请求参数。参数值将转换为声明的方法参数类型。

  • java.util.Map/org.springframework.ui.Model/org.springframework.ui.ModelMap用于丰富将公开给 Web 视图的隐式模型。

  • 将参数绑定到的命令/表单对象:作为 Bean 属性或字段,具有可自定义的类型转换,具体取决于@InitBinder方法和/或 HandlerAdapter 配置-参见“ webBindingInitializer" property on AnnotationMethodHandlerAdapter”。此类命令对象及其验证结果将以模型属性,默认情况下使用属性符号中的非限定命令类名称(例如,“ mypackage.OrderAddress”类型为“ orderAddress”)指定参数级ModelAttributeComments 以声明特定的模型属性名称。

  • 前一个命令/表单对象(前一个直接参数)的org.springframework.validation.Errors/org.springframework.validation.BindingResult验证结果。

  • org.springframework.web.bind.support.SessionStatus状态句柄,用于将表单处理标记为完成(触发在处理程序类型级别清除由@SessionAttributes注解指示的会话属性)。

处理程序方法支持以下返回类型:

  • 一个ModelAndView对象,该模型隐含了命令对象和@ModelAttributeComments 的参考数据访问器方法的结果。

  • Model对象,其视图名称是通过RequestToViewNameTranslator隐式确定的,模型隐含了命令对象和@ModelAttributeComments 的参考数据访问器方法的结果。

  • 一个用于公开模型的Map对象,其视图名称是通过RequestToViewNameTranslator隐式确定的,并且该模型隐含了命令对象和@ModelAttributeComments 的参考数据访问器方法的结果。

  • View对象,其模型是通过命令对象和带有@ModelAttributeComments 的参考数据访问器方法隐式确定的。处理程序方法还可以pass 语句Model参数来以编程方式丰富模型(请参见上文)。

  • String值解释为视图名称,模型通过命令对象和带有@ModelAttributeComments 的参考数据访问器方法隐式确定。处理程序方法还可以pass 语句Model参数来以编程方式丰富模型(请参见上文)。

  • void(如果该方法自行处理响应)(例如,直接编写响应内容)。

  • 使用方法级通过@ModelAttribute指定的属性名称(或基于返回类型的类名称的默认属性名称),任何其他返回类型都将被视为要显示给视图的单个模型属性。该模型将隐含地丰富命令对象和带有@ModelAttributeComments 的参考数据访问器方法的结果。

25.9.5 使用@RequestParam 将请求参数绑定到方法参数

@RequestParam注解用于将请求参数绑定到控制器中的方法参数。

PetPortal 示例应用程序中的以下代码片段显示了用法:

@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {

    // ...

    public void removeSite(@RequestParam("site") String site, ActionResponse response) {
        this.petSites.remove(site);
        response.setRenderParameter("action", "list");
    }

    // ...

}

默认情况下,使用此注解的参数是必需的,但是您可以通过将@RequestParam'srequired属性设置为false(例如@RequestParam(name="id", required=false)`)来指定参数是可选的。

25.9.6 使用@ModelAttribute 提供指向模型数据的链接

@ModelAttribute在控制器中有两种使用情况。当放在方法参数上时,@ModelAttribute用于将模型属性 Map 到带 Comments 的特定方法参数(请参见下面的populateSite()方法)。控制器就是通过这种方式获取对包含表单中 Importing 数据的对象的引用。此外,可以将参数声明为表单支持对象的特定类型,而不是通用java.lang.Object,从而提高类型安全性。

@ModelAttribute还用于方法级别,以为模型提供参考数据(请参见下面的getPetSites()方法)。对于这种用法,方法签名可以包含与上面针对@RequestMappingComments 记录的类型相同的类型。

Note

@ModelAttribute带 Comments 的方法将选定的@RequestMapping带 Comments 的处理程序方法之前执行。它们有效地预填充了具有特定属性的隐式模型,这些属性通常是从数据库中加载的。然后,可以通过所选处理程序方法中带有@ModelAttributeComments 的处理程序方法参数来访问此属性,并可能对其应用绑定和验证。

以下代码片段显示了此注解的这两种用法:

@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {

    // ...

    @ModelAttribute("petSites")
    public Properties getPetSites() {
        return this.petSites;
    }

    @RequestMapping(params = "action=add") // action phase
    public void populateSite( @ModelAttribute("site") PetSite petSite, BindingResult result, SessionStatus status, ActionResponse response) {
        new PetSiteValidator().validate(petSite, result);
        if (!result.hasErrors()) {
            this.petSites.put(petSite.getName(), petSite.getUrl());
            status.setComplete();
            response.setRenderParameter("action", "list");
        }
    }
}

25.9.7 使用@SessionAttributes 指定要存储在会话中的属性

类型级别的@SessionAttributesComments 声明特定处理程序使用的会话属性。这通常会列出模型属性的名称或模型属性的类型,这些名称应透明地存储在会话或某些会话存储中,用作后续请求之间的表单支持 Bean。

以下代码段显示了此注解的用法:

@Controller
@RequestMapping("EDIT")
@SessionAttributes("site")
public class PetSitesEditController {
    // ...
}

25.9.8 自定义 WebDataBinder 初始化

要通过 Spring 的WebDataBinder自定义与 PropertyEditor 等的请求参数绑定,您可以在控制器中使用@InitBinder-带 Comments 的方法,也可以通过提供自定义WebBindingInitializer来外部化配置。

使用@InitBinder 自定义数据绑定

使用@InitBinderComments 控制器方法,可以直接在控制器类中配置 Web 数据绑定。 @InitBinder标识用于初始化WebDataBinder的方法,该方法将用于填充命令并形成带 Comments 的处理程序方法的对象参数。

此类 init-binder 方法支持@RequestMapping支持的所有参数,但命令/表单对象和相应的验证结果对象除外。初始化绑定器方法不能具有返回值。因此,它们通常被声明为void。典型的参数包括WebDataBinderWebRequestjava.util.Locale结合使用,从而允许代码注册特定于上下文的编辑器。

下面的示例演示了如何使用@InitBinder为所有java.util.Date表单属性配置CustomDateEditor

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...

}

配置自定义 WebBindingInitializer

要外部化数据绑定初始化,可以提供WebBindingInitializer接口的自定义实现,然后通过为AnnotationMethodHandlerAdapter提供自定义 bean 配置来启用该接口,从而覆盖默认配置。

25.10 Portlet 应用程序部署

部署 Spring Portlet MVC 应用程序的过程与部署任何 JSR-286 Portlet 应用程序没有什么不同。但是,该领域通常令人困惑,因此在这里值得一谈。

通常,门户网站/ portlet 容器在 servlet 容器中的一个 webapp 中运行,而 portlet 在 servlet 容器中的另一个 webapp 中运行。为了使 portlet 容器 webapp 能够对您的 portlet webapp 进行调用,它必须对知名的 servlet 进行跨上下文调用,该 servlet 提供对portlet.xml文件中定义的 portlet 服务的访问。

JSR-286 规范并未确切说明应如何进行,因此每个 Portlet 容器都有自己的机制,通常涉及某种“部署过程”,该过程对 Portlet Webapp 本身进行更改,然后将 Portlet 注册到 Weblet 中。 Portlet 容器。

至少要修改 Portlet Web 应用程序中的web.xml文件,以注入 Portlet 容器将调用的知名 Servlet。在某些情况下,单个 servlet 将为 webapp 中的所有 portlet 提供服务,在其他情况下,每个 portlet 都将有一个 servlet 实例。

某些 portlet 容器也将库和/或配置文件注入到 webapp 中。 Portlet 容器还必须使您的 Webapp 可以使用其 Portlet JSP 标记库的实现。

最重要的是,了解目标门户的部署需求并确保满足它们(通常通过遵循其提供的自动部署过程)非常重要。请确保在此过程中仔细查看门户网站中的文档。

部署 portlet 后,请检查生成的web.xml文件是否完整。已知某些较旧的门户网站破坏了ViewRendererServlet的定义,从而破坏了 portlet 的呈现。