热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringMVC应用程序中的Thymeleaf模板布局,无扩展

在使用JSPJSTL和ApacheTiles几年之后,我开始为我的SpringMVC应用程序发现Thymeleaf。Thymeleaf是一个非常出色的视图引擎

在使用JSP / JSTL和Apache Tiles几年之后,我开始为我的Spring MVC应用程序发现Thymeleaf。 Thymeleaf是一个非常出色的视图引擎,尽管目前缺乏良好的IntelliJ(投票:http: //youtrack.jetbrains.com/issue/IDEABKL-6713 )支持,但它简化并加快了开发速度(有Eclipse)插件 )。 在学习如何使用Thymeleaf的同时,我研究了使用布局的不同可能性。

除了本机片段包含机制之外 ,还至少有两个选项可用于布局: Thymeleaf与Apache Tile的集成以及Thymeleaf Layout Dialect 。 两者似乎都可以正常工作,但是受关于简单和自定义选项的评论的启发,我尝试了一下。 在这篇文章中,我将展示我创建了解决方案。

创建具有Thymeleaf支持的Spring MVC项目

为了快速入门,我在Thymeleaf 2.1支持下使用了Spring MVC原型 。 我通过简单地调用原型创建了一个项目,然后将其导入到IntellJ中。

创建布局文件

在WEB-INF / views目录中,我创建了一个布局文件夹,在其中放置了第一个名为default.html的布局文件:$ {view}变量将包含@Controller返回的视图名称和$ {view}中的内容片段文件将放置在这里。

创建视图文件

我编辑了WEB-INF / views / homeNotSignedIn.html,并按如下方式定义了内容片段:因此,唯一的更改是定义名为content的片段并删除重复的片段包含。 无需其他更改。 @Controller返回原始视图名称,与之前一样:

@Controller
class HomeController {@RequestMapping(value = "/", method = RequestMethod.GET)String index(Principal principal) {return principal != null ? "home/homeSignedIn" : "home/homeNotSignedIn";}
}

我相应地改变了其他观点。

创建拦截器并与Spring MVC集成

为了完成“新布局框架”,我创建了一个处理程序拦截器来完成工作:

public class ThymeleafLayoutInterceptor extends HandlerInterceptorAdapter {private static final String DEFAULT_LAYOUT = "layouts/default";private static final String DEFAULT_VIEW_ATTRIBUTE_NAME = "view";@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {if (!modelAndView.hasView()) {return;}String originalViewName = modelAndView.getViewName();modelAndView.setViewName(DEFAULT_LAYOUT);modelAndView.addObject(DEFAULT_VIEW_ATTRIBUTE_NAME, originalViewName);}
}

ThymeleafLayoutInterceptor获取从处理程序的方法返回的原始视图名称,并将其替换为布局名称(在WEB-INF / views / layouts / default.html中定义)。 原始视图作为视图变量放置在模型中,因此可以在布局文件中使用它。 我覆盖了postHandle方法,因为它是在呈现视图之前执行的。

添加拦截器很容易:

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {@Overrideprotected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new ThymeleafLayoutInterceptor());}
}

这就是基本配置。 此后没有火箭。 转到localhost:8080后的结果。 这是我所期望的。 奇迹般有效。 因此,我尝试注册一个帐户以及提交表单后看到的内容:

500 returned for /signup with message Error resolving template "redirect:/", template might not exist or might not be accessible by any of the configured Template Resolvers

当然,
重定向:/提交表单后。 我需要像这样修改拦截器:

public class ThymeleafLayoutInterceptor extends HandlerInterceptorAdapter {private static final String DEFAULT_LAYOUT = "layouts/default";private static final String DEFAULT_VIEW_ATTRIBUTE_NAME = "view";@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {if (!modelAndView.hasView()) {return;}String originalViewName = modelAndView.getViewName();if (isRedirectOrForward(originalViewName)) {return;}modelAndView.setViewName(DEFAULT_LAYOUT);modelAndView.addObject(DEFAULT_VIEW_ATTRIBUTE_NAME, originalViewName);} private boolean isRedirectOrForward(String viewName) {return viewName.startsWith("redirect:") || viewName.startsWith("forward:");}
}

它按预期工作。 但是我意识到我需要定义和附加布局,因为Signup和Signin之前(而不是在应用上述更改之后)使用了此布局。

创建其他布局

我创建了一个名为blank.html的新布局,并将其放置到WEB-INF / views / layouts文件夹中。 但是如何使用选择布局? 可能有很多方法可以做到这一点。 我最简单的方法之一就是通过简单地添加一个名为layout的模型属性来从@Controller返回布局名称。 如果未给出布局,则使用默认布局,否则使用给定布局。 简单。 但是我想要一个更强大的解决方案。 所以我想也许我可以这样使用注释:

@Controller
class SigninController {@Layout(value = "layouts/blank")@RequestMapping(value = "signin")String signin() {return "signin/signin";}
}

对我来说,这听起来像是一个很好的解决方案。 所以我实现了它。

选择布局

我创建了一个方法级别@Layout批注,并将其放置在org.thymeleaf.spring.support包中(与ThymeleafLayoutInterceptor一起):

package org.thymeleaf.spring.support;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Layout {String value() default "";
}

我将拦截器更改如下:

public class ThymeleafLayoutInterceptor extends HandlerInterceptorAdapter {private static final String DEFAULT_LAYOUT = "layouts/default";private static final String DEFAULT_VIEW_ATTRIBUTE_NAME = "view";@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {if (!modelAndView.hasView()) {return;}String originalViewName = modelAndView.getViewName();if (isRedirectOrForward(originalViewName)) {return;}String layoutName = getLayoutName(handler);modelAndView.setViewName(layoutName);modelAndView.addObject(DEFAULT_VIEW_ATTRIBUTE_NAME, originalViewName);}private boolean isRedirectOrForward(String viewName) {return viewName.startsWith("redirect:") || viewName.startsWith("forward:");}private String getLayoutName(Object handler) {HandlerMethod handlerMethod = (HandlerMethod) handler;Layout layout = handlerMethod.getMethodAnnotation(Layout.class);if (layout == null) {return DEFAULT_LAYOUT;} else {return layout.value();}}
}

现在,当使用@Layout注释对处理程序方法进行注释时,它将获得其value属性。 效果很好。 但是,当我开始更改SignupController时,我意识到我需要注释这两种方法。 如果可以通过对@Controller类进行注释,将我的注释一次用于所有方法,那就更好了:

@Controller
@Layout(value = "layouts/blank")
class SignupController {}

所以我做了。

最后的修饰

首先,我更改了注释,以便可以将其定位为类型级别:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Layout {String value() default "";
}

和拦截器:

public class ThymeleafLayoutInterceptor extends HandlerInterceptorAdapter {private static final String DEFAULT_LAYOUT = "layouts/default";private static final String DEFAULT_VIEW_ATTRIBUTE_NAME = "view";private String defaultLayout = DEFAULT_LAYOUT;private String viewAttributeName = DEFAULT_VIEW_ATTRIBUTE_NAME;public void setDefaultLayout(String defaultLayout) {Assert.hasLength(defaultLayout);this.defaultLayout = defaultLayout;}public void setViewAttributeName(String viewAttributeName) {Assert.hasLength(defaultLayout);this.viewAttributeName = viewAttributeName;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {if (!modelAndView.hasView()) {return;}String originalViewName = modelAndView.getViewName();if (isRedirectOrForward(originalViewName)) {return;}String layoutName = getLayoutName(handler);modelAndView.setViewName(layoutName);modelAndView.addObject(this.viewAttributeName, originalViewName);}private boolean isRedirectOrForward(String viewName) {return viewName.startsWith("redirect:") || viewName.startsWith("forward:");}private String getLayoutName(Object handler) {HandlerMethod handlerMethod = (HandlerMethod) handler;Layout layout = getMethodOrTypeAnnotation(handlerMethod);if (layout == null) {return this.defaultLayout;} else {return layout.value();}}private Layout getMethodOrTypeAnnotation(HandlerMethod handlerMethod) {Layout layout = handlerMethod.getMethodAnnotation(Layout.class);if (layout == null) {return handlerMethod.getBeanType().getAnnotation(Layout.class);}return layout;}
}

如您所见,方法级别注释比类型级别注释更重要,这提供了一定的灵活性。 此外,我还添加了配置拦截器的可能性。 我认为,设置默认布局名称和视图属性名称可能会很有用。

摘要

提出的解决方案可能需要一些改进才能在生产中使用,但是它显示了我们可以简单地构建模板布局而无需在项目中添加额外的库并仅利用Thymeleaf的核心功能。 请分享您对解决方案的评论和意见。

  • 请在GitHub上找到源代码: https : //github.com/kolorobot/thymeleaf-custom-layout

参考: Spring MVC应用程序中的Thymeleaf模板布局,在Codeleak.pl博客上没有 JCG合作伙伴 Rafal Borowiec的扩展 。

翻译自: https://www.javacodegeeks.com/2013/11/thymeleaf-template-layouts-in-spring-mvc-application-with-no-extensions.html



推荐阅读
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • andr ... [详细]
  • 解决JAX-WS动态客户端工厂弃用问题并迁移到XFire
    在处理Java项目中的JAR包冲突时,我们遇到了JaxWsDynamicClientFactory被弃用的问题,并成功将其迁移到org.codehaus.xfire.client。本文详细介绍了这一过程及解决方案。 ... [详细]
  • 本文探讨了在Java中实现系统托盘最小化的两种方法:使用SWT库和JDK6自带的功能。通过这两种方式,开发者可以创建跨平台的应用程序,使窗口能够最小化到系统托盘,并提供丰富的交互功能。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • PyCharm下载与安装指南
    本文详细介绍如何从官方渠道下载并安装PyCharm集成开发环境(IDE),涵盖Windows、macOS和Linux系统,同时提供详细的安装步骤及配置建议。 ... [详细]
  • 探讨如何高效使用FastJSON进行JSON数据解析,特别是从复杂嵌套结构中提取特定字段值的方法。 ... [详细]
  • 如何在窗口右下角添加调整大小的手柄
    本文探讨了如何在传统MFC/Win32 API编程中实现类似C# WinForms中的SizeGrip功能,即在窗口的右下角显示一个用于调整窗口大小的手柄。我们将介绍具体的实现方法和相关API。 ... [详细]
  • 作为一名新手,您可能会在初次尝试使用Eclipse进行Struts开发时遇到一些挑战。本文将为您提供详细的指导和解决方案,帮助您克服常见的配置和操作难题。 ... [详细]
  • 使用Python在SAE上开发新浪微博应用的初步探索
    最近重新审视了新浪云平台(SAE)提供的服务,发现其已支持Python开发。本文将详细介绍如何利用Django框架构建一个简单的新浪微博应用,并分享开发过程中的关键步骤。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
author-avatar
日月星辰淡定鍀好男孩_933
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有