1. 程式人生 > >【Spring Boot課程】5.web開發

【Spring Boot課程】5.web開發

使用spring boot的開發流程:

  1. 建立Spring Boot應用,選中我們需要的模組;
  2. Spring Boot已經為我們將所有的場景配置好了(spring-boot-autoconfigure包自動配置),我們只需要在配置檔案中指定對應Properties相關的少量配置就可以執行起來;
  3. 編寫業務程式碼。

自動配置原理?

請記住,飲水則思源,在每運用一個場景的時候,都要記住這是自動配置原理生效的。同時也要思考一下,spring-boot為我們配置了什麼?能不能修改?有哪些配置可以修改?是否可以擴充套件等。

一定要記住:xxxAutoConfiguration幫我們在容器中自動的配置相關元件,而其xxxProperties封裝了配置檔案的內容。

5.1 spring boot對靜態資源的對映規則

查詢類(ctrl+shift+n)WebMvcAutoConfiguration檢視其原始碼,與web相關的配置在此處都可以看到:

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties {

也就是說有關配置可以通過spring.resourcec進行設定。

webjars

再來看


        public void
addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); } else { Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl =
this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } } }

從該程式碼中我們可以看到,所有/webjars/**都去classpath:/META-INF/resources/webjars中找資源;

  • webjars: 以jar包的方式引入靜態資源,如jquery等,我們只需要配置pom檔案即可引入這些靜態資源,參考:官網地址

引入jquery:

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

我們將下載到的jquery包展開可以看清楚目錄結構,結合上面有關webjars的解釋,我們可以嘗試通過訪問127.0.0.1:8081/webjars/jquery/3.3.0/jquery.js,發現可以成功的訪問到該靜態資源。也就是說,打包後的專案的確是可以訪問到的。

靜態資原始檔夾

還有一種配置資源的方式,"/**"訪問當前專案的任何資源:

  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/
  • /:當前專案的根路徑

也就說,我們在上述資料夾上面放置靜態資源,如果沒有特別處理,系統預設都是可以訪問到的。

歡迎頁

繼續檢視該類的原始碼:

        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
            return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
        }

此處配置的是歡迎頁的對映。他都是靜態資原始檔夾下的所有index.html檔案。被/**對映。
如localhost:8008則就是訪問歡迎頁面。

預設圖示

            @Bean
            public SimpleUrlHandlerMapping faviconHandlerMapping() {
                SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
                mapping.setOrder(-2147483647);
                mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler()));
                return mapping;
            }

該處配置我們喜歡的圖示,即瀏覽器表標籤頁左方的小圖示。還是對映到了**.favicon.ico,也就是我們的所有靜態資料夾下面。

從上面可以看出,系統預設用一個staticLocations作為對映路徑,該路徑預設為剛才所列出的預設路徑,如果要配置自定義的資源路徑,則可以:

spring.resources.static-locations=classpath:/hello,classpath:/hello2

就可以了,但要注意之前的預設值就不生效了。

5.2 模板引擎

市面上比較常用的有JSP、Velocity、FreeMarker、以及Spring boot推薦的Thymeleaf。

雖然模板引擎很多,但是其核心思想都是一樣的,即按照一定的語法標準,將你的資料轉化為實際的html頁面,他們的區別只在於語法。

spring boot推薦的themeleaf語法比較簡單,而且功能更強大。

引入themeleaf

注意,如果是1.x版本的spring boot此處若想使用3.x版本的thymeleaf的話,請在properties配置節配置其版本號以及佈局版本號,以覆蓋SB中預設的低版本thymeleaf。

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

檢視spring-boot-autoconfigure包可以檢視到有關thymeleaf相關的配置資訊ThymeleafAutoConfiguration。其無非就是為thymeleaf新增相關的元件。我們主要關注的是其相關的配置規則:ThymeleafProperties。

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

從預設規則裡面我們不難看出很多東西其實已經無需修改,就按該預設配置進行業務程式碼編寫就行了。也就是從配置中可以看出,我們的所有頁面只需要放在classpath:/templates/資原始檔夾下面,並以.html即為即可,根本無需其他多餘的配置:

  @RequestMapping("success")
    public String success(){
        return "success";
    }

該程式碼交給thymeleaf渲染template/success.html的檔案。

5.3 thymeleaf語法

詳細的官方檔案請點選:參考文件

使用步驟:

  1. 匯入thymeleaf的名稱空間在html標籤中,這樣就有語法提示了;
<html xmlns:th="http://www.thymeleaf.org">
  1. controller傳值
    @RequestMapping("success")
    public String success(Map<String, Object> map){
        map.put("hello", "你好");
        return "success";
    }

  1. 渲染頁使用thymeleaf語法
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
this is success page.
<p th:text="${hello}">這裡顯示預設資訊。</p>
</body>
</html>

從輸出結果可以看出,該頁面完全可以獨立於出來直接執行,其輸出的結果不經過渲染只不過只會輸出這裡是預設資訊這樣的欄位而已,做到了很好的前後端分離。

標籤語法

改變當前元素的文字內容等,例如:th:html屬性 其值都可以替換原生的元素對應的值。
相關的介紹參考官方文件;

標籤語法詳解

th:each

參考文件Iteration部分。

該標籤用於遍歷陣列資料。需要注意的是,該標籤寫在那個標籤上,那個標籤都會在每次遍歷中生成一次。由此我們可以瞭解到,該標籤經常會和表格行標籤<tr>搭配使用。

<h1>Product list</h1>
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
</table>

表示式語法

  1. 簡單表示式:
  • Variable Expressions: ${…}
  • Selection Variable Expressions: *{…}
  • Message Expressions: #{…}
  • Link URL Expressions: @{…}
  • Fragment Expressions: ~{…}
  1. 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 ,…
  1. Text operations(文字操作)
  • String concatenation: +
  • Literal substitutions: |The name is ${name}|
  1. Arithmetic operations(數學運算)
  • Binary operators: + , - , * , / , %
  • Minus sign (unary operator): -
  1. Boolean operations(布林運算)
  • Binary operators: and , or
  • Boolean negation (unary operator): ! , not
  1. Comparisons and equality(比較運算)
  • Comparators: > , < , >= , <= ( gt , lt , ge , le )
  • Equality operators: == , != ( eq , ne )
  1. Conditional operators(條件操作)
  • If-then: (if) ? (then)
  • If-then-else: (if) ? (then) : (else)
  • Default: (value) ?: (defaultvalue)
  1. Special tokens(特殊標記-token:特徵;記號)
  • No-Operation: _

以上所有的片段都可以組合使用,例如:

'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))

表示式語法詳解

${…}

該表示式用於獲取變數的值,使用的是OGNL語法:

  1. 獲取物件的屬性、呼叫方法
  2. 使用內建的基本物件
${session.user.id}
  1. 內建的工具物件

*{…}

和${…}的功能是一樣的,不過有一個補充使用,即配合<th:object>使用,如下面的示例:

<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

其等價於:

<div>
<p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>

也就是說,用*{}可以省略公共的物件資訊。當然,我們也可以在迴圈體內部混合使用這兩種語法:

<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

#{…}

該表示式用於獲取國際化內容。

@{…}

定義url。

<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

如果需要新增多個引數的話,用逗號分隔即可。

@{/order/process(execId=${execId},execType='FAST')}

~{…}

片段引用表示式。

<div th:insert="~{commons :: main}">...</div>

行內表示式

參考文件Inlining部分。

很多時候,我們想顯示的文字其實是單純的文字節點,完全不想使用html標籤包裹,這時候需要怎麼寫呢。我們顯然不能這樣寫:

my name is ${$name}.

正確的寫法應該使用行內表示式

my name is [[${name}]]!

關於行內式有兩種標籤寫法:

  • [[...]] 等價於th:text,會轉義特殊字元,即按原樣輸出,<h1>標籤會格式化內部文字;
  • [(...)] 等價於th:utext,不會轉義特殊字元,即按相應的標籤格式輸出,例如<h1>標籤直接輸出為<h1>

轉義這個有點繞,不小心把自己也繞進去了。簡單一點的記法:想要有html標籤格式化文字,用text([[...]]),想原樣輸出標籤用utext,。

5.4 spring mvc自動配置

通過官方文件查閱

原始碼查閱

檢視文件

Spring boot 自動配置好了Spring mvc,以下是SB對spring mvc的預設配置,這些配置元件都可以在WebMvcConfigurationSupport類中檢視相應原始碼:

  1. 自動配置了ViewResoulver(檢視解析器:根據方法的返回值得到檢視物件(View),檢視物件決定如何渲染…轉發、重定向)

    • ContentNegotiatingViewResolver: 組合所有的檢視解析器。
    • 如何定製:我們可以自己給容器中新增一個檢視解析器,他會自動的將其組合起來。
  2. Support for serving static resources, including support for WebJars (covered later in this document)): 靜態資原始檔夾路徑,webjars.

  3. Automatic registration of Converter, GenericConverter, and Formatter beans.

    • Converter:轉換器,型別轉化
    • Formatter 格式化器:2017.1.1===Date;
  4. Support for HttpMessageConverters (covered later in this document)

    • springMVC中用來轉換http請求和響應的User===Json
    • HttpMessageConverters是從容器中確定獲取所有的HttpMessageConver;自己給容器中新增賭贏的元件,只需要將自己的元件註冊到容器中即可(@Bean,@Component)
  5. Automatic registration of MessageCodesResolver (covered later in this document).

    • 定義錯誤程式碼的生成規則的;
  6. Static index.html support.

    • 靜態頁面支援
  7. Custom Favicon support (covered later in this document).

    • 圖示支援
  8. Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

    • 作用為初始化WebDataBinder的,例如請求資料JavaBean,就需要用到資料繫結器,裡面牽涉到型別轉化、格式化等等;
    • 我們可以配置一個ConfigurableWebBindingInitializer來替換預設的。

SB對web的自動配置其實不止這個類中做了,其實還有其他的以***AutoConfiguration的類,都對web場景進行了一些配置。

5.5 修改SpringBoot的預設配置

模式:給容器中新增對應的自定義元件就可以了。

  1. Spring Boot在自動配置很多元件的時候,都會先看容器中有沒有使用者自己配置(@Bean、@Component),如果有就用使用者配置的,如果沒有就用Spring Boot自己預設配置的;但是如果元件可以有多個的話,則是將其所有配置的組合起來;
  2. 僅靠Spring boot的預設配置還是不夠的,我們還需要一些自己新的配置,那麼我們則需要擴充套件以及全面接管springMVC的相關功能。如何做呢?見下一節。

5.6 擴充套件與全面接管SpringMVC

我們以前配置試圖對映、攔截器都可以在springmvc.xml中進行配置,如下圖所示:

```xml
    <mvc:view-controller path="/hello" view-name="success"/>
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/hello"/>
            <bean></bean>
        </mvc:interceptor>
    </mvc:interceptors>

那在spring boot中該怎麼做呢?

編寫一個配置類(@Configuration),是WebMvcConfigurerAdapter型別;不能標註@EnableWebMvc

注意此處是1.x版本,2.x版本提示這個類準備移除了,我們不再需要繼承WebMvcConfigurerAdapter,而是直接實現介面WebMvcConfigurer來寫配置類即可。

@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 瀏覽器傳送/restweb也會來到hello頁面.
        registry.addViewController("/restweb").setViewName("hello");
    }
}

該方式即保留了spring boot的所有自動配置,也能用我們的擴充套件配置。可以檢視如下了解原理:

  • WebMvcAutoConfiguration是springmvc的自動配置類;
  • 再做其他自動配置時會匯入,@import(EnableWebMvcConfiguration.class)
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}

spring boot 會將所有的WebMvcConfiguration相關配置一起呼叫

  • 容器中所有的WebMvcConfigurer都會起作用,包括我們自己配置的;

全面接管SpringMVC

這種情況下,我們一般是不想要SpringBoot的所有配置,所有的都是由我們自己來指定。只需要在自定義配置類中新增一個配置@EnableWebMvc即可,這樣之後所有的SpringMVC的自動配置都失效了。

@EnableWebMvc原理:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

注意其中的DelegatingWebMvcConfiguration,其原始碼如下:

@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

我們再來看自動配置類的原始碼:

@Configuration
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {

我們可以看到這一句

@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})

其生效方式剛好就是若容器中沒有WebMvcConfigurationSupport,由於前面的註解導致該元件匯入了進來,因此自動配置類就不生效了。匯入的WebMvcConfigurationSupport只是SpringMVC最基本的功能。

使用SpringBoot一般