1. 程式人生 > >springboot web專案建立及自動配置分析(thymeleaf+flyway)

springboot web專案建立及自動配置分析(thymeleaf+flyway)

@

目錄
  • 原始碼分析
  • webjars
  • thymeleaf
  • thymeleaf語法
  • springmvc 啟動配置原理
  • 整合flyway外掛

springboot 建立web專案只需要引入對應的web-starter,自己定義好moudel層,再採用相應的模版引擎技術(view層)就可以將資料渲染到模版中,從而生成一個單體的web應用!那這些檢視是如何解析的呢?最常用的模版引擎語法有哪些呢?

新建一個空的專案,我們選擇對應的web依賴,工具相關我三個都勾選上,資料庫驅動選mysql驅動!具體見我的另一篇部落格:
springboot整合mybatis和druid監控,此處不再贅述.

建立好專案後,我們來分析下原始碼

原始碼分析

首先我們都知道,springboot初始化的專案下面都是沒有webapp這樣一個模組的,那我們的web相關的一些資源,該放在哪裡呢?為何對應的放置就可以生效呢?

我們嘗試從原始碼中尋求答案

SpringMVC 整個 SSM 都是基於它的,所以我們第一步應該去研究 SpringBoot 關於Mvc的自動配置!

  • 1、所有mvc相關的配置都在 WebMvcAutoConfiguration (檢視解析器、靜態資源過濾!)
  • 2、addResourceHandlers 靜態資源處理方法
@Override
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(); 
// 分析原始碼,需要掌握看物件的方法呼叫!
// localhost:8080/webjars/jquery.js
// 判斷是否存在一個對映路徑 /webjars/**,
// addResourceHandler 處理邏輯 /webjars/a.js
// addResourceLocations 處理資源的地址 classpath:/META-INF/resources/webjars/a.js
if (!registry.hasMappingForPattern("/webjars/**")) { 		 
    customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
    .addResourceLocations("classpath:/META-INF/resources/webjars/") 
    .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
// 獲取靜態資源路徑!
String staticPathPattern = this.mvcProperties.getStaticPathPattern(); // localhost:8080/
// 如果訪問對映的路徑是 staticPathPattern = "/**";
// this.resourceProperties.getStaticLocations())
if (!registry.hasMappingForPattern(staticPathPattern)) {
    customizeResourceHandlerRegistration(registry.addResourceHandler("/**") 
    .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
    .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); }
}
// 對應的資源載入先後順序 優先順序:META-INF > resources > static > public
// 對於怎麼驗證這個優先順序,可以建對於的檔案加,放些靜態資源,頁面直接訪問測試
private static final String[] CLASSPATH_RESOURCE_LOCATIONS =
{
     "classpath:/META-INF/resources/",
     "classpath:/resources/",
     "classpath:/static/",
     "classpath:/public/"
};

我們一句句的解讀,就可以讀懂原始碼!可以看到這段原始碼中就這個webjars我們不怎麼熟悉

webjars

什麼是 webjars?

webjars官網

webjars是一個前端依賴管理工具,集成了前端主流的一些框架,使得我們只需引入對應的jar包就可以在專案中使用它!

接下來,我們引入jquery的依賴:

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

看下生成的依賴:

我們看下是否可以直接通過路徑訪問:http://localhost:8080/webjars/jquery/3.4.1/jquery.js

很明顯,這樣是可以直接訪問的。那這些可以常用的框架等靜態資源我們可以這樣引入,我們自定義的東西例如css 圖片等該如何使用呢?
我常用的規則推薦如下:

private static final String[] CLASSPATH_RESOURCE_LOCATIONS =
{
"classpath:/META-INF/resources/", // 在 starter 中使用! SWAGGER-UI 
"classpath:/resources/", // 檔案資源
"classpath:/static/", // 靜態資源 
"classpath:/public/" // 公共的,圖示......
};

當然我們也可以更改spring的預設資源路徑配置:

# 一旦自己配置了  那麼預設的就會失效
spring.resources.static-locations=xxx

thymeleaf

  • 1.官網:https://www.thymeleaf.org/documentation.html
  • 2.github:https://github.com/thymeleaf/thymeleaf/blob/3.0-master

引入依賴,在spring中採用jar一般都是使用對應的starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

任何starter都有一個xxxProperties我們去其依賴下看看原始碼:

@ConfigurationProperties(prefix = "spring.thymeleaf") public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html"; 
    ...省略
}

可以看出thymeleaf的預設配置路徑是templates下,預設檔案格式是.html
我們要改只需要spring.thymeleaf.prefix=xxx,當然更改了預設的便不會生效了。

測試thymeleaf

templates新增一個頁面test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>hello,thymeleaf!</p>
</body>
</html>

controller中新增一個介面:

package com.blog.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
    @GetMapping(value = "/test")
    public String test(){
        return "test";
    }
}

啟動專案可見:

thymeleaf語法

瞭解了基本的頁面渲染規則後,我們來看下thymeleaf的語法:

我們還可以編寫哪些表示式呢?
Variable Expressions: ${...} 獲取一些基本的變數值! OGNL;

  1. 物件的屬性,呼叫方法
  2. 使用內建的基本物件
${#ctx.locale}
${param.foo}
${session.foo} 
${application.foo} 
${#request.getAttribute('foo')} 
${#servletContext.contextPath}
  1. 工具物件
${#messages.msg('msgKey')}
${#uris.escapePath(uri)} 
${#conversions.convert(object, 'java.util.TimeZone')} 
${#dates.format(date, 'dd/MMM/yyyy HH:mm')} 
${#calendars.format(cal)} 
${#numbers.formatInteger(num,3)} 
${#strings.toString(obj)}
${#arrays.toArray(object)}
.....

4.其他

Selection Variable Expressions: *{...} 選擇表示式,和 ${} 是一樣的;
 Message Expressions: #{...} 國際化內容獲取!
Link URL Expressions: @{...} URL表示式;th:href=“@{/login}” 
Fragment Expressions: ~{...} 元件化表示式;
Literals (字面量);
Text literals: 'one text' , 'Another one!' ,... (字串) 
Number literals: 0 , 34 , 3.0 , 12.3 ,...
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,...
Text operations: (文字操作)
String concatenation: +
Literal substitutions: |The name is ${name}| Arithmetic operations: (數學運算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
Boolean operations: (布林運算)
Binary operators: and , or
Boolean negation (unary operator): ! , not 
Comparisons and equality: (比較運算) 
Comparators: > , < , >= , <= ( gt , lt , ge , le ) 
Equality operators: == , != ( eq , ne ) 
Conditional operators: (條件運算子) 
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else) 
Default: (value) ?: (defaultvalue) 
Special tokens:
Page 17 of 104**No-Operation:** _

springmvc 啟動配置原理

我們來看官方文件,雖然都是英文但是不要慫,慢慢的翻的多了也就認識了!

地址:官網

找到對應的Spring MVC Auto-configuration

我們來解讀下:

Spring MVC Auto-configuration
// SpringBoot為SpringMVC 提供提供了自動配置,他可以很多好的工作於大多數的應用!
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自動配置在Spring預設配置的基礎上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults: // 包含檢視解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支援靜態資原始檔的路徑嗎,包含webjar的支援
Support for serving static resources, including support for WebJars (covered later in this document)).
// 自動註冊了轉換器
// 轉換器 網頁提交的前端物件,到後臺自動封裝為具體的物件;"1" 自動轉換為 數字 1; // 格式化器Formatter 【2020-03-18 後臺可以自動封裝為Date】
Automatic registration of Converter, GenericConverter, and Formatter beans. // 支援訊息轉換
// request、response,物件自動轉換為 json物件
Support for HttpMessageConverters (covered later in this document).
// 定錯程式碼生成規則
Automatic registration of MessageCodesResolver (covered later in this document). // 支援首頁定製
Static index.html support.
// 支援自定義圖示
Custom Favicon support (covered later in this document).
//配置web資料繫結
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

// 如果你希望保持 Spring Boot MVC 一些功能,並且希望新增一些其他的 MVC配置(攔截器、格式化 器、檢視控制器、或其他的配置),你可以新增自己的配置類 (型別為WebMvcConfigurer) 需要添加註 解@Configuration ,一定不能擁有註解@EnableWebMvc.
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

//如果要提供RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定義例項,並且仍然保留Spring Boot MVC自定義,則可以宣告WebMVCregistration型別的bean,並使用它來提供這些元件的自定義例項
If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

// 全面接管Spring MVC,自己配置配置類的時候加上 @EnableWebMvc即可!
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

我們先分析下為什麼加了@EnableWebMvc註解,檢視解析器就不生效了,也就是說springmvc這一套東西都不好使了!這個很神奇

原始碼:

// 如果這個bean不存在,這個類才生效!~ 
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
// @EnableWebMvc 原始碼 
@Import(DelegatingWebMvcConfiguration.class) 
public @interface EnableWebMvc
// 點進DelegatingWebMvcConfiguration繼承了WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport

//其實 @EnableWebMvc 就是匯入了一個類 WebMvcConfigurationSupport ,但是原始碼中,一旦匯入了 這個類,我們自動配置類就會全部失效!
//如果我們要擴充套件springmvc
//擴充套件mvc的方法:
//1、編寫一個自己的config配置類 
//2、實現一個介面WebMvcConfigurer 
//3、重寫裡面的方法即可!
//@Configuration
//public class MyMvcConfig implements WebMvcConfigurer {
//}

試圖解析器

ContentNegotiatingViewResolver

@Bean
@ConditionalOnBean(ViewResolver.class) // 自動配置了 ViewResolver,就是SpringMVC中的檢視解析器
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new
ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationMan ager.class));
    // ContentNegotiatingViewResolver uses all the other view resolvers to ocate
    // a view so it should have a high precedence
    // ContentNegotiatingViewResolver 使用其他所有的檢視解析器定位檢視,因此它應該具有一 個高的優先順序!
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

解析檢視名字

resolveViewName

@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;
    } 
}

既然他是從容器中載入所有的檢視解析器,那麼我們可以猜想,我們自己寫一個檢視解析器,也可以被 掃描並載入!

// 自己寫一個 bean
@Bean
public ViewResolver myViewResolver(){
    return new MyViewResolver();
}
private static class MyViewResolver implements ViewResolver{
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception{
        return null;
    }
}

整合flyway外掛

一直知道這麼個東西,很好用單獨寫一篇部落格又顯得很浪費;那就跟著這篇部落格一併說了吧

概念:

Flyway是獨立於資料庫的應用、管理並跟蹤資料庫變更的資料庫版本管理工具,說白了就是Flyway可以像Git管理不同人的程式碼那樣,管理不同人的sql指令碼,從而做到資料庫同步

食用方法

如果新建專案可以直接勾選上flyway外掛依賴,我們這裡沒勾選就自己手動新增:

  • 新增依賴
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
  • 配置
# 預設不開啟flyway
spring.flyway.enabled=false
spring.flyway.baseline-on-migrate=true
# flyway字元編碼
spring.flyway.encoding=UTF-8
# flyway檔案位置
spring.flyway.locations=classpath:db/migration
# ִV1__xxx.sql v開頭預設執行一次 
# R1__xxx 開頭的指令碼則會在專案啟動時每次都會清除表後執行
spring.flyway.clean-disabled=false
# flyway 歷史記錄表
spring.flyway.table=flyway_schema_history
  • 新建資料夾

如下圖 resource 新增指令碼檔案(按圖所示目錄新建,不然無法生成)啟動專案可以看到資料庫中出現對應的flyway_schema_history表還有按指令碼生成的表和資料,flyway_schema_history表中記錄的指令碼的變更歷史

小結:至此我們完成SpringBoot web 專案的搭建,以及thymeleaf 模板的整合和資料庫版本管理外掛的整合。