【问题标题】:Spring MVC complex model population from multiple sources来自多个来源的 Spring MVC 复杂模型填充
【发布时间】:2011-07-19 23:31:19
【问题描述】:

好吧,我的问题可能听起来有点模糊,但无论如何,就是这样。 我正在使用 Spring MVC 3.1.M1、JSP 2.1 构建一个 Web 应用程序(没有 Tiles,我使用纯 JSP 标记文件来组成我的布局)。

基本上,我的页面是使用一些常见部分的布局构建的 - 页眉、页脚、横幅、菜单等。这些部分大部分是动态的,即包含当前用户的相关信息。

JSP 没有“组件”概念,因此我无法在某个地方定义模板的一部分及其支持的 Java 代码,并将它们耦合在一起。在我的@Controllers 中,我必须完全填充我的模型,包括页眉、页脚、菜单和其他内容的数据。我真正想做的是避免这种代码重复。带有一些通用模型填充方法的抽象 BaseController 类也不好看。

JSP 和 Spring MVC 经常一起使用,所以我希望在这个主题上存在一些最佳实践。 让我们讨论一下。

【问题讨论】:

  • 在您的 JSP 中,听起来您有不止 1 个模型对象,对吗?
  • 嗯,这是一个值得讨论的话题。目前我有 1 个模型对象,但如果它有助于我消除代码重复并实现简单的架构,我可以将其拆分为两个或更多。
  • @mkoryak @o-richie-nal 我发现了一篇关于这个主题的好文章developingdeveloper.wordpress.com/2008/02/28/…。它相对较旧,涵盖了更简单的案例(应用程序范围的参考数据,但不是布局范围或组件范围的数据),但仍然非常有用。

标签: java templates jsp layout spring-mvc


【解决方案1】:

好的,所以我花了一些时间研究 Spring MVC 参考和示例应用程序,并找到了一些其他方法来完成我的任务。他们在这里:

1) 方法一,糟糕且无法使用,这里仅提一下。具有 populateHeaderData(Model model)、populateFooterData(Model model) 等方法的抽象 BaseController。所有扩展 BaseController 的控制器类中的所有 @RequestMapping 方法都会调用这些方法来填充特定于布局的模型数据。

优点:没有

缺点:代码重复保持不变,只是重复代码的数量减少了

2) @ModelAttribute 方法,即隐式模型丰富。好像

@Controller
@RequestMapping(value="/account")
public class AccountController {

    @ModelAttribute("visitorName")
    private String putVisitor() {
        return visitorService.getVisitorName();
    }

    // handler methods
}

而在 JSP 中,

<span id="username">Welcome, ${visitorName}!</span>

优点:无需显式调用模型扩充方法 - 它可以正常工作

缺点:这是一件棘手的事情。 Spring MVC 使用“推”模板模型而不是“拉”。在这种情况下,这意味着当调用此类中定义的任何@RequestMapping 方法时,将调用此类的所有@ModelAttribute 方法。模板是否真的需要 visitorName 和模板是否确实存在用于特定操作没有区别。这包括表单提交的 POST 请求等。事实上,这迫使我们改变控制器分离。例如,所有表单提交都应该在单独的控制器类中,并且处理程序方法应该以某种方式按布局分组。我得好好想想,也许它并没有乍看之下那么糟糕。

更多缺点:假设我们的布局 A 和 B 具有相同的非静态页眉,而 B 和 C 具有相同的非静态页脚(所有其他部分都不同)。我们无法为布局 B 实现基类,因为 Java 中没有多重继承。

向观众提问: Spring MVC 参考声明“处理程序方法支持以下返回类型:ModelAndView 对象,模型隐含地丰富了命令对象和 @ModelAttribute 注释的引用数据访问器方法的结果......”。这些命令对象是什么鬼?

3) 我自己的 pull-like 方法。我们可以以

的形式创建自定义上下文
@Component("headerContext")
public class HeaderContext {

    @Autowired
    private VisitorService visitorService;

    public String getVisitorName() {
        return visitorService.getVisitorName();
    }

    // more getters here

}

然后,通过

将这些 bean 公开给 JSP EL
<!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/"/>
    <beans:property name="suffix" value=".jsp"/>
    <beans:property name="exposedContextBeanNames" value="headerContext,footerContext"/>
</beans:bean>

并且在header.tag(重用头的JSP标签文件)中

<span id="username">Welcome, ${headerContext.visitorName}!</span>

优点:“拉动”策略(没有人问 - 没有任何内容),易于创建上下文 @Scope("request") 并启用请求范围的缓存,多重继承没有问题。只需在一处编码,一处配置,即可在任何 JSP 或标记文件中作为常用表达式使用。

缺点: 在一个框架内混合推入和拉取(必须考虑更多),上下文实现类中不支持 Spring MVC(我的意思是控制器处理程序方法中的这些讨厌的预填充参数),只是春豆。

【讨论】:

  • 命令对象是请求参数(隐式转换后),是控制器的参数。我想。
  • 你也可以直接从你的jsp调用服务:欢迎,${visitorService.visitorName}!。我一直这样做,只在控制器中填充模型属性,这些属性是作为用户输入的结果在那里计算的。
【解决方案2】:

springframework 包含处理程序拦截器作为处理程序映射机制的一部分。
在拦截器中,您可以在实际处理程序执行之前使用postHandle 方法。

这样的拦截器必须实现org.springframework.web.servlet.HandlerInterceptororg.springframework.web.servlet.handler.HandlerInterceptorAdapter以简化实现。

public class MyHandlerInterceptor extends HandlerInterceptorAdapter {

    public void postHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {

        //populate header, menu, footer, ... model
    }
}

以及处理程序映射的配置。

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
    <list>
        <bean id="myInterceptor" class="...MyHandlerInterceptor"/>
    </list>
</property>

【讨论】:

  • 感谢您的回复,O-richie!但是,如果我有一些布局 - 一些有菜单,一些没有,并且只想在我的页面需要时使用菜单数据填充模型?
  • handler 参数包含执行的处理程序,或者您可以使用 request.getRequestURI() 获取请求的 url
【解决方案3】:

最后,我决定坚持使用@ModelAttribute 方法,尽管它有局限性。

/**
 * Base class for all page controllers (i.e., not form submits)
 * @author malexejev
 * 23.03.2011
 */
public abstract class AbstractPageController {

    @Autowired
    private VisitorService visitorService;

    @Autowired
    private I18nSupport i18nSupport;

    @Value("${xxx.env}")
    private String environment;

    /**
     * Implicit model enrichment with reference data.
     * No heavy operations allowed here, since it is executed before any handler method of 
     * all extending controllers
     */
    @ModelAttribute("appContext")
    public Map<String, Object> populateReferenceData(HttpServletRequest request) {
        Map<String, Object> dataMap = new HashMap<String, Object>();

        // FIXME some data is app-wide and constant, no need to re-create such map entries
        // I should take care about it when more reference data is added
        dataMap.put("visitorName", visitorService.getVisitorName());
        dataMap.put("env", environment);
        dataMap.put("availableLanguages", i18nSupport.getAvailableLanguages());
        dataMap.put("currentPath", request.getPathInfo() != null ? request.getPathInfo() : request.getServletPath());

        return Collections.unmodifiableMap(dataMap);
    }

}

这样我可以通过 ${appContext.visitorName} 获取视图中的数据。它允许我透明地切换到 Spring bean 实现(参见上面我的答案中的第 3 点,@Component("headerContext")),以防@ModelAttributes 将来出现任何问题。

感谢大家的讨论。我在这里没有看到任何“银弹”解决方案,所以我不会将任何答案标记为已接受,但会投票赞成这个问题的所有答案。

【讨论】:

    【解决方案4】:

    你有几个选择,虽然它们也不完美..

    1. 你提到的抽象控制器
    2. 创建一个将返回模型数据的服务。现在您已将问题转移到它可能不属于的服务层,但至少您的控制器可以在每个控制器方法期间只进行一次服务调用。
    3. 创建一个过滤器并在过滤器中填充模型的公共部分。
    4. 你可能会创建一些带有注释的怪物,例如,注释控制器方法,然后对控制器对象进行后期处理以注入数据(这个,我不知道该怎么做,但必须有办法)
    5. spring AOP 或许能帮你更优雅地完成#4

    这些只是引发讨论的一些想法

    【讨论】:

    • 酷,讨论开始了。今晚阅读了整个 Spring MVC 参考后,我也有一些信息,明天再补充。
    【解决方案5】:

    处理程序拦截器适用于每个页面中使用的共享数据。

    如果你想要细粒度的“组件”,你真的应该重新考虑使用 apache 瓦片。 从那里,您可以对此处指出的每个图块使用“控制器”(ViewPreparer):

    http://richardbarabe.wordpress.com/2009/02/19/apache-tiles-2-viewpreparer-example/

    【讨论】:

      猜你喜欢
      • 2020-03-29
      • 1970-01-01
      • 1970-01-01
      • 2019-01-03
      • 2014-11-15
      • 2021-07-18
      • 1970-01-01
      • 2021-11-19
      • 2020-04-23
      相关资源
      最近更新 更多