javascript
Spring MVC应用程序中的Thymeleaf模板布局,无扩展
在使用JSP / JSTL和Apache Tiles幾年之后,我開始為我的Spring MVC應(yīng)用程序發(fā)現(xiàn)Thymeleaf。 Thymeleaf是一個(gè)非常出色的視圖引擎,盡管目前缺乏良好的IntelliJ(投票:http: //youtrack.jetbrains.com/issue/IDEABKL-6713 )支持,但它簡化并加快了開發(fā)速度(有Eclipse)插件 )。 在學(xué)習(xí)如何使用Thymeleaf的同時(shí),我研究了使用布局的不同可能性。
除了本機(jī)片段包含機(jī)制之外 ,還至少有兩個(gè)選項(xiàng)可用于布局: Thymeleaf與Apache Tile的集成以及Thymeleaf Layout Dialect 。 兩者似乎都可以正常工作,但是受關(guān)于簡單和自定義選項(xiàng)的評論的啟發(fā),我嘗試了一下。 在這篇文章中,我將展示我創(chuàng)建了解決方案。
創(chuàng)建具有Thymeleaf支持的Spring MVC項(xiàng)目
為了快速入門,我在Thymeleaf 2.1支持下使用了Spring MVC原型 。 我通過簡單地調(diào)用原型創(chuàng)建了一個(gè)項(xiàng)目,然后將其導(dǎo)入到IntellJ中。
創(chuàng)建布局文件
在WEB-INF / views目錄中,我創(chuàng)建了一個(gè)布局文件夾,在其中放置了第一個(gè)名為default.html的布局文件:$ {view}變量將包含@Controller返回的視圖名稱和$ {view}中的內(nèi)容片段文件將放置在這里。
創(chuàng)建視圖文件
我編輯了WEB-INF / views / homeNotSignedIn.html,并按如下方式定義了內(nèi)容片段:因此,唯一的更改是定義名為content的片段并刪除重復(fù)的片段包含。 無需其他更改。 @Controller返回原始視圖名稱,與之前一樣:
@Controller class HomeController {@RequestMapping(value = "/", method = RequestMethod.GET)String index(Principal principal) {return principal != null ? "home/homeSignedIn" : "home/homeNotSignedIn";} }我相應(yīng)地改變了其他觀點(diǎn)。
創(chuàng)建攔截器并與Spring MVC集成
為了完成“新布局框架”,我創(chuàng)建了一個(gè)處理程序攔截器來完成工作:
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方法,因?yàn)樗窃诔尸F(xiàn)視圖之前執(zhí)行的。
添加攔截器很容易:
@Configuration public class WebMvcConfig extends WebMvcConfigurationSupport {@Overrideprotected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new ThymeleafLayoutInterceptor());} }這就是基本配置。 此后沒有火箭。 轉(zhuǎn)到localhost:8080后的結(jié)果。 這是我所期望的。 奇跡般有效。 因此,我嘗試注冊一個(gè)帳戶以及提交表單后看到的內(nèi)容:
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 當(dāng)然,
重定向:/提交表單后。 我需要像這樣修改攔截器:
它按預(yù)期工作。 但是我意識(shí)到我需要定義和附加布局,因?yàn)镾ignup和Signin之前(而不是在應(yīng)用上述更改之后)使用了此布局。
創(chuàng)建其他布局
我創(chuàng)建了一個(gè)名為blank.html的新布局,并將其放置到WEB-INF / views / layouts文件夾中。 但是如何使用選擇布局? 可能有很多方法可以做到這一點(diǎn)。 我最簡單的方法之一就是通過簡單地添加一個(gè)名為layout的模型屬性來從@Controller返回布局名稱。 如果未給出布局,則使用默認(rèn)布局,否則使用給定布局。 簡單。 但是我想要一個(gè)更強(qiáng)大的解決方案。 所以我想也許我可以這樣使用注釋:
@Controller class SigninController {@Layout(value = "layouts/blank")@RequestMapping(value = "signin")String signin() {return "signin/signin";} }對我來說,這聽起來像是一個(gè)很好的解決方案。 所以我實(shí)現(xiàn)了它。
選擇布局
我創(chuàng)建了一個(gè)方法級別@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();}} }現(xiàn)在,當(dāng)使用@Layout注釋對處理程序方法進(jìn)行注釋時(shí),它將獲得其value屬性。 效果很好。 但是,當(dāng)我開始更改SignupController時(shí),我意識(shí)到我需要注釋這兩種方法。 如果可以通過對@Controller類進(jìn)行注釋,將我的注釋一次用于所有方法,那就更好了:
@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;} }如您所見,方法級別注釋比類型級別注釋更重要,這提供了一定的靈活性。 此外,我還添加了配置攔截器的可能性。 我認(rèn)為,設(shè)置默認(rèn)布局名稱和視圖屬性名稱可能會(huì)很有用。
摘要
提出的解決方案可能需要一些改進(jìn)才能在生產(chǎn)中使用,但是它顯示了我們可以簡單地構(gòu)建模板布局而無需在項(xiàng)目中添加額外的庫并僅利用Thymeleaf的核心功能。 請分享您對解決方案的評論和意見。
- 請?jiān)贕itHub上找到源代碼: https : //github.com/kolorobot/thymeleaf-custom-layout
翻譯自: https://www.javacodegeeks.com/2013/11/thymeleaf-template-layouts-in-spring-mvc-application-with-no-extensions.html
總結(jié)
以上是生活随笔為你收集整理的Spring MVC应用程序中的Thymeleaf模板布局,无扩展的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 券商集合理财产品的优缺点
- 下一篇: 使用JSF 2.2功能来开发可滚动,可延