Spring Boot入门教程(十九): Thymeleaf

时间:2022-06-23 05:49:46

一:Thymeleaf

Spring官方也推荐使用Thymeleaf,Thymeleaf最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个Web应用。

JSP技术Spring Boot官方是不推荐的,原因有三:

  • tomcat只支持war的打包方式,不支持可执行的jar。
  • Jetty 嵌套的容器不支持jsp
  • Undertow
  • 创建自定义error.jsp页面不会覆盖错误处理的默认视图,而应该使用自定义错误页面

1. pom.xml

<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>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

spring-boot-starter-thymeleaf会自动包含spring-boot-starter-web,所以我们就不需要单独引入web依赖了

2. application.properties

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML5
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=true
spring.thymeleaf.enabled=true

默认的配置如下:

# THYMELEAF (ThymeleafAutoConfiguration)
#开启模板缓存(默认值:true)
spring.thymeleaf.cache=true 
#Check that the template exists before rendering it.
spring.thymeleaf.check-template=true 
#检查模板位置是否正确(默认值:true)
spring.thymeleaf.check-template-location=true
#Content-Type的值(默认值:text/html)
spring.thymeleaf.content-type=text/html
#开启MVC Thymeleaf视图解析(默认值:true)
spring.thymeleaf.enabled=true
#模板编码
spring.thymeleaf.encoding=UTF-8
#要被排除在解析之外的视图名称列表,用逗号分隔
spring.thymeleaf.excluded-view-names=
#要运用于模板之上的模板模式。另见StandardTemplate-ModeHandlers(默认值:HTML5)
spring.thymeleaf.mode=HTML5
#在构建URL时添加到视图名称前的前缀(默认值:classpath:/templates/)
spring.thymeleaf.prefix=classpath:/templates/
#在构建URL时添加到视图名称后的后缀(默认值:.html)
spring.thymeleaf.suffix=.html
#Thymeleaf模板解析器在解析器链中的顺序。默认情况下,它排第一位。顺序从1开始,只有在定义了额外的TemplateResolver Bean时才需要设置这个属性。
spring.thymeleaf.template-resolver-order=
#可解析的视图名称列表,用逗号分隔
spring.thymeleaf.view-names=

3. controller

@Controller
public class SampleController {
    @GetMapping("/index")
    public ModelAndView index(Model model){
        ModelAndView modelAndView = new ModelAndView("index");

        List<Fans> fans = new ArrayList<>();
        fans.add(new Fans(1L, "zhangsan"));
        fans.add(new Fans(2L, "lis"));
        fans.add(new Fans(3L, "wangwu"));

        modelAndView.addObject("user", new User(1L, "mengday", "xxx@gmail.com", fans, true, new Date()));

        return modelAndView;
    }


    @Data
    @AllArgsConstructor
    public class User {
        private Long id;
        private String username;
        private String email;
        private List<Fans> fans;
        private Boolean isAdmin;
        private Date birthday;
    }

    @Data
    @AllArgsConstructor
    public class Fans {
        private Long id;
        private String name;
    }
}

4. index.html

注意:在html标签中使用xmlns:th=”http://www.thymeleaf.org”

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<link rel="stylesheet" type="text/css" href="css/style.css"/>
<body>

<div th:object="${user}">
    <span th:text="*{id}"></span><br/>
    <span th:text="'nickname:' + *{username}"></span><br/>
    <span th:text="|email: ${user.email}|"></span><br/>
    <span th:if="${user.isAdmin}">超级管理员</span><br/>
    <span th:unless="${user.isAdmin == true}">普通用户</span><br/>
    <span> [[${#dates.format(user.birthday,'yyyy-MM-dd HH:mm:ss')}]] </span><br/>
    <table border="1px" >
        <tr> <td>index</td> <td>id</td> <td>name</td> <td>length</td></tr>
        <tr th:each="item, stat : ${user.fans}" th:style="'background-color:' + ${stat.even == true ? 'bisque' : 'gray'}">
            <td>[[${stat.index}]]</td>
            <td>[[${item.id}]]</td>
            <td>
                <a th:href="@{/fans/{id}(id=${item.id})}">[[${item.name}]]</a>
            </td>
            <td>[[${#strings.length(user.username)}]]</td>
        </tr>
    </table>
</div>
<br/>
</body>
</html>

src/resources/static/css/style.css
一般静态资源(js、css、image等)都放在static目录下

body { color: red; }

Spring Boot入门教程(十九): Thymeleaf


二:静态资源的存放位置

Spring Boot 提供了对静态资源的支持,在创建Spring Boot项目时默认只创建了一个存放静态资源的目录src/main/resources/static,一共有四个,其它三个如果需要可以手动创建出目录,而且这四个是有优先级,当同一个资源出现在静态目录的多个地方时是按照优先级获取的,先获取优先级高的。

  1. classpath:/META-INF/resources
  2. classpath:/resources
  3. classpath:/static
  4. classpath:/public

Spring Boot入门教程(十九): Thymeleaf

1. application.properties配置

对应的源码:spring-configuration-metadata.json

{
  "sourceType": "org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties",
  "defaultValue": "\/**",
  "name": "spring.mvc.static-path-pattern",
  "description": "Path pattern used for static resources.",
  "type": "java.lang.String"
},

{
  "sourceType": "org.springframework.boot.autoconfigure.web.ResourceProperties",
  "defaultValue": [
    "classpath:\/META-INF\/resources\/",
    "classpath:\/resources\/",
    "classpath:\/static\/",
    "classpath:\/public\/"
  ],
  "name": "spring.resources.static-locations",
  "description": "Locations of static resources. Defaults to classpath:[\/META-INF\/resources\/,\n \/resources\/, \/static\/, \/public\/].",
  "type": "java.lang.String[]"
},

对应application.properties的默认配置

spring.mvc.static-path-pattern=/**
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

在应用程序启动的时候会有日志打印,可以看到

  • /** 映射到 /static (或/public、/resources、/META-INF/resources)
  • /webjars/** 映射到 classpath:/META-INF/resources/webjars/

Spring Boot入门教程(十九): Thymeleaf

假如 在src/main/resources/static目录下放一个index.html, 通过http://localhost:8080/index.html就能直接访问,路径中不用在包含static子路径了。

如果访问静态资源想在路径上有体现,也可以自定义,如将static-path-pattern配置成spring.mvc.static-path-pattern=/static/**就需要用http://localhost:8080/static/index.html来访问
同样如果对于系统定义的静态资源的存放位置不满意也可以通过spring.resources.static-locations来定义,一般静态资源都放在src/main/resource下面。

2. JavaConfig - WebMvcConfigurationSupport

addResourceHandlers用于配置spring.mvc.static-path-pattern。

传统Spring MVC开发经常将jsp页面放置在webapp/WEB-INF下,这样要想访问页面就需要写一个controller对应的方法,即使方法中没什么业务逻辑,只仅仅用于页面跳转也不得不写,感觉有点low,在Spring Boot中通过配置addViewControllers,配置一个路径和一个视图名称的映射即可,也不用再controller中写页面跳转的RequestMapping方法了。啰嗦一句:看到addViewControllers这个方法感觉突然好熟悉,原来在iOS中有个addChildViewController方法,而且在iOS中有UIViewController这种类,类似于Java中的Controller的概念

@Configuration
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurationSupport {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/html/");
        // addResourceLocations也可以指定一个系统中的一个目录
        // registry.addResourceHandler("/static/**").addResourceLocations("file:/Users/xxx/Documents/demo/static");
        super.addResourceHandlers(registry);
    }

    // http://localhost:8080/home
    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        super.addViewControllers(registry);
    }
}

webjars

webjars 就是将web开发中常用的静态资源放到classpath:/META-INF/resources/webjars/ 中,然后打包成.jar发布到maven仓库中,使用时通过maven来引入,假如项目中用到bootstrap,常规的做法就是手动去官网下载,然后手动将bootstrap放到静态目录。一般常用的像jquery、bootstrap、angularjs、react、vue、swagger-ui等都有相应的maven依赖,目前有92个这种依赖。

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>bootstrap</artifactId>
    <version>4.0.0</version>
</dependency>

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.3.1</version>
</dependency>

Spring Boot入门教程(十九): Thymeleaf

在页面上通过/webjars/jquery/3.3.1/jquery.js 来引入,Spring Boot 默认将 /webjars/** 映射到 classpath:/META-INF/resources/webjars/,可以通过http://localhost:8080/webjars/jquery/3.3.1/jquery.js来验证一下看是否能获取到。

<script src="${pageContext.request.contextPath}/webjars/jquery/3.3.1/jquery.js"></script>

webjars-locator

在上面访问静态资源时路径中包含了版本号(http://localhost:8080/webjars/jquery/3.3.1/jquery.js),假如版本升级需要全局替换所有引入的文件,当然也不太费事一个全局替换就搞定了,当然也可以防止于未然,webjars提供了一个webjars-locator的依赖,目的是让前端页面引入的静态资源不包含路径,当请求一个静态资源时会自动帮你映射到对的版本对应的资源。

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator</artifactId>
    <version>0.33</version>
</dependency>
@Controller
public class WebJarsController {

    private final WebJarAssetLocator assetLocator = new WebJarAssetLocator();

    @ResponseBody
    @RequestMapping("/webjarslocator/{webjar}/**")
    public ResponseEntity<Object> locateWebjarAsset(@PathVariable String webjar, HttpServletRequest request) {
        try {
            // /webjarslocator/jquery/
            String mvcPrefix = "/webjarslocator/" + webjar + "/"; // This prefix must match the mapping path!
            // /webjarslocator/jquery/jquery.js
            String mvcPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
            // META-INF/resources/webjars/jquery/3.3.1/jquery.js
            String fullPath = assetLocator.getFullPath(webjar, mvcPath.substring(mvcPrefix.length()));
            return new ResponseEntity<>(new ClassPathResource(fullPath), HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }
}
<script src="${pageContext.request.contextPath}/webjarslocator/jquery/jquery.js"></script>

假如前端访问http://localhost:8080/webjarslocator/jquery/jquery.js(此地址中不包含版本号),地址映射到WebJarsController,返回META-INF/resources/webjars/jquery/3.3.1/jquery.js对应的资源。这样就达到前端引入的静态资源中不包含具体版本号,当需要升级的时候直接修改maven对应的依赖版本即可,前端也不用全局替换版本号操作了。感觉这个功能不实用,版本升级很久才会一次,而且全局替换也很简单,也是秒秒钟搞定的事情。

静态资源浏览器缓存

开发时,业务对应的js经常会变动,一般浏览器会缓存静态资源,一般防止浏览器缓存静态资源的做法是在地址后面使用版本号参数,或者在地址后面使用时间戳,Spring Boot 给出了自己的解决方案。

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
@ControllerAdvice
public class ResourceUrlProviderController {

    @Autowired
    private ResourceUrlProvider resourceUrlProvider;

    @ModelAttribute("urls")
    public ResourceUrlProvider urls() {
        return this.resourceUrlProvider;
    }
}

common.js放在 src/main/resources/static/js/目录下

<script src="${pageContext.request.contextPath }${urls.getForLookupPath('/js/common.js') }"></script>

当我们访问页面后,HTML中实际生成的代码为common-<md5>.js, 每当common.js内容发生变化时MD5值就会重新生成,当js文件没有时md5值不变,当js文件内容发生改变时,md5值发生变化,js文件的内容也随之改变。这样就保证了js文件始终是最新的。

<script src="/js/common-c6b7da8fffc9be141b48c073e39c7340.js"></script>

三: Interceptor拦截器

拦截器的使用分两步:

  1. 声明拦截器,实现HandlerInterceptor
  2. 配置拦截器,在WebMvcConfigurerAdapter#addInterceptors配置
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println(request.getRequestURI() + "-------------");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("------------postHandle------------");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("------------afterCompletion------------");
    }
}
@Configuration
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");

        super.addResourceHandlers(registry);
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/");
        super.addInterceptors(registry);
    }
}

注意:访问http://localhost:8080/static/index.html确实对静态资源不拦截了,访问http://localhost:8080/index被拦截到了