![thymeleaf菜鸟教程](https://img-blog.csdnimg.cn/img_convert/216ac16c71a902d8657061519d13bbba.png)
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返回原始视图名称,就像以前一样:
html" title=java>java">@Controller
class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Principal principal) {
return principal != null ? "home/homeSignedIn" : "home/homeNotSignedIn";
}
}
我相应地改变了其他观点。
创建拦截器并与Spring MVC集成
为了完成“新布局框架”,我创建了一个处理程序拦截器来完成工作:
html" title=java>java">public class ThymeleafLayoutInterceptor extends HandlerInterceptorAdapter {
private static final String DEFAULT_LAYOUT = "layouts/default";
private static final String DEFAULT_VIEW_ATTRIBUTE_NAME = "view";
@Override
public 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方法,因为它是在呈现视图之前执行的。
添加拦截器很容易:
html" title=java>java">@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ThymeleafLayoutInterceptor());
}
}
这就是基本配置。 此后没有火箭。 转到localhost:8080后的结果。 这是我所期望的。 奇迹般有效。 因此,我尝试注册一个帐户以及提交表单后看到的内容:
html" title=java>java">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
当然,重定向:/提交表单后。 我需要像这样修改拦截器:
html" title=java>java">public class ThymeleafLayoutInterceptor extends HandlerInterceptorAdapter {
private static final String DEFAULT_LAYOUT = "layouts/default";
private static final String DEFAULT_VIEW_ATTRIBUTE_NAME = "view";
@Override
public 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返回布局名称。 如果未给出布局,则使用默认布局,否则使用给定布局。 简单。 但是我想要一个更强大的解决方案。 所以我想也许我可以这样使用注释:
html" title=java>java">@Controller
class SigninController {
@Layout(value = "layouts/blank")
@RequestMapping(value = "signin")
String signin() {
return "signin/signin";
}
}
对我来说,这听起来像是一个很好的解决方案。 所以我实现了它。
选择布局
我创建了一个方法级别@Layout批注,并将其放置在org.thymeleaf.html" title=spring>spring.support包中(与ThymeleafLayoutInterceptor一起):
html" title=java>java">package org.thymeleaf.html" title=spring>spring.support;
import html" title=java>java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Layout {
String value() default "";
}
我将拦截器更改如下:
html" title=java>java">public class ThymeleafLayoutInterceptor extends HandlerInterceptorAdapter {
private static final String DEFAULT_LAYOUT = "layouts/default";
private static final String DEFAULT_VIEW_ATTRIBUTE_NAME = "view";
@Override
public 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类进行注释,将我的注释一次用于所有方法,那就更好了:
html" title=java>java">@Controller
@Layout(value = "layouts/blank")
class SignupController {
}
所以我做了。
最后的润色
首先,我更改了注释,以便可以将其定位为类型级别:
html" title=java>java">@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Layout {
String value() default "";
}
和拦截器:
html" title=java>java">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;
}
@Override
public 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
翻译自: https://www.html" title=java>javacodegeeks.com/2013/11/thymeleaf-template-layouts-in-html" title=spring>spring-mvc-application-with-no-extensions.html
thymeleaf菜鸟教程