朝小闇的博客

海上月是天上月,眼前人是心上人

SpringBoot(二)——Thymeleaf及MVC装配扩展

SpringBoot配置了什么?能不能对其进行修改?能修改什么?能不能扩展?通过自动装配原理的学习,接下来主要根据自动装配源码分析并得到以下结论。

1.导入静态资源

  • 这里的静态资源由果索因,即在网页中localhost:8080/后输入webjars路径相当于访问源文件路径,以此找到源文件中存放静态资源位置,注:静态资源包含html等文件;

  • 找到WebMvcAutoConfiguration文件以下部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 这一段是判断是否在配置文件中自主配置资源路径,如果配置则默认配置失效,直接在此返回
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}

Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();

// webjars是以jar包形式封装引入前端资源,此处可以从官网取出引入静态资源依赖的代码
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}

String staticPathPattern = this.mvcProperties.getStaticPathPattern();
/* 首先从mvcProperties对象的类WebMvcProperties文件中找到staticPathPattern变量
其次从resourceProperties对象的类ResourceProperties文件中找到staticLocations变量
*/
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
  • webjars引入 Maven jquery为例:

    image-20210305125609037
    • 将该段依赖代码放到pom.xml文件依赖中并加载相关依赖项,如图:

      image-20210305130316069

      这一段的含义是localhost:8080/webjars/路径相当于外部依赖库目录的classpath:/META-INF/resources/webjars/,因此在网页中输入localhost:8080/webjars/jquery/3.6.0/jquery.js即可获取到该静态资源,如图所示:

      image-20210305130913244
  • ResourceProperties文件静态资源默认路径:

    image-20210305131847108
    • 此处四个路径全部是根目录下resources文件下目录,而系统默认生成static文件夹用以存放静态资源。经过实践发现后三者优先级:/resources > /static > /public,而第一个路径其实就是外部依赖包路径,参考webjars
    • WebMvcProperties文件下staticPathPattern变量的默认地址就是根路径,即根路径下的资源也能直接访问到;
  • 关于在application.properties文件中修改资源路径,一般不使用,修改的话直接修改staticPathPattern变量即可,这样一来默认存放静态资源的路径就会全部失效;

2.定制首页

WebMvcAutoConfiguration文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 此方法用以获取location路径,并调用getIndexHtml方法在默认路径中寻找index.html文件
ResourceProperties文件中staticLocations变量即为静态资源路径,因此首页index.html文件可放在静态资源路径下
不过一般不使用此方法,下述模板引擎会讲到另一种方式
*/
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}

private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}

private boolean isReadable(Resource resource) {
try {
return resource.exists() && (resource.getURL() != null);
}
catch (Exception ex) {
return false;
}
}

3.模板引擎Thymeleaf

使用模板自动渲染数据生成静态网页

image-20210305211634119
  • Thymeleaf导入依赖包:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- Thymeleaf -->
    <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    </dependency>
    <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
    </dependency>

    在父配置文件中进入依赖文件可查看Thymeleaf的默认导入版本。

  • 首先查看源码,在ThymeleafProperties文件(每一个引入依赖都拥有自己的自动装配文件)中看到代码:

    1
    2
    3
    // 两个变量分别定义了默认前缀和默认后缀,即前缀路径在根目录下resources/templates/目录下,即通过thymeleaf转换的html页面全部存放于该目录下
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
  • templates目录下新建test.html文件,并在controller目录下新建IndexController.java文件用于测试Thymeleaf:

    • IndexController.java文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.kun.demoweb.controller;

    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;

    // thymeleaf转换的所有templates目录下页面全部需要通过Controller跳转
    @Controller
    public class IndexController {

    // 跳转路由
    @RequestMapping("/test")
    public String test(Model model){
    // 添加属性值传递到页面中
    model.addAttribute("msg","hello,springboot");
    // 从此处索引页面文件名
    return "test";
    }
    }
    • test.html文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!DOCTYPE html>
    <!-- html引入thymeleaf -->
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- thymeleaf语法,引用传递过来的属性,类似于Vue语法 -->
    <!-- 在html标签属性前添加th: 所有属性都可以被thymeleaf接管 -->
    <div th:text="${msg}"></div>
    </head>
    <body>

    </body>
    </html>
  • Thymeleaf常见属性,此处不细讲,可去官网下载文档具体学习:

    image-20210305220947183
  • Thymeleaf常见语法:

    image-20210305221142248

4.装配扩展SpringMVC

  • 查看官网image-20210305230035024

  • 创建config包,新建MyMvcConfig.java文件用于扩展SpringMVC,并根据官网说明添加注解及接口,且不能使用注解@EnableWebMvc防止全部接管,由此可以重写一些功能;

  • image-20210305230640368

  • 如图,示例重写自己的视图解析器:

    • 先找到该文件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      /*	1.resolveViewName方法获取所有的候选视图,并从中选择最好的视图返回,此时返回的视图解析器是Thymeleaf的
      */
      @Override
      @Nullable
      public View resolveViewName(String viewName, Locale locale) throws Exception {
      RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
      Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
      List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
      if (requestedMediaTypes != null) {
      List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
      View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
      if (bestView != null) {
      return bestView;
      }
      }

      String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
      " given " + requestedMediaTypes.toString() : "";

      if (this.useNotAcceptableStatusCode) {
      if (logger.isDebugEnabled()) {
      logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
      }
      return NOT_ACCEPTABLE_VIEW;
      }
      else {
      logger.debug("View remains unresolved" + mediaTypeInfo);
      return null;
      }
      }
      /* 2.getCandidateViews方法用以从Bean工具类中获取所有的候选视图
      */
      private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
      throws Exception {

      List<View> candidateViews = new ArrayList<>();
      if (this.viewResolvers != null) {
      Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
      for (ViewResolver viewResolver : this.viewResolvers) {
      View view = viewResolver.resolveViewName(viewName, locale);
      if (view != null) {
      candidateViews.add(view);
      }
      for (MediaType requestedMediaType : requestedMediaTypes) {
      List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
      for (String extension : extensions) {
      String viewNameWithExtension = viewName + '.' + extension;
      view = viewResolver.resolveViewName(viewNameWithExtension, locale);
      if (view != null) {
      candidateViews.add(view);
      }
      }
      }
      }
      }
      if (!CollectionUtils.isEmpty(this.defaultViews)) {
      candidateViews.addAll(this.defaultViews);
      }
      return candidateViews;
      }
    • 将自己重写的视图解析器放入Bean工具类中即可:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      package com.kun.demoweb.config;

      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.servlet.View;
      import org.springframework.web.servlet.ViewResolver;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

      import java.util.Locale;

      @Configuration
      public class MyMvcConfig implements WebMvcConfigurer {

      // 将自定义的视图解析器添加进Bean中
      @Bean
      public ViewResolver myViewResolver(){
      return new MyViewResolver();
      }

      // 自定义自己的视图解析器,并重写resolveViewName方法
      public static class MyViewResolver implements ViewResolver {
      @Override
      public View resolveViewName(String viewName, Locale locale) throws Exception {
      return null;
      }
      }
      }
  • Ctrl+shift+N搜索文件快捷方式;
-------- 本文结束 感谢阅读 --------