3 传递参数
Thymeleaf 在抽取和引入公共页面片段时,还可以进行参数传递,引用公共页面片段时,我们可以通过以下 2 种方式,将参数传入到被引用的页面片段中:
- 模板名::选择器名或片段名(参数1=参数值1,参数2=参数值2)
- 模板名::选择器名或片段名(参数值1,参数值2)
我们一般使用第一种,示例如下:
<!--th:insert 片段名引入--> <div th:insert="commons::fragment-name(var1='insert-name',var2='insert-name2')"></div> <!--th:insert id 选择器引入--> <div th:insert="commons::#fragment-id(var1='insert-id',var2='insert-id2')"></div> ------------------------------------------------ <!--th:replace 片段名引入--> <div th:replace="commons::fragment-name(var1='replace-name',var2='replace-name2')"></div> <!--th:replace id 选择器引入--> <div th:replace="commons::#fragment-id(var1='replace-id',var2='replace-id2')"></div> ------------------------------------------------ <!--th:include 片段名引入--> <div th:include="commons::fragment-name(var1='include-name',var2='include-name2')"></div> <!--th:include id 选择器引入--> <div th:include="commons::#fragment-id(var1='include-id',var2='include-id2')"></div>
在commons.html公共页面我们可以使用这些参数:
<!--使用 var1 和 var2 声明传入的参数,并在该片段中直接使用这些参数 --> <div th:fragment="fragment-name(var1,var2)" id="fragment-id"> <p th:text="'参数1:'+${var1} + '-------------------参数2:' + ${var2}">...</p> </div>
启动 Spring Boot,使用浏览器访问
Spring Boot整合Thymeleaf
Spring Boot 推荐使用 Thymeleaf 作为其模板引擎。SpringBoot 为 Thymeleaf 提供了一系列默认配置,项目中一但导入了 Thymeleaf 的依赖,相对应的自动配置 ThymeleafAutoConfiguration
就会自动生效,因此 Thymeleaf 可以与 Spring Boot 完美整合
1 引入依赖
Spring Boot 整合 Thymeleaf 的第一步,就是在项目的 pom.xml 中添加 Thymeleaf 的 Starter 依赖,代码如下
<!--Thymeleaf 启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
2 创建模板html文件
Spring Boot 通过 ThymeleafAutoConfiguration 自动配置类对 Thymeleaf 提供了一整套的自动化配置方案,该自动配置类的部分源码如下
@Configuration( proxyBeanMethods = false ) @EnableConfigurationProperties({ThymeleafProperties.class}) @ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class}) @AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class}) public class ThymeleafAutoConfiguration { public ThymeleafAutoConfiguration() { }
ThymeleafAutoConfiguration 使用 @EnableConfigurationProperties 注解导入了 ThymeleafProperties 类,该类包含了与 Thymeleaf 相关的自动配置属性,其部分源码如下
@ConfigurationProperties( prefix = "spring.thymeleaf" ) public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; //默认编码格式 public static final String DEFAULT_PREFIX = "classpath:/templates/"; //视图解析器的前缀 public static final String DEFAULT_SUFFIX = ".html"; //视图解析器的后缀 private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML"; private Charset encoding; private boolean cache; private Integer templateResolverOrder; private String[] viewNames; private String[] excludedViewNames; private boolean enableSpringElCompiler; private boolean renderHiddenMarkersBeforeCheckboxes; private boolean enabled; private final ThymeleafProperties.Servlet servlet; private final ThymeleafProperties.Reactive reactive; ····· }
ThymeleafProperties 通过 @ConfigurationProperties 注解将配置文件(application.properties/yml) 中前缀为 spring.thymeleaf 的配置和这个类中的属性绑定,通过配置默认值可知:Thymeleaf 模板的默认位置在 resources/templates
目录下,默认的后缀是 html,即只要将 HTML 页面放在classpath:/templates/
下,Thymeleaf 就能自动进行渲染。我们创建一个html文件:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>tml第一个thymeleaf页面</title> </head> <body> <h1>测试页面</h1> <div th:text="${msg}"></div> <!--遍历数据--> <!--th:each每次遍历都会生成当前这个标签:--> <h4 th:each="user :${users}" th:text="${user}"></h4> <!--th:text 为 Thymeleaf 属性,用于在展示文本--> <h1 th:text="能看到我说明你是在web模式下启动">直接打开浏览器你是看不到我的动态数据的</h1> </body> </html>
3 创建Controller
然后我们请求Controller页面,用来路由结果到hello.html页面
package com.example.springboot.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import java.util.Arrays; import java.util.Map; /** ** @Name HelloController ** @Description ** @author tianmaolin ** @Data 2021/10/14 */ @Controller public class HelloController { @GetMapping("/hello-thymeleaf") public String test2(Map<String,Object> map){ //存入数据 map.put("msg","Hello"); map.put("users", Arrays.asList("gcy","tml")); //classpath:/templates/hello.html return "hello"; } }
4 测试实现
我们在页面请求测试下:http://localhost:8080/hello-thymeleaf
SpringBoot页面国际化
国际化(Internationalization 简称 I18n,其中“I”和“n”分别为首末字符,18 则为中间的字符数)是指软件开发时应该具备支持多种语言和地区的功能。换句话说就是,开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据,例如,为中国用户提供汉语界面显示,为美国用户提供提供英语界面显示
1 编写国际化资源文件
在 Spring Boot 的类路径下创建国际化资源文件,文件名格式为:基本名_语言代码_国家或地区代码,例如 login_en_US.properties、login_zh_CN.properties。
以 spring-boot-springmvc-demo1为例,在 src/main/resources 下创建一个 i18n 的目录,并在该目录中按照国际化资源文件命名格式分别创建以下三个文件,
- login.properties:无语言设置时生效
- login_en_US.properties :英语时生效
- login_zh_CN.properties:中文时生效
以上国际化资源文件创建完成后,IDEA 会自动识别它们,编写并配置国际化资源文件如下:
login.properties
loginBtn=登录 password=密码 registerBtn=注册 username=用户名
login_en_US.properties
loginBtn=Login password=PassWord registerBtn=Register username=UserName
login_zh_CN.properties
loginBtn=登录 password=密码 registerBtn=注册 username=用户名
2 application.yml配置管理国际化资源文件
Spring Boot 已经对 ResourceBundleMessageSource 提供了默认的自动配置。Spring Boot 通过 MessageSourceAutoConfiguration 对 ResourceBundleMessageSource 提供了默认配置,其部分源码如下
@Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Conditional(org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition.class) @EnableConfigurationProperties public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = {}; // 将 MessageSourceProperties 以组件的形式添加到容器中 // MessageSourceProperties 下的每个属性都与以 spring.messages 开头的属性对应 @Bean @ConfigurationProperties(prefix = "spring.messages") public MessageSourceProperties messageSourceProperties() { return new MessageSourceProperties(); } //Spring Boot 会从容器中获取 MessageSourceProperties // 读取国际化资源文件的 basename(基本名)、encoding(编码)等信息 // 并封装到 ResourceBundleMessageSource 中 @Bean public MessageSource messageSource(MessageSourceProperties properties) { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); //读取国际化资源文件的 basename (基本名),并封装到 ResourceBundleMessageSource 中 if (StringUtils.hasText(properties.getBasename())) { messageSource.setBasenames(StringUtils .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename()))); } //读取国际化资源文件的 encoding (编码),并封装到 ResourceBundleMessageSource 中 if (properties.getEncoding() != null) { messageSource.setDefaultEncoding(properties.getEncoding().name()); } messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); Duration cacheDuration = properties.getCacheDuration(); if (cacheDuration != null) { messageSource.setCacheMillis(cacheDuration.toMillis()); } messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); return messageSource; } ... }
从以上源码可知:
- Spring Boot 将 MessageSourceProperties 以组件的形式添加到容器中;
- MessageSourceProperties 的属性与配置文件中以“spring.messages”开头的配置进行了绑定;
- Spring Boot 从容器中获取 MessageSourceProperties 组件,并从中读取国际化资源文件的 basename(文件基本名)、encoding(编码)等信息,将它们封装到ResourceBundleMessageSource 中;
- Spring Boot 将 ResourceBundleMessageSource 以组件的形式添加到容器中,进而实现对国际化资源文件的管理。
查看 MessageSourceProperties 类,其代码如下
public class MessageSourceProperties { private String basename = "messages"; private Charset encoding; @DurationUnit(ChronoUnit.SECONDS) private Duration cacheDuration; private boolean fallbackToSystemLocale; private boolean alwaysUseMessageFormat; private boolean useCodeAsDefaultMessage; public MessageSourceProperties() { this.encoding = StandardCharsets.UTF_8; this.fallbackToSystemLocale = true; this.alwaysUseMessageFormat = false; this.useCodeAsDefaultMessage = false; } ... }
通过以上代码,我们可以得到以下 3 点信息:
- MessageSourceProperties 为 basename、encoding 等属性提供了默认值;
- basename 表示国际化资源文件的基本名,其默认取值为“messages”,即 Spring Boot 默认会获取类路径下的 message.properties 以及 message_XXX.properties 作为国际化资源文件;
- 在 application.porperties/yml 等配置文件中,使用配置参数“spring.messages.basename”即可重新指定国际化资源文件的基本名。
通过以上源码分析可知,Spring Boot 已经对国际化资源文件的管理提供了默认自动配置,我们这里只需要在 Spring Boot 全局配置文件中,使用配置参数“spring.messages.basename”指定我们自定义的国际资源文件的基本名即可,代码如下(当指定多个资源文件时,用逗号分隔)
application.yml
spring: #页面国际化配置 messages: basename: i18n.login encoding: UTF-8 cache-duration: 1
3 定义区域信息解析器
我们知道,Spring MVC 进行国际化时有 2 个十分重要的对象:
- Locale:区域信息对象
- LocaleResolver:区域信息解析器,容器中的组件,负责获取区域信息对象
我们可以通过以上两个对象对区域信息的切换,以达到切换语言的目的,Spring Boot 在 WebMvcAutoConfiguration 中为区域信息解析器(LocaleResolver)进行了自动配置,源码如下
@Bean @ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME) @SuppressWarnings("deprecation") public LocaleResolver localeResolver() { if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.webProperties.getLocale()); } if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale() : this.mvcProperties.getLocale(); localeResolver.setDefaultLocale(locale); return localeResolver; }
从以上源码可知:
- 该方法默认向容器中添加了一个区域信息解析器(LocaleResolver)组件,它会根据请求头中携带的“Accept-Language”参数,获取相应区域信息(Locale)对象。
- 该方法上使用了 @ConditionalOnMissingBean 注解,其参数 name 的取值为 localeResolver(与该方法注入到容器中的组件名称一致),该注解的含义为:当容器中不存在名称为 localResolver 组件时,该方法才会生效。
当我们手动向容器中添加一个名为“localeResolver”的组件时,Spring Boot 自动配置的区域信息解析器会失效,而我们定义的区域信息解析器则会生效。创建一个 component 包,并在该包中创建一个区域信息解析器 MyLocalResolver:
MyLocalResolver.java
package com.example.springboot.component; import org.springframework.util.StringUtils; import org.springframework.web.servlet.LocaleResolver; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Locale; /** * * @Name MyLocalResolver * * @Description * * @author tianmaolin * * @Data 2021/10/14 */ public class MyLocalResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { //获取请求中参数 String l = request.getParameter("l"); //获取默认的区域信息解析器 Locale locale = Locale.getDefault(); //根据请求中的参数重新构造区域信息对象 if (StringUtils.hasText(l)) { System.out.println(l); String[] s = l.split("_"); locale = new Locale(s[0], s[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } }
4 注册区域信息解析器
我们需要定制化配置SpringMVC,让我们的视图解析器生效:
MyMvcConfig
package com.example.springboot.config; import com.example.springboot.component.MyLocalResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.*; @Configuration public class MyMvcConfig implements WebMvcConfigurer { //添加视图控制器 @Override public void addViewControllers(ViewControllerRegistry registry) { //当访问/或者/index.html都会跳转到首页 registry.addViewController("/").setViewName("login"); registry.addViewController("/login.html").setViewName("login"); } //将自定义的区域信息解析器以组件的形式添加到容器中 @Bean public LocaleResolver localeResolver(){ return new MyLocalResolver(); } }
5 获取国际化内容
通过Controller路由到html,所以我们需要写一个Controller【这里Controller不写也可以,因为MyMvcConfig中配置了registry.addViewController("/login.html").setViewName("login")
】
LoginTestController
package com.example.springboot.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; /** * * @Name LoginController * * @Description * * @author tianmaolin * * @Data 2021/10/14 */ @Controller public class LoginTestController { @GetMapping("/login-i18") public String loginTest(){ return "login"; } }
通过如下语法获取国际化内容:
login.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> </head> <body> <div class="login-container" > <form id="loginForm"> <div > <input type="text" th:placeholder="#{username}" /> </div> </br> <div> <input type="password" th:placeholder="#{password}" /> </div> </br> <button id="btn" th:text="#{loginBtn}"></button> <button type="btn" th:text="#{registerBtn}"></button><br> <!--thymeleaf 模板引擎的参数用()代替 ?--> <a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>| <a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a> </form> </div> </body> </html>
实现效果如下
当我们点击English时:
切换回中文时:
总结一下
本篇Blog详细了解了Thymeleaf的一些语法以及SpringBoot是如何定制整合Thymeleaf模板引擎,本质上Thymeleaf和JSP没有什么区别,只不过在SpringBoot时代,Thymeleaf能更好的和SpringBoot使用,有定义好的场景启动器starter供开发者方便的使用,而且还有方便使用的国际化配置。我们只需要简单的配置即可使用这个模板,当然和之前聊JSP时一样,在前后端分离时代其实我们更多的是通过RestController和前端交互,很少有后端自己定义前端页面,不过了解一下也好,对于小型项目,了解了Thymeleaf使用之后自己就可以独立搞定一个能完整运行的前后端都有的项目了,多学一点总归没有坏处。