Spring MVC复杂模型来自多个来源

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

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

JSP没有“组件”概念,所以我无法在一些地方定义我的模板的一部分及其支持java代码,而是在一起。 在我的@Controllers中,我必须完全填充我的模型,包括页眉,页脚,菜单和其他内容的数据。 我真正想要做的是避免这种代码重复。 使用一些通用模型填充方法的抽象BaseController类也不太好看。

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

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

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

优点:没有

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

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

@Controller @RequestMapping(value="/account") public class AccountController { @ModelAttribute("visitorName") private String putVisitor() { return visitorService.getVisitorName(); } // handler methods } 

在JSP中,

 Welcome, ${visitorName}! 

优点:无需明确调用模型丰富方法 – 它只是有效

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

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

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

3)我自己的拉式方法。 我们可以以一种forms创建自定义上下文

 @Component("headerContext") public class HeaderContext { @Autowired private VisitorService visitorService; public String getVisitorName() { return visitorService.getVisitorName(); } // more getters here } 

然后,将这些bean暴露给JSP EL

       

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

 Welcome, ${headerContext.visitorName}! 

优点: “拉”策略(没有人要求 – 没有任何东西可以使用),易于制作上下文@Scope(“请求”)并启用请求范围的缓存,多重inheritance没有问题。 只编码在一个地方,在一个地方配置,可以在任何JSP或标记文件中用作通常的表达式。

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

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 } } 

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

       

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

 /** * Base class for all page controllers (ie, 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 populateReferenceData(HttpServletRequest request) { Map dataMap = new HashMap(); // 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实现(参见上面的答案中的No 3,@ Component(“headerContext”)),以防将来出现@ModelAttributes问题。

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

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

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

这些只是进行一些讨论的一些想法

handler interceptor适用于每个页面中使用的共享数据。

如果你想要细粒度的“组件”,你真的应该重新考虑使用apache瓷砖。 从那里你可以为每个瓷砖使用一个“控制器”( ViewPreparer ),如下所示:

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