【问题标题】:Spring MVC Controller DesignSpring MVC 控制器设计
【发布时间】:2011-07-13 01:15:38
【问题描述】:

我们正在将一个 struts 应用程序迁移到 Spring MVC 并利用 @Controller 注解将页面定向到各种方法调用。

不过,我无法确定一个好的重用策略。

我们在许多页面中基本上都做同样的事情:

prepareView(..., ...); //Various params -- could likely be standardized

if (!allowedToView()) {
    mav.setViewName(injectedErrorPage);
}

performBusinessLogic(..., ...);  //Various params -- not seeing how to standardize

persistEntities();
finalizeView(..., ...);  // Various params -- could likely be standardized

使用什么策略来创建最终方法,让开发人员“忘记”这些过程?我曾考虑过创建一个抽象类,但由于每种方法所采用的方法不同,我确实没有看到“标准化”它的方法。

例如,我们有以下内容:

@RequestMapping("params="assign", method=RequestMethod.Post)
public ModelAndView assign(@SessionAttribute(value="sessionAttr") Pojo pojo,
                           @ModelAttribute("command") CommandPojo commandPojo,
                           BindingResult result) {
    //Follows pattern above
}

@RequestMapping()
public ModelAndView filterResults(@SessionAttribute(value="sessionAttr") Pojo pojo,
                                  @RequestAttribute("requestAttr") String requestAttr,
                                  @ModelAttribute("command") CommandPojo2 commandPojo2,
                                  BindingResult result) {

    //Follows pattern above
}

拥有最终方法需要将其分解为两个 POJO(然后调用描述性函数)。我最关心的是我们如何处理进入这个最终方法的不同参数?我看不出有什么办法可以处理这种情况。

如果我们仍然可以拥有这个带有受保护函数的“最终”方法,我们可以在需要的地方覆盖它,那就太好了。

【问题讨论】:

  • 注意:SessionAttribute 和 RequestAttribute 是我们创建的自定义注解,它们无论如何都不是官方的,我们只是希望能够使用它们而不是将我们耦合到 Session / Request。它还允许我们拥有非必需的 SessionAttributes(官方 spring 注释不支持)。
  • 如果您告诉我们准备和最终确定视图的实际作用,则可能更容易评估该任务是否存在常见的 Spring 习语。
  • 你考虑过使用拦截器吗?
  • 你能利用参数集合吗?类似于参数包的字典?
  • @three_cups_of_java 这看起来可能是我们最好的选择。但是,我们确实失去了一些不错的功能(无法将 ModelAttributes 拉出——无需在模型上添加额外的对象并将其拉入 postHandle,我们还将视图注入到我们的控制器中,这也需要添加到模型中(或至少部分),但它允许我们在拦截器中完成常见工作,并允许控制器代码简单地执行所需的业务逻辑。

标签: java spring-mvc


【解决方案1】:

您能否按照您的建议实现一个基类并强制采用Template Method 设计模式,并借鉴 Nix 在他之前对您关于在此基类中利用参数集合的问题的评论中所说的话?

这有帮助吗?

【讨论】:

  • 模板方法是我的最终目标。我正在寻找让我们到达那里的具体示例(参数从一个函数到下一个函数不同)。我见过的所有模板方法示例都采用相同的参数。也许有一个比我见过的更优雅/灵活的解决方案。
【解决方案2】:

如果参数因函数而异,我认为@Nix 建议的参数收集是一个很好的建议。或者,您可以使用对象的 var arg。但是您可能需要在调用函数之前检查所有参数是否存在,例如前置条件检查。 或者可能是两者的组合,您会知道某些参数始终是必需的,而其他参数是可选的。所以使用 varargs 作为可选参数,如下所示用于 filterResults

public ModelAndView filterResults(@SessionAttribute(value="sessionAttr") Pojo pojo,
                                  @RequestAttribute("requestAttr") String requestAttr,
                                  @ModelAttribute("command") CommandPojo2 commandPojo2,
                                  Object...restOfParameters){}

这可以与前面讨论的模板模式结合使用。

【讨论】:

  • 避免在控制器中“泛化”参数。它隐藏了控制器与其依赖服务之间的功能关系,但收效甚微。
【解决方案3】:

我和你有同样的问题。我还没有一个干净的解决方案,但我相信我已经取得了一些进展,所以我想与你分享我到目前为止的发现。

我按照three_cups_of_java 的建议探索了拦截器的使用,但遇到了各种问题(如下所述)。目前我正在尝试使用自定义 AnnotationMethodHandlerAdapter,但我还没有完成这项工作。

拦截器

由于拦截器无法访问它们拦截的控制器对象(更正:它们确实可以访问它,但对执行流程的控制有限),控制器和拦截器必须通过会话中的对象进行通信。

这是我的意思的一个稍微简化的例子:

在我们的旧架构中,我们有自己的基础控制器,每个人都可以扩展。它本身扩展了 MultiActionController,并添加了一些自定义行为 - 例如在您的示例中,在发布请求 before 调用处理程序方法之后更新服务器端视图。这是因为所有控制器都提供了模板方法的实现(例如getViewKeyInSession())。

因此,基本控制器中的自定义代码大致如下所示:

// inside handleRequestInternal method
if (request.getMethod().equals("POST") {
    updateViewAfterPost (session.get(getViewKeyInSession());
}
return super.handleRequestInternal();

现在,当我们将此代码移至拦截器时,我们遇到了几个问题:

  1. 拦截器无法调用 getViewKeyInSession(),迫使我们对所有控制器使用相同的会话密钥(不好),或者遵守一些约定,即视图的会话密钥基于 url 或参数请求(到目前为止这也不好)。
  2. 单个控制器不能再覆盖updateModelAfterPost 的行为。这通常不是必需的,但不幸的是,对于某些控制器来说这是必需的。
  3. 如果控制器提供了 updateModelAfterPost 的实现,并希望向拦截器发出信号,它对拦截器的帮助不感兴趣,则需要在会话中放置一个标记对象以供拦截器查看,然后需要在之前的 GET 请求期间执行(也不好且不灵活)。

使用自定义 AnnotationMethodHandlerAdapter

目前我正在考虑直接在我的 xml 中指定 DefaultAnnotationHandlerMapping(而不是 mvc:annotation-driven),然后为其提供自定义 AnnotationMethodHandlerAdapter

正如我之前所说,我没有取得足够的进展来呈现完整的结果,但是我的目标是这样的:

我认为 AnnotationMethodHandlerAdapter 是 Spring 提供的 MultiActionController,但用于 pojo 控制器。例如,我已经知道如何插入我自己的方法解析器(参见 question)和其他 Spring 好东西。

这个适配器有几个方法可以覆盖,例如
invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler),
也许
handle(HttpServletRequest request, HttpServletResponse response, Object handler)
也是。

在您的自定义代码中,您可以检查处理程序类,然后采取相应措施。继续我之前的示例,如果处理程序类有一个方法updateViewAfterPost 或者如果它实现了某个接口,那么您可以调用该方法,然后调用super 让spring 继续进行常规调用。因此,代码大致如下所示:

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    // inspect handler object, look for marker interface, methods and/or annotations
    // perform pre-processing on the handler object
    // e.g. handler.updateViewAfterPost(request, response)
    ModelAndView mav = super.handle (request, response, handler);
    // post-processing on the handler object
    return mav;
}

(当然,这只是一个玩具示例。在实际代码中,您需要更好的异常处理)

更新:

我用自定义AnnotationMethodHandlerAdapter 尝试了上述策略,它确实有效。我在我的 pojo 控制器上使用了一个标记接口,并且只在生命周期中引入了一个名为 updateModelAfterPost 的新方法,它按预期工作。

我遇到了几个小警告,主要是因为我在同一个 mvc 上下文中将旧方法与新方法结合起来。您可以在下面看到我对 xml 上下文所做的更改,然后是我认为值得强调的问题的列表。

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="order" value="2" />
 </bean>

<bean class="com.sample.MyAnnotationMethodHandlerAdapter">
    <property name="order" value="2" />
</bean>

<bean class="com.sample.MySimpleControllerHandlerAdapter" >
    <property name="order" value="1" />
</bean>

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="order" value="1" />
    <property name="mappings">
        <props>
            ...
        </props>
    </property>
</bean>
  • 正如评论中提到的,我展开了 简写。我必须明确定义两个处理程序映射,并定义两个处理程序适配器。
  • 不幸的是,在我的遗留代码中,一些控制器是跨国家的,并由 cglib 代理。 AnnotationMethodHandlerAdapter 不能很好地处理这个问题,因此我设置了元素的顺序,使得遗留处理程序映射和处理程序适配器首先起作用,然后基于注释的处理程序映射和处理程序适配器起作用。
  • 我必须明确定义 Spring 的 SimpleControllerHandlerAdapter,但我还必须用我自己的类扩展它,因为它没有实现 Ordered 接口。
  • 我在定义验证器时遇到了问题,因为我没有 jsr-303 的 jar。因此我放弃了验证器和转换服务的声明。上面的xml sn-p正是我用的,不是为了回答而简化的精简版。

最后,这里是相关类的代码:

package com.sample;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;

public class MyAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter {

    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof MyMarkerInterface) {
            MyMarkerInterface handler2 = (MyMarkerInterface) handler;
            handler2.updateModelAfterPost(request);
        }
        return super.invokeHandlerMethod(request, response, handler);
    }

}


package com.sample;

import org.springframework.core.Ordered;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;

public class MySimpleControllerHandlerAdapter extends SimpleControllerHandlerAdapter implements Ordered {

    private int order = 0;

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }


}

【讨论】:

  • 那么你"unroll" mvc:annotation-driven 了吗?我还没有展开,但能够添加 Flash Scope 而无需展开 XML,但看不到处理程序的简单覆盖(或者它是否像使用 handlerMapping 创建 bean id 一样简单?
  • 是的,我展开了它。不是因为我不喜欢 mvc:annotation-driven,而是因为我正在尝试将新功能和我们的遗留系统集成在一起,以便它们在相同的上下文中“快乐地”并排生活。展开后轻松很多
  • 我很想看看你得到的任何进一步的结果。我可能会在早上给你赏金。
  • 嘿,斯科特,在经历了这么多麻烦之后,我发现拦截器确实可以访问处理程序对象!看看 HandlerInterceptor preHandle 和 postHandle 方法的 api。我怀疑您可以使用我展示的相同代码 sn-ps,但在拦截器中而不是自定义处理程序适配器中。
  • 没错。那时我唯一的抱怨是我们将失去一些测试事物的能力,因为我们现在可以神奇地访问现在不再需要作为参数的变量。
【解决方案4】:

如果你想要可重用性,你真的应该研究一下 spring webflow,如果你还没有的话。简而言之,webflow 是一个高级的 spring mvc 控制器,可以更好地分离视图层和业务逻辑。控制器接受请求,映射并验证您的模型,将请求委托给正确的业务服务,最后根据调用的服务的结果和模型的状态决定呈现哪个视图。

一切都是通过 xml 配置的,这为您提供了所有 webflow 逻辑和导航所在的单个点(如果您不喜欢 xml,可以使用 eclipse 插件来可视化导航)。如果您需要以老式方式处理某些请求,Spring webflow 也可以与其他 mvc 控制器配合使用。最后但并非最不重要的一点是,spring webflow 为您的变量添加了一些非常方便的范围。除了 request 、 session 和 application 之外,您还可以获得 flow 和 conversation 范围,这有点像 session 范围,但仅适用于当前应用程序窗口。这意味着您可以在浏览器中拥有多个窗口/选项卡,而不会相互干扰。

但是您应该自己检查一下,spring 网站上有一个小的参考指南,以及他们的 svn 存储库中的多个演示。此外,“spring in action”一书也涉及到 webflow 的主题。希望这有用。

http://www.springsource.org/webflow

【讨论】:

    猜你喜欢
    • 2013-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-10
    • 2017-09-21
    • 2013-05-14
    • 1970-01-01
    相关资源
    最近更新 更多