SpringBoot进阶学习下篇
5、SprinBoot整合Thymeleaf模版引擎
5.1 SpringBoot对模板的支持
SpringBoot框架支持很多种模板,(FreeMarker、Thymeleaf、Mustache等),大大方便了前端的开发。
SpringBoot对Jsp不太支持,并且官方没有提供整合的配置,原因:
1、SpringBoot使用的是嵌入式Servlet web容器,默认使用jar的打包方式,jar包本身不支持jsp模板。
2、如果使用Undertow嵌入式容器部署SpringBoot项目的话,不支持Jsp模板。
3、SpringBoot默认提供了一个处理请求路径"/error"的统一错误处理器,来返回具体的异常信息,如果使用jsp模板的话无法对默认的错误信息进行覆盖,只能在指定的位置指定错误页面。
5.2 Thymeleaf模板引擎技术
5.2.1 Thymeleaf介绍
Thymeleaf是一种基于服务器端的java模板引擎技术,也是一个优秀的面向Java的XML、XHTML、HTML5页面模板,它具有丰富的标签语言、函数和表达式,在使用Spring Boot框架进行页面设计时,一般会选择Thymeleaf模板。
5.2.2Thymeleaf语法
在HTML页面上使用Thymeleaf标签,Thymeleaf 标签能够动态地替换掉静态内容,使页面动态展示。
常用的标签
th:标签 说明
th:insert 布局标签,替换内容到引入的文件
th:replace 页面片段包含(类似JSP中的include标签)
th:each 元素遍历(类似JSP中的c:forEach标签)
th:if 条件判断,如果为真
th:unless 条件判断,如果为假
th:switch 条件判断,进行选择性匹配
th:case 条件判断,进行选择性匹配
th:value 属性值修改,指定标签属性值
th:href 用于设定链接地址
th:src 用于设定链接地址
th:text 用于指定标签显示的文本内容
标准表达式
1、变量表达式 ${...}
来获取到上下文中变量的值。
Demo:<p th:text="${title}">这是标题</p>
Thymeleaf内置的上下文对象有:
ctx 上下文对象
vars 上下文变量
locale 上下文区域设置
request (仅限Web Context)HttpServletRequest对象
response (仅限Web Context)HttpServletResponse对象
session (仅限Web Context)HttpSession对象
servletContext (仅限Web Context)ServletContext对象
2、选择变量表达式 *{...}
也是获取到上下问的变量只不过是从业务对象中获取
Demo:
<div th:object="${book}"> <p>titile: <span th:text="*{title}">标题</span>.</p></div>
3、消息表达式 #{...}
消息表达式#{...}主要用于Thymeleaf模板页面国际化内容的动态替换和展示,使用消息表达式#
{...}进行国际化设置时,还需要提供一些国际化配置文件。后边会说明
4、链接表达式 @{...}
链接表达式@{...}一般用于页面跳转或者资源的引入,在Web开发中占据着非常重要的地位,并且使用
也非常频繁,Demo:
<a th:href="@{http://localhost:8080/order/details(orderId={o.id})}">view</a>
5、片段表达式 ~{...}
片段表达式~{...}用来标记一个片段模板,并根据需要移动或传递给其他模板。其中,最常见的用法是使用th:insert或th:replace属性插入片段。
<div th:insert="~{thymeleafDemo::title}"></div>
thymeleaf Demo
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
<title>Title</title>
</head>
<body>
<p th:text="${hello}">HelloWorld</p>
</body>
</html>
5.2.3Thymeleaf使用Demo
POM
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
配置
模板缓存
spring.thymeleaf.cache = true
模板编码
spring.thymeleaf.encoding=utf-8
应用于模板的模板模式
spring.thymeleaf.mode = HTML5
指定模板页面存放路径
spring.thymeleaf.prefix = classpath:/templates/
指定模板页面名称的后缀
spring.thymeleaf.suffix = .html
配置国际化文件基础名
spring.messages.basename=i18n.login
代码
@Controller
public class LoginController {
@RequestMapping("/toLoginPage")
public String toLoginPage(Model model){
model.addAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
return "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">
<meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no">
<title>用户登录界面</title>
<link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/login/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin">
<img class="mb-4" th:src="@{/login/img/login.jpg}" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">请登录</h1>
<input type="text" class="form-control"
th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control"
th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.button}">登录</button>
<p class="mt-5 mb-3 text-muted">© <span th:text="{currentYear}+1">2020</span></p>
<a class="btn btn-sm" th:href="@{/toLoginPage(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/toLoginPage(l='en_US')}">English</a>
</form>
</body>
</html>
5.2.4Thymeleaf设置本地化语言
首先需要在resources目录下创建一个文件夹来存放默认的和翻译好的语言
login.properties,login_zh_CN.properties 【默认和中文的都一样】
login.tip=请登录
login.username=用户名
login.password=密码
login.button=登录
login.rememberme=记住我
login_en_US.properties【英文】
login.tip=please sign in
login.username=username
login.password=password
login.button=login
login.rememberme=remmeber me
配置
配置国际化文件基础名
spring.messages.basename=language.login
Html中修改
<form class="form-signin">
<img class="mb-4" th:src="@{/login/img/login.jpg}" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">请登录</h1>
<input type="text" class="form-control"
th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control"
th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.button}">登录</button>
<p class="mt-5 mb-3 text-muted">© <span th:text="{currentYear}+1">2020</span></p>
<a class="btn btn-sm" th:href="@{/toLoginPage(setLanguage='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/toLoginPage(setLanguage='en_US')}">English</a>
</form>
</body>
</html>
设置本地化解析器
public class LanguageLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String setLanguage = request.getParameter("setLanguage");
Locale locale;
if (!StringUtils.isEmpty(setLanguage)) {
String[] s = setLanguage.split("_");
locale = new Locale(s[0],s[1]);
}else{//为空的情况
String header = request.getHeader("Accept-Language");
String[] split = header.split(",");
String[] split1 = split[0].split("-");
locale = new Locale(split1[0],split1[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
配置到Spring容器中进行覆盖 一定要是localeResolver才行
@Bean
public LocaleResolver localeResolver(){
return new LanguageLocaleResolver();
}
总结:在进行本地化配置的时候一定要注意配置文件的命名 文件名语言国家
5.3SpringBoot缓存
SpringBoot支持很多缓存组件:
Generic
JCache (JSR-107) (EhCache 3、Hazelcast、Infinispan等)
EhCache 2.x
Hazelcast
Infinispan
Couchbase
Redis
Caffeine
Simple
添加@EnableCaching之后默认会从这个9个缓存组件中去查找,没有的话默认使用Simple缓存组件。
5.3.1默认缓存使用
使用方法:
在启动类上贴上@EnableCaching,在对应的service查询方法上贴上@Cacheable(cacheNames = "name")来开启缓存。
@SpringBootApplication
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
//根据id查询person对象爱ing
@Cacheable(cacheNames = "person")
public Person findById(Integer id){
Optional<Person> op = personRepository.findById(id);
return op.isPresent() ? op.get() : null;
}
@Cacheable 注解
属性名 说明
value/cacheNames 指定缓存空间的名称,必配属性。这两个属性二选一使用
key 指定缓存数据的key,默认使用方法参数值,可以使用SpEL表达式SpEL表达式:#root.mathodName ->获取当前方法名#root.mathod ->当前被调用的方法 #root.target ->当前被调用的目标对象#root.targetClass ->当前被调用的目标对象类#root.args[0] 第一个参数, #root.args[1] 第二个参数... ->当前被调用的方法的参数列表#参数名, 也可以使用 #p0 #a0 0是参数的下标索引 ->根据参数名字取出值#result ->当前方法的返回值
keyGenerator 指定缓存数据的key的生成器,与key属性二选一使用
cacheManager 指定缓存管理器
cacheResolver 指定缓存解析器,与cacheManager属性二选一使用
condition 指定在符合某条件下,进行数据缓存
unless 指定在符合某条件下,不进行数据缓存
sync 指定是否使用异步缓存。默认false
执行原理
Cacheable通过动态代理的方式进行缓存,方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取,(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建;
@Cacheable在不引入任何的依赖的情况下默认使用的是SimpleCacheConfiguration配置的ConcurrentMapCacheManager缓存组件,将缓存结果存储在cacheMap中。使用一个key,默认就是方法的参数,如果多个参数或者没有参数,是按照某种策略生成的,默认是使用KeyGenerator生成的。
使用SimpleKeyGenerator生成key策略:
参数个数 key
没有参数 new SimpleKey()
有一个参数 参数值
多个参数 new SimpleKey(params)
@CachePut注解
目标方法执行完之后生效, @CachePut被使用于修改操作比较多,哪怕缓存中已经存在目标值了,但是这个
注解保证这个方法依然会执行,执行之后的结果被保存在缓存中
@CacheEvict注解
@CacheEvict注解是由Spring框架提供的,可以作用于类或方法(通常用在数据删除方法上),该注解
的作用是删除缓存数据。@CacheEvict注解的默认执行顺序是,先进行方法调用,然后将缓存进行清
除。
5.3.2Redis缓存
1、注解方式使用redis缓存
在使用注解的方式使用redis缓存的时候只需要导入redis的包和配置redis,SpringBoot默认就会走redis缓存,使用方法和默认缓存的使用方法一致。
POM
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置
Redis配置
Redis服务地址
spring.redis.host=127.0.0.1
Redis服务器连接端口
spring.redis.port=6379
Redis服务器连接密码(默认为空)
spring.redis.password=
spring.redis.database=1
对基于注解的Redis缓存数据统一设置有效期为1分钟,单位毫秒
spring.cache.redis.time-to-live=60000
原理是在引入redis配置之后,系统直接就会使用RedisCacheConfiguration对缓存进行配置,使用RedisCacheManager来管理缓存,使用方式也是通过@Cacheable等注解来实现的。
修改默认的序列化方式为json方式
通过查看RedisCacheConfiguration 类的源码发现默认是jdk的序列化方式
修改默认的序列化方式
@Configuration
public class RedisAnnoConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jacksonSeial =
new Jackson2JsonRedisSerializer(Object.class);
// 解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 定制缓存数据序列化方式及时效
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(1))//缓存时间为1天
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(jacksonSeial))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory).cacheDefaults(config).build();
return cacheManager;
}
}
2、使用RedisTemplate进行缓存
直接注入RedisTemplate进行操作,但是有个问题,注入的redisTemplate在保存数据的时候还是jdk默认的序列化方式因此需要修改序列化方式。RedisAutoConfiguration类来分析。
修改RedisTemplate序列化方式,思路是直接覆盖自动注册的redisTemplate实例。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object,Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置序列化方式
Jackson2JsonRedisSerializer jackSonSerializer =
new Jackson2JsonRedisSerializer(Object.class);
redisTemplate.setDefaultSerializer(jackSonSerializer);
//解决查询缓存转换异常问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackSonSerializer.setObjectMapper(om);
// 设置RedisTemplate模板API的序列化方式为JSON
redisTemplate.setDefaultSerializer(jackSonSerializer);
return redisTemplate;
}
}