SpringBoot零基礎詳解四 :web開發及原理

Table of Contents

 

四:WEB開發

1:web開發簡介

2:SpringBoot對靜態資源的映射;

2.1:所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找資源;

2.2:"/**" 訪問當前項目的任何資源,都去(靜態資源的文件夾)找映射

2.3:歡迎頁的處理

2.4:配置瀏覽器喜歡的圖標

3:模板引擎

3.1:引入thymeleaf

3.2:thymeleaf語法

3.3:語法規則;

4:SpringMvc的自動配置

4.1:Spring MVC auto-configuration

4.2:擴展MVC

4.3:全面接管SpringMVC

5:如何修改SpringBoot的默認配置

6:基於webmvc的RestfulCRUD實驗

6.1:默認訪問首頁

6.2:國際化配置---中英文顯示

6.3:登錄

6.4:攔截器進行登錄檢查

6.5:CRUD-員工列表

6.6:thymeleaf公共頁面元素抽取

6.7:CRUD-員工添加

6.8:CRUD-員工修改

6.9:CRUD-員工刪除

7:錯誤處理的機制

7.1:SpringBoot默認的錯誤處理機制

7.2:springboot的錯誤機制原理

7.3:定製錯誤響應-定製錯誤頁面

7.4:定製錯誤響應-定製錯誤json數據

7.5:定製錯誤響應-將我們的數據攜帶出去

8:配置嵌入式Servlet容器

8.1:如何定製和修改Sevlet容器的相關配置

8.2:註冊Servlet三大組件【Servlet、Filter、Listener】

8.3:替換爲其他嵌入式Servlet容器

8.4:嵌入式Servlet容器自動配置原理;

8.5:嵌入式Servlet容器啓動原理;

9、使用外置的Servlet容器

9.1:步驟:

9.2:原理

9.3:過程


四:WEB開發

1:web開發簡介

使用SpringBoot;

1)、創建SpringBoot應用,選中我們需要的模塊;

2)、SpringBoot已經默認將這些場景配置好了,只需要在配置文件中指定少量配置就可以運行起來

3)、自己編寫業務代碼;

自動配置原理?

這個場景SpringBoot幫我們配置了什麼?能不能修改?能修改哪些配置?能不能擴展?xxx

xxxxAutoConfiguration:幫我們給容器中自動配置組件;
xxxxProperties:配置類來封裝配置文件的內容;

2:SpringBoot對靜態資源的映射;

2.1:所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找資源;

webjars:以jar包的方式引入靜態資源;

訪問鏈接:http://www.webjars.org/

在pom文件中引入:

就可以訪問靜態資源:http://127.0.0.1:8088/webjars/jquery/3.5.1/jquery.js

2.2:"/**" 訪問當前項目的任何資源,都去(靜態資源的文件夾)找映射

加載自己的靜態資源

要找下面的/**資源 

到下面找:

"classpath:/META-INF/resources/", 
"classpath:/resources/", 
"classpath:/static/", 
"classpath:/public/"

將文件放入上述文件夾後可以訪問:http://127.0.0.1:8088/asserts/js/Chart.min.js

2.3:歡迎頁的處理

靜態資源文件夾下的所有index.html頁面;被"/**"映射

2.4:配置瀏覽器喜歡的圖標

所有的 **/favicon.ico 都是在靜態資源文件下找;

//配置喜歡的圖標
		@Configuration
		@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
		public static class FaviconConfiguration {

			private final ResourceProperties resourceProperties;

			public FaviconConfiguration(ResourceProperties resourceProperties) {
				this.resourceProperties = resourceProperties;
			}

			@Bean
			public SimpleUrlHandlerMapping faviconHandlerMapping() {
				SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
				mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
              	//所有  **/favicon.ico 
				mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
						faviconRequestHandler()));
				return mapping;
			}

			@Bean
			public ResourceHttpRequestHandler faviconRequestHandler() {
				ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
				requestHandler
						.setLocations(this.resourceProperties.getFaviconLocations());
				return requestHandler;
			}

		}

ps:圖標訪問不到記得清緩存!!!

3:模板引擎

模板引擎種類:JSP、Velocity、Freemarker、Thymeleaf

SpringBoot推薦的Thymeleaf;

語法更簡單,功能更強大;

 

 

3.1:引入thymeleaf

<!--        引入thymeleaf啓動器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

3.2:thymeleaf語法

只要我們把HTML頁面放在classpath:/templates/,以.html結尾,thymeleaf就能自動渲染;

第一步:導入thymeleaf的名稱空間,爲了能自動提示

<html lang="en" xmlns:th="http://www.thymeleaf.org">

第二部:使用thymeleaf語法

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>成功!</h1>
<div th:text="${hello}"></div>
</body>
</html>

3.3:語法規則;

1:th:text;改變當前元素裏面的文本內容;

      th:任意html屬性;來替換原生屬性的值

2:表達式

Simple expressions:(表達式語法)
   一: Variable Expressions: ${...}:獲取變量值;OGNL;
    		1)、獲取對象的屬性、調用方法
    		2)、使用內置的基本對象:
    			#ctx : the context object.
    			#vars: the context variables.
                #locale : the context locale.
                #request : (only in Web Contexts) the HttpServletRequest object.
                #response : (only in Web Contexts) the HttpServletResponse object.
                #session : (only in Web Contexts) the HttpSession object.
                #servletContext : (only in Web Contexts) the ServletContext object.
                
                ${session.foo}
            3)、內置的一些工具對象:
#execInfo : information about the template being processed.
#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
#uris : methods for escaping parts of URLs/URIs
#conversions : methods for executing the configured conversion service (if any).
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).

   二: Selection Variable Expressions: *{...}:選擇表達式:和${}在功能上是一樣;
    	補充:配合 th:object="${session.user}:
       <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>
    
    三:Message Expressions: #{...}:獲取國際化內容
    四:Link URL Expressions: @{...}:定義URL;
    		@{/order/process(execId=${execId},execType='FAST')}
    五:Fragment Expressions: ~{...}:片段引用表達式
    		<div th:insert="~{commons :: main}">...</div>
    		
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:
    No-Operation: _ 

簡單例子:

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>成功!</h1>

<hr/>
<div th:text="${hello}"></div>
<div th:utext="${hello}"></div>

<hr/>
<!--th:each在那個標籤上,每次遍歷都會生成這個標籤,這是3個h4-->
<h4 th:text="${user}" th:each="user:${users}"></h4>

<hr/>
<h4>
    <!--寫一個行內寫法-->
    <span th:text="${user}" th:each="user:${users}"></span>
</h4>

</body>
</html>

4:SpringMvc的自動配置

4.1:Spring MVC auto-configuration

Spring Boot 自動配置好了SpringMVC

以下是SpringBoot對SpringMVC的默認配置:(WebMvcAutoConfiguration)

1:Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

  • 自動配置了ViewResolver(視圖解析器:根據方法的返回值得到視圖對象(View),視圖對象決定如何渲染(轉發?重定向?))

  • ContentNegotiatingViewResolver:組合所有的視圖解析器的;

  • 如何定製:我們可以自己給容器中添加一個視圖解析器;自動的將其組合進來

2:Support for serving static resources, including support for WebJars (see below).靜態資源文件夾路徑,webjars

3:Static index.html support. 靜態首頁訪問

4:Custom Favicon support (see below). favicon.ico

5:自動註冊了 of Converter, GenericConverter, Formatter beans.

  • Converter:轉換器; public String hello(User user):接口接受數據類型轉換使用Converter

  • Formatter 格式化器; 2017.12.17===Date;

  • 自己添加的格式化器轉換器,我們只需要放在容器中即可

6:Support for HttpMessageConverters (see below).

  • HttpMessageConverter:SpringMVC用來轉換Http請求和響應的;User---Json;

  • HttpMessageConverters 是從容器中確定;獲取所有的HttpMessageConverter;

    自己給容器中添加HttpMessageConverter,只需要將自己的組件註冊容器中(@Bean,@Component)

7:Automatic registration of MessageCodesResolver (see below).定義錯誤代碼生成規則

8:Automatic use of a ConfigurableWebBindingInitializer bean (see below).

  • 我們可以配置一個ConfigurableWebBindingInitializer來替換默認的;(添加到容器)

**org.springframework.boot.autoconfigure.web:web的所有自動場景;**

If you want to keep Spring Boot MVC features, and you just want to add additional [MVC configuration](https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/htmlsingle#mvc) (interceptors, formatters, view controllers etc.) you can add your own `@Configuration` class of type `WebMvcConfigurerAdapter`, but **without** `@EnableWebMvc`. If you wish to provide custom instances of `RequestMappingHandlerMapping`, `RequestMappingHandlerAdapter` or `ExceptionHandlerExceptionResolver` you can declare a `WebMvcRegistrationsAdapter` instance providing such components.

If you want to take complete control of Spring MVC, you can add your own `@Configuration` annotated with `@EnableWebMvc`.

4.2:擴展MVC

以前用mvc的時候是這樣用的

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

現在SpringBoot是這樣用的:

編寫一個配置類(@Configuration),是WebMvcConfigurerAdapter類型;不能標註@EnableWebMvc;2.x後直接實現WebMvcConfigurer接口

既保留了所有的自動配置,也能用我們擴展的配置;

/**
 * Description:我們自己的配置類,//使用WebMvcConfigurer可以來擴展SpringMVC的功能
 * Date:       2020/5/20 - 下午 3:59
 * author:     wangkanglu
 * version:    V1.0
 */
@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //瀏覽器發送 /hello 請求來到 success頁面
        registry.addViewController("/hello").setViewName("success");
    }
}

原理:

  1. WebMvcAutoConfiguration是SpringMVC的自動配置類
  2. 在做其他自動配置時會導入;@Import(EnableWebMvcConfiguration.class)
@Configuration
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
      private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	 //從容器中獲取所有的WebMvcConfigurer
      @Autowired(required = false)
      public void setConfigurers(List<WebMvcConfigurer> configurers) {
          if (!CollectionUtils.isEmpty(configurers)) {
              this.configurers.addWebMvcConfigurers(configurers);
            	//一個參考實現;將所有的WebMvcConfigurer相關配置都來一起調用;  
            	@Override
             // public void addViewControllers(ViewControllerRegistry registry) {
              //    for (WebMvcConfigurer delegate : this.delegates) {
               //       delegate.addViewControllers(registry);
               //   }
              }
          }
	}

3:容器中所有的WebMvcConfigurer都會一起起作用;

4:我們的配置類也會被調用;效果:SpringMVC的自動配置和我們的擴展配置都會起作用;

4.3:全面接管SpringMVC

SpringBoot對SpringMVC的自動配置不需要了,所有都是我們自己配置;所有的SpringMVC的自動配置都失效了

我們需要在配置類中添加@EnableWebMvc即可;

@EnableWebMvc
@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //瀏覽器發送 /hello 請求來到 success頁面
        registry.addViewController("/hello").setViewName("success");
    }
}

原理:

爲什麼@EnableWebMvc自動配置就失效了;

1:@EnableWebMvc的核心

@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

2:DelegatingWebMvcConfiguration類

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
 

3:WebMvcAutoConfiguration工作的原理

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
//當沒有這個類的時候mvc的自動配置才生效
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})

4:@EnableWebMvc將WebMvcConfigurationSupport組件導入進來;

5:導入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;

5:如何修改SpringBoot的默認配置

模式:

1)、SpringBoot在自動配置很多組件的時候,先看容器中有沒有用戶自己配置的(@Bean、@Component)如果有就用用戶配置的,如果沒有,才自動配置;如果有些組件可以有多個(ViewResolver)將用戶配置的和自己默認的組合起來;

2)、在SpringBoot中會有非常多的xxxConfigurer幫助我們進行擴展配置

3)、在SpringBoot中會有很多的xxxCustomizer幫助我們進行定製配置

6:基於webmvc的RestfulCRUD實驗

6.1:默認訪問首頁

注:在配置文件中添加項目名是

#項目名
server.servlet.context-path=/crud

6.2:國際化配置---中英文顯示

springmvc使用的步驟:

1)、編寫國際化配置文件;

2)、使用ResourceBundleMessageSource管理國際化資源文件

3)、在jsp頁面使用fmt:message取出國際化內容

SpringBoot的使用步驟:

1:編寫國際化配置文件,抽取頁面需要的國際化信息

2:查看springboot的國際化配置原理

在配置文件中配置國際化文件的地址

3:去頁面獲取國際化的值(用#{})

效果:根據瀏覽器的默認語言,來獲取對應的字段

注:亂碼的話設置properties文件爲utf-8

4:根據鏈接切換國際化

原理:

國際化Locale(區域信息對象);LocaleResolver(獲取區域信息對象); 默認的就是根據請求頭帶來的區域信息獲取Locale進行國際化

我們自己配置自己的LocaleResolver,然後其拋出去用我們的不用系統的

6.3:登錄

開發期間模板引擎頁面修改以後,要實時生效

1)、禁用模板引擎的緩存---開發專用

# 禁用緩存
spring.thymeleaf.cache=false 

2)、頁面修改完成以後ctrl+f9:重新編譯;

注:springboot中登錄後的頁面做成重定向;

先映射

再重定向

在頁面利用thymleaf的if判斷增加登錄消息錯誤的提示

6.4:攔截器進行登錄檢查

1:將登錄成功後的標識加入session中

2:自定義攔截器

3:註冊攔截器(springboot1.x不用排除靜態資源,但是2.x也屏蔽了靜態資源,需要排除)

6.5:CRUD-員工列表

1)、RestfulCRUD:CRUD滿足Rest風格;

URI: /資源名稱/資源標識 HTTP請求方式區分對資源CRUD操作

2)、實驗的請求架構;

6.6:thymeleaf公共頁面元素抽取

1:springboot文檔說明

1、抽取公共片段
<div th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</div>

2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::#selector}:模板名::如id選擇器選擇器
~{templatename::fragmentname}:模板名::片段名

3、默認效果:
insert的公共片段在div標籤中
如果使用th:insert等屬性進行引入,可以不用寫~{}:
行內寫法可以加上:[[~{}]];[(~{})];

2:三種引入公共片段的th屬性:

th:insert:將公共片段整個插入到聲明引入的元素中

th:replace:將聲明引入的元素替換爲公共片段

th:include:將被引入的片段的內容包含進這個標籤中

<footer th:fragment="copy">
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

引入方式
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>

效果
<div>
    <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
</div>

<footer>
&copy; 2011 The Good Thymes Virtual Grocery
</footer>

<div>
&copy; 2011 The Good Thymes Virtual Grocery
</div>

3:例子:

片段名:
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
        </li>
    </ul>
</nav>


引入:
 <!--		引入topbar:-->
 <div th:replace="~{commons/bar::topbar}"></div>
id選擇器
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id = "rightbar">

引入:
 <!--		引入sidebar		-->
 <div th:replace="commons/bar::#rightbar(activeUrl='main.html')"></div>

4:向引入的片段中傳參數

6.7:CRUD-員工添加

//return"/emps";表示來到thymleaf引擎下的這個頁面
//return"redirect:/emps";表示重定向到一個地址,“/”表示當前項目下
//return"forward:/emps";表示轉發到一個地址

注:

提交的數據格式不對:生日:日期;

格式有:2017-12-12;2017/12/12;2017.12.12;

日期的格式化;SpringMVC將頁面提交的值需要轉換爲指定的類型;默認日期是按照/的方式;

如果需要更改格式化需要在配置文件中配置:

#更改日期格式
spring.mvc.format.date=yyyy-MM-dd

 

6.8:CRUD-員工修改

resful接口,讀取路徑中的參數

修改員工頁面和添加員工頁面二合一(主要是thymleaf的用法)

<!--需要區分是員工修改還是添加;-->
<form th:action="@{/emp}" method="post">
	<!--發送put請求修改員工數據-->
	<!--
1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自動配置好的)
2、頁面創建一個post表單
3、創建一個input項,name="_method";值就是我們指定的請求方式
-->
	<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
	<input type="hidden" name="id" th:value="${emp!=null}?${emp.id}" th:if="${emp!=null}"/>
	<div class="form-group">
		<label>LastName</label>
		<input type="text" name="lastName" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
	</div>
	<div class="form-group">
		<label>Email</label>
		<input type="email" name="email" class="form-control" placeholder="[email protected]" th:value="${emp!=null}?${emp.email}">
	</div>
	<div class="form-group">
		<label>Gender</label><br/>
		<div class="form-check form-check-inline">
			<input class="form-check-input" type="radio" name="gender"  value="1" th:checked="${emp!=null}?${emp.gender==1}">
			<label class="form-check-label">男</label>
		</div>
		<div class="form-check form-check-inline">
			<input class="form-check-input" type="radio" name="gender"  value="0" th:checked="${emp!=null}?${emp.gender==0}">
			<label class="form-check-label">女</label>
		</div>
	</div>
	<div class="form-group">
		<label>department</label>
		<select class="form-control" name="department.id">
			<option th:selected="${emp!=null}?${department.id==emp.department.id}" th:value="${department.id}"  th:text="${department.departmentName}"
					th:each="department:${departments}">1
			</option>

		</select>
	</div>
	<div class="form-group">
		<label>Birth</label>
		<input type="text" name="birth" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}">
	</div>
	<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
</form>

注:springboot2.x版本後開啓put接口的攔截器要打開

#開啓springboot的put模式
spring.mvc.hiddenmethod.filter.enabled=true

 

6.9:CRUD-員工刪除

 

<td>
										<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">編輯</a>
										<button id="deletebtn" th:attr="dele_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger">刪除</button>
									</td>
								</tr>
							</tbody>

						</table>
					</div>
				</main>
				<form id="deleteEmpForm"  method="post">
					<input  type="hidden" name="_method" value="delete"/>
				</form>



<script>
			$("#deletebtn").click(function () {
				// alert(1)
				$("#deleteEmpForm").attr("action",$(this).attr("dele_uri")).submit();
			});
</script>

注:同時也記得在配置文件中配置:

spring.mvc.hiddenmethod.filter.enabled=true

 

7:錯誤處理的機制

7.1:SpringBoot默認的錯誤處理機制

1:瀏覽器,默認返回一個頁面

請求頭:

2:如果是其他客戶端訪問返回json數據

請求頭:

7.2:springboot的錯誤機制原理

可以參照ErrorMvcAutoConfiguration;錯誤處理的自動配置;

步驟:

1:一但系統出現4xx或者5xx之類的錯誤;ErrorPageCustomizer就會生效(定製錯誤的響應規則);

系統出現錯誤以後來到/error請求進行處理;(相當於web.xml註冊的錯誤頁面規則)

2:來到/error請求;就會被BasicErrorController處理;處理默認的/error請求

這個類的代碼是: 

     @RequestMapping(
        //返回html頁面,瀏覽器請求來到這個請求
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        //通過這個方法來到錯誤頁面
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

    //返回json數據,其他客戶端來到這個請求
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            //當ResponseEntity的數據爲list,map之類的話,自動轉爲json
            return new ResponseEntity(body, status);
        }
    }

3:上述跳轉錯誤頁面的方法resolveErrorView解析如下:

//所有的ErrorViewResolver得到ModelAndView,去哪個頁面是由DefaultErrorViewResolver解析得到的;

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        Iterator var5 = this.errorViewResolvers.iterator();

        ModelAndView modelAndView;
        //所有的ErrorViewResolver得到ModelAndView
        do {
            if (!var5.hasNext()) {
                return null;
            }

            ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
            //通過這個方法來到DefaultErrorViewResolver
            modelAndView = resolver.resolveErrorView(request, status, model);
        } while(modelAndView == null);

        return modelAndView;
    }

4:如果模板引擎可用就來到如:/error/404.html,如果不存在就在靜態資源中尋找404.html

 

7.3:定製錯誤響應-定製錯誤頁面

注:springboot2.x後默認不顯示message和excption需要在配置文件中配置

#提示message消息
server.error.include-message=always
#顯示excepting消息
server.error.include-exception=true

1)、有模板引擎的情況下;error/狀態碼;

【將錯誤頁面命名爲 錯誤狀態碼.html 放在模板引擎文件夾裏面的 error文件夾下】,發生此狀態碼的錯誤就會來到 對應的頁面;

我們可以使用4xx和5xx作爲錯誤頁面的文件名來匹配這種類型的所有錯誤,精確優先(優先尋找精確的狀態碼.html);

當跳轉錯誤頁面時,會向頁面帶錯誤信息,該信就由下邊的 方法得到:

 

所以頁面能獲取的信息;

timestamp:時間戳

status:狀態碼

error:錯誤提示

exception:異常對象

message:異常消息

errors:JSR303數據校驗的錯誤都在這裏

前臺展示:

<h1>status:[[${status}]]</h1>
<h2>timestamp:[[${timestamp}]]</h2>
<h2>error:[[${error}]]</h2>
<h2>exception:[[${exception}]]</h2>
<h2>message:[[${message}]]</h2>
<h2>errors:[[${errors}]]</h2>

 

7.4:定製錯誤響應-定製錯誤json數據

1:自定義異常處理,返回定製的json數據;

@ControllerAdvice
public class MyExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UserNotException.class)
    public Map<String,Object> handldeException(Exception e){
        Map<String ,Object> map = new HashMap<>();
        map.put("code","usernotfind");
        map.put("message",e.getMessage());
        return map;
    }
}

 弊端就是這個方法沒有自適應效果,不論是客戶端還是瀏覽器都返回該json數據;

2:轉發到/error進行自適應響應效果處理

@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(UserNotException.class)
    public String handldeException(Exception e, HttpServletRequest request){
        Map<String ,Object> map = new HashMap<>();
        // Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
        //傳入我們自己的狀態碼
        request.setAttribute("javax.servlet.error.status_code",400);
        map.put("code","usernotfind");
        map.put("message",e.getMessage());
        System.out.println("e:"+ JSONObject.toJSONString(e));
        return "forward:/error";
    }
}

 

7.5:定製錯誤響應-將我們的數據攜帶出去

出現錯誤以後,會來到/error請求,會被BasicErrorController處理,響應出去可以獲取的數據是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)規定的方法);

1:所以我們可以完全來編寫一個ErrorController的實現類【或者是編寫AbstractErrorController的子類】,放在容器中; 但是太費時 不推薦

2:頁面上能用的數據,或者是json返回能用的數據都是通過errorAttributes.getErrorAttributes得到;是DefaultErrorAttributes的方法,所以我們可以重寫DefaultErrorAttributes類的方法

@Component
public class MyErrorContoller extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, options);
        //增加我們自己的標識
        map.put("sign","wkl");
        //或者從請求域中拿到我們跑出異常的信息(該信息事前放在請求域中)
        //他的構造方法,0代表request請求域,1代表session
        Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
        map.put("ext",ext);
        return map;
    }
}

8:配置嵌入式Servlet容器

Springboot默認使用Tomcat作爲嵌入式的容器

8.1:如何定製和修改Sevlet容器的相關配置

1:配置文件修改和server有關的配置(ServerProperties);

server.port=8081
server.context-path=/crud

server.tomcat.uri-encoding=UTF-8

//通用的Servlet容器設置
server.xxx
//Tomcat的設置
server.tomcat.xxx

2:編寫一個EmbeddedServletContainerCustomizer:嵌入式的Servlet容器的定製器;來修改Servlet容器的配置

@Bean
    public ConfigurableServletWebServerFactory configurableServletWebServerFactory(){
        TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
        tomcatServletWebServerFactory.setPort(8089);
        return tomcatServletWebServerFactory;
    }

8.2:註冊Servlet三大組件【Servlet、Filter、Listener】

由於SpringBoot默認是以jar包的方式啓動嵌入式的Servlet容器來啓動SpringBoot的web應用,沒有web.xml文件。

註冊三大組件用以下方式

1:ServletRegistrationBean

@Configuration
public class MyServletConfig {

    @Bean
    public ServletRegistrationBean servletRegistrationBean(){
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/servlet");
        return servletRegistrationBean;
    }
}

2:FilterRegistrationBean

public class MyFileter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter process...");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}
//註冊Filter
    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new MyFileter());
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/servlet"));
        return filterRegistrationBean;
    }

3:ServletListenerRegistrationBean

//監聽服務器啓動關閉
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contenx監聽器啓動了。。。");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contenx監聽器關閉了。。。");

    }
}
//註冊監聽器
    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean(){
        ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean(new MyListener());

        return servletListenerRegistrationBean;
    }

4:SpringBoot幫我們自動配置SpringMVC的時候,自動的註冊SpringMVC的前端控制器;DIspatcherServlet;

在DispatcherServletAutoConfiguration類中:

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
      DispatcherServlet dispatcherServlet) {
   ServletRegistrationBean registration = new ServletRegistrationBean(
         dispatcherServlet, this.serverProperties.getServletMapping());
    //默認攔截: /  所有請求;包靜態資源,但是不攔截jsp請求;   /*會攔截jsp
    //可以通過server.servletPath來修改SpringMVC前端控制器默認攔截的請求路徑
    
   registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
   registration.setLoadOnStartup(
         this.webMvcProperties.getServlet().getLoadOnStartup());
   if (this.multipartConfig != null) {
      registration.setMultipartConfig(this.multipartConfig);
   }
   return registration;
}

8.3:替換爲其他嵌入式Servlet容器

默認支持:

Tomcat(默認使用)

        <!--引入web啓動器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

Jetty(長鏈接如聊天)

<!--        引入web啓動器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--   引入其他sevrlet容器     -->
        <dependency>
            <artifactId>spring-boot-starter-jetty</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

Undertow(不支持jsp)

<!--        引入web啓動器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--   引入其他sevrlet容器     -->
        <dependency>
            <artifactId>spring-boot-starter-undertow</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>

 

8.4:嵌入式Servlet容器自動配置原理;

步驟:

1)、SpringBoot根據導入的依賴情況,給容器中添加相應的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】

2)、容器中某個組件要創建對象就會驚動後置處理器;EmbeddedServletContainerCustomizerBeanPostProcessor;

只要是嵌入式的Servlet容器工廠,後置處理器就工作;

3)、後置處理器,從容器中獲取所有的EmbeddedServletContainerCustomizer,調用定製器的定製方法

8.5:嵌入式Servlet容器啓動原理;

什麼時候創建嵌入式的Servlet容器工廠?什麼時候獲取嵌入式的Servlet容器並啓動Tomcat;

獲取嵌入式的Servlet容器工廠:

1)、SpringBoot應用啓動運行run方法

2)、refreshContext(context);SpringBoot刷新IOC容器【創建IOC容器對象,並初始化容器,創建容器中的每一個組件】;如果是web應用創建AnnotationConfigEmbeddedWebApplicationContext,否則:AnnotationConfigApplicationContext

3)、refresh(context);刷新剛纔創建好的ioc容器;

4)、 onRefresh(); web的ioc容器重寫了onRefresh方法

5)、webioc容器會創建嵌入式的Servlet容器;createEmbeddedServletContainer();

6)、獲取嵌入式的Servlet容器工廠:

EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();

從ioc容器中獲取EmbeddedServletContainerFactory 組件;TomcatEmbeddedServletContainerFactory創建對象,後置處理器一看是這個對象,就獲取所有的定製器來先定製Servlet容器的相關配置;

7)、使用容器工廠獲取嵌入式的Servlet容器:this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());

8)、嵌入式的Servlet容器創建對象並啓動Servlet容器;

先啓動嵌入式的Servlet容器,再將ioc容器中剩下沒有創建出的對象獲取出來;

IOC容器啓動創建嵌入式的Servlet容器

 

9、使用外置的Servlet容器

嵌入式Servlet容器:應用打成可執行的jar

優點:簡單、便攜;

缺點:默認不支持JSP、優化定製比較複雜(使用定製器【ServerProperties、自定義EmbeddedServletContainerCustomizer】,自己編寫嵌入式Servlet容器的創建工廠【EmbeddedServletContainerFactory】);

外置的Servlet容器:外面安裝Tomcat---應用war包的方式打包;

9.1:步驟:

1:必須創建一個war項目;

2:利用idea創建好目錄結構(創建webapp目錄和web.xml)

創建webapp目錄

創建web.xml

3:將嵌入式的Tomcat指定爲provided;

4:必須編寫一個SpringBootServletInitializer的子類,並調用configure方法  

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        //傳入SpringBoot應用的主程序
        return application.sources(Springboot04WebJspApplication.class);
    }

}

5:配置外部tomcat

6:測試路徑

#跳轉頁面前綴
spring.mvc.view.prefix=/WEB-INF/
#跳轉頁面後綴
spring.mvc.view.suffix=.jsp

9.2:原理

jar包:執行SpringBoot主類的main方法,啓動ioc容器,創建嵌入式的Servlet容器;

war包:啓動服務器,服務器啓動SpringBoot應用【SpringBootServletInitializer】,啓動ioc容器;

 

規則:

1)、服務器啓動(web應用啓動)會創建當前web應用裏面每一個jar包裏面ServletContainerInitializer實例:

2)、ServletContainerInitializer的實現放在jar包的META-INF/services文件夾下,有一個名爲javax.servlet.ServletContainerInitializer的文件,內容就是ServletContainerInitializer的實現類的全類名

3)、還可以使用@HandlesTypes,在應用啓動的時候加載我們感興趣的類;

9.3:過程

1)、啓動Tomcat

2)、org/springframework/spring-web/5.2.6.RELEASE/spring-web-5.2.6.RELEASE.jar!/META-INF/services/javax.servlet.ServletContainerInitializer

Spring的web模塊裏面有這個文件,內容:org.springframework.web.SpringServletContainerInitializer

3:SpringServletContainerInitializer將@HandlesTypes(WebApplicationInitializer.class)標註的所有這個類型的類都傳入到onStartup方法的Set<Class<?>>;爲這些不是接口的WebApplicationInitializer類型的類創建實例;

4:每一個WebApplicationInitializer都調用自己的onStartup;

他的實現類:

5:相當於我們的SpringBootServletInitializer的類會被創建對象,並執行onStartup方法,該方法就是ServletInitializer的父類

6:SpringBootServletInitializer實例執行onStartup的時候會createRootApplicationContext;創建根容器

protected WebApplicationContext createRootApplicationContext(
      ServletContext servletContext) {
    //1、創建SpringApplicationBuilder環境構建器
   SpringApplicationBuilder builder = createSpringApplicationBuilder();
   StandardServletEnvironment environment = new StandardServletEnvironment();
   environment.initPropertySources(servletContext, null);
   builder.environment(environment);
   builder.main(getClass());
   ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
   if (parent != null) {
      this.logger.info("Root context already created (using as parent).");
      servletContext.setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
      builder.initializers(new ParentContextApplicationContextInitializer(parent));
   }
   builder.initializers(
         new ServletContextApplicationContextInitializer(servletContext));
   builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
    
    //調用configure方法,子類重寫了這個方法,將SpringBoot的主程序類傳入了進來
   builder = configure(builder);
    
    //使用builder創建一個Spring應用
   SpringApplication application = builder.build();
   if (application.getSources().isEmpty() && AnnotationUtils
         .findAnnotation(getClass(), Configuration.class) != null) {
      application.getSources().add(getClass());
   }
   Assert.state(!application.getSources().isEmpty(),
         "No SpringApplication sources have been defined. Either override the "
               + "configure method or add an @Configuration annotation");
   // Ensure error pages are registered
   if (this.registerErrorPageFilter) {
      application.getSources().add(ErrorPageFilterConfiguration.class);
   }
    //啓動Spring應用
   return run(application);
}

7:Spring的應用就啓動並且創建IOC容器

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      analyzers = new FailureAnalyzers(context);
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
       
       //刷新IOC容器
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章