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

时间:2011-03-21 20:16:32

标签: java templates jsp layout spring-mvc

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

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

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

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

5 个答案:

答案 0 :(得分:4)

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>

答案 1 :(得分:4)

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

1)第一种方式,糟糕且无法使用,这里就提一下。抽象BaseController的方法有populateHeaderData(模型模型),populateFooterData(模型模型)等。扩展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)我自己的拉式方法。我们可以以

的形式创建自定义上下文
@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(“请求”)并启用请求范围的缓存,多重继承没有问题。只需在一个地方编码,在一个地方配置,可以在任何JSP或标记文件中用作通常的表达式。

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

答案 2 :(得分:2)

最后,我决定坚持@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问题。

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

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

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

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