開發與維運

為什麼阿里、頭條、美團這些互聯網大廠都在用Spring Boot?

前言

現在各大技術社區 Spring Boot 的文章越來越多,Spring Boot 相關的圖文、視頻教程越來越多,使用 Spring Boot 的互聯網公司也越來越多; Java 程序員現在出去面試, Spring Boot 已經成了必問的內容。

一切都在證明,Spring Boot 已經成為了 Java 程序員必備的技能。並且可以預見的是未來 Spring Boot 的發展還會更好。

所以對Java程序員來說其中不乏說對 Spring Boot 非常熟悉的,然後當問到一些 Spring Boot 核心功能和原理的時候,沒人能說得上來,或者說不到點上,可以說一個問題就問趴下了!(問題:你能講下為什麼我們要用 Spring Boot 嗎?)

相信我,上面這些類似的問題,90%有經驗的Java程序員超都曾遇見過!但很少有系統化的回答。

因此,總結了這份Spring Boot核心知識點實戰教程,通過這份教程,帶你梳理Spring Boot 技術體系。

文末有彩蛋~

Spring Boot2教程

在Spring Boot項目中,正常來說是不存在XML配置,這是因為Spring Boot不推薦使用 XML ,注意,並非不支持,Spring Boot 推薦開發者使用 Java 配置來搭建框架,Spring Boot 中,大量的自動化配置都是通過 Java 配置來實現的,這一套實現方案,我們也可以自己做,即自己也可以使用純 Java 來搭建一個 SSM 環境,即在項目中,不存在任何 XML 配置,包括 web.xml 。

環境要求:

使用純 Java 來搭建 SSM 環境,要求 Tomcat 的版本必須在 7 以上。

1、創建工程

創建一個普通的 Maven工程(注意,這裡可以不必創建Web工程),並添加SpringMVC的依賴,同時,這裡環境的搭建需要用到 Servlet ,所以我們還需要引入 Servlet 的依賴(一定不能使用低版本的Servlet),最終的 pom.xml 文件如下:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.1.6.RELEASE</version>
</dependency>
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>4.0.1</version>
  <scope>provided</scope>
</dependency>

2 、添加 Spring 配置

工程創建成功之後,首先添加 Spring 的配置文件,如下:

@Configuration
@ComponentScan(basePackages = "org.javaboy", useDefaultFilters = true,
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
Controller.class)})
public class SpringConfig {
}

關於這個配置,我說如下幾點:

@Configuration 註解表示這是一個配置類,在我們這裡,這個配置的作用類似於applicationContext.xml

@ComponentScan 註解表示配置包掃描,裡邊的屬性和 xml 配置中的屬性都是一一對應的,useDefaultFilters 表示使用默認的過濾器,然後又除去 Controller 註解,即在 Spring 容器中掃描除了 Controller 之外的其他所有 Bean 。

3、 添加 SpringMVC 配置

接下來再來創建 springmvc 的配置文件:

@Configuration
@ComponentScan(basePackages = "org.javaboy",useDefaultFilters =
false,includeFilters = {@ComponentScan.Filter(type =
FilterType.ANNOTATION,classes = Controller.class)})
public class SpringMVCConfig {
}

注意,如果不需要在SpringMVC中添加其他的額外配置,這樣就可以了。即視圖解析器、JSON解析、文件上傳......等等,如果都不需要配置的話,這樣就可以了。

4、配置 web.xml

此時,我們並沒 web.xml 文件,這時,我們可以使用Java代碼去代替 web.xml 文件,這裡會用到WebApplicationInitializer ,具體定義如下:

public class WebInit implements WebApplicationInitializer {
  public void onStartup(ServletContext servletContext) throws ServletException
{
    //首先來加載 SpringMVC 的配置文件
    AnnotationConfigWebApplicationContext ctx = new
AnnotationConfigWebApplicationContext();
    ctx.register(SpringMVCConfig.class);
    // 添加 DispatcherServlet
    ServletRegistration.Dynamic springmvc =
servletContext.addServlet("springmvc", new DispatcherServlet(ctx));
    // 給 DispatcherServlet 添加路徑映射
    springmvc.addMapping("/");
    // 給 DispatcherServlet 添加啟動時機
    springmvc.setLoadOnStartup(1);
 }
}

WebInit 的作用類似於 web.xml,這個類需要實現 WebApplicationInitializer 接口,並實現接口中的方法,當項目啟動時,onStartup 方法會被自動執行,我們可以在這個方法中做一些項目初始化操作,例如加載 SpringMVC 容器,添加過濾器,添加 Listener、添加 Servlet 等。關注公種浩:程序員追風,回覆 005 獲取這份整理好的Spring Boot實戰教程。

注意:

由於我們在WebInit中只是添加了SpringMVC的配置,這樣項目在啟動時只會去加載SpringMVC容器,而不會去加載 Spring 容器,如果一定要加載 Spring 容器,需要我們修改 SpringMVC 的配置,在SpringMVC 配置的包掃描中也去掃描 @Configuration 註解,進而加載 Spring 容器,還有一種方案可以解決這個問題,就是直接在項目中捨棄 Spring 配置,直接將所有配置放到 SpringMVC 的配置中來完成,這個在 SSM 整合時是沒有問題的,在實際開發中,較多采用第二種方案,第二種方案,SpringMVC 的配置如下:

@Configuration
@ComponentScan(basePackages = "org.javaboy")
public class SpringMVCConfig {
}

這種方案中,所有的註解都在 SpringMVC 中掃描,採用這種方案的話,則 Spring 的配置文件就可以刪除了。

5、測試

最後,添加一個 HelloController ,然後啟動項目進行測試:

@RestController
public class HelloController {
  @GetMapping("/hello")
  public String hello() {
    return "hello";
 }
}

啟動項目,訪問接口,結果如下:

Spring Boot全局異常處理

在Spring Boot項目中 ,異常統一處理,可以使用Spring中@ControllerAdvice來統一處理,也可以自己來定義異常處理方案。Spring Boot 中,對異常的處理有一些默認的策略,我們分別來看。

默認情況下,Spring Boot 中的異常頁面 是這樣的:

我們從這個異常提示中,也能看出來,之所以用戶看到這個頁面,是因為開發者沒有明確提供一個/error 路徑,如果開發者提供了 /error 路徑 ,這個頁面就不會展示出來,不過在 Spring Boot 中,提供/error 路徑實際上是下下策,Spring Boot本身在處理異常時,也是當所有條件都不滿足時,才會去找 /error 路徑。那麼我們就先來看看,在 Spring Boot 中,如何自定義 error 頁面,整體上來說,可以分為兩種,一種是靜態頁面,另一種是動態頁面。

靜態異常頁面

自定義靜態異常頁面,又分為兩種,第一種 是使用HTTP響應碼來命名頁面,例如404.html、405.html、500.html ....,另一種就是直接定義一個 4xx.html,表示400-499 的狀態都顯示這個異常頁面,5xx.html 表示 500-599 的狀態顯示這個異常頁面。

默認是在 classpath:/static/error/ 路徑下定義相關頁面:

此時,啟動項目,如果項目拋出 500 請求錯誤,就會自動展示 500.html 這個頁面,發生 404 就會展示404.html 頁面。如果異常展示頁面既存在 5xx.html,也存在 500.html ,此時,發生500異常時,優先展示 500.html 頁面。

動態異常頁面

動態的異常頁面定義方式和靜態的基本 一致,可以採用的頁面模板有 jsp、freemarker、thymeleaf。

動態異常頁面,也支持 404.html 或者 4xx.html ,但是一般來說,由於動態異常頁面可以直接展示異常詳細信息,所以就沒有必要挨個枚舉錯誤了 ,直接定義 4xx.html(這裡使用thymeleaf模板)或者5xx.html 即可。

注意,動態頁面模板,不需要開發者自己去定義控制器,直接定義異常頁面即可 ,Spring Boot 中自帶的異常處理器會自動查找到異常頁面。

頁面定義如下:

頁面內容如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>5xx</h1>
<table border="1">
  <tr>
    <td>path</td>
    <td th:text="${path}"></td>
  </tr>
  <tr>
    <td>error</td>
    <td th:text="${error}"></td>
  </tr>
  <tr>
    <td>message</td>
    <td th:text="${message}"></td>
  </tr>
  <tr>
    <td>timestamp</td>
    <td th:text="${timestamp}"></td>
  </tr>
  <tr>
    <td>status</td>
    <td th:text="${status}"></td>
  </tr>
</table>
</body>
</html>

默認情況下,完整的異常信息就是這5條,展示 效果如下 :

如果動態頁面和靜態頁面同時定義了異常處理頁面,例如 classpath:/static/error/404.html 和classpath:/templates/error/404.html 同時存在時,默認使用動態頁面。即完整的錯誤頁面查找

方式應該是這樣:

發生了 500 錯誤-->查找動態 500.html 頁面-->查找靜態 500.html --> 查找動態 5xx.html-->查找靜態5xx.html。

自定義異常數據

默認情況下,在 Spring Boot 中,所有的異常數據其實就是上文所展示出來的 5 條數據,這 5 條數據定義在 org.springframework.boot.web.reactive.error.DefaultErrorAttributes 類中,具體定義在 getErrorAttributes 方法中 :

public Map<String, Object> getErrorAttributes(ServerRequest request,
        boolean includeStackTrace) {
    Map<String, Object> errorAttributes = new LinkedHashMap<>();
    errorAttributes.put("timestamp", new Date());
    errorAttributes.put("path", request.path());
    Throwable error = getError(request);
    HttpStatus errorStatus = determineHttpStatus(error);
    errorAttributes.put("status", errorStatus.value());
    errorAttributes.put("error", errorStatus.getReasonPhrase());
    errorAttributes.put("message", determineMessage(error));
    handleException(errorAttributes, determineException(error),
includeStackTrace);
    return errorAttributes;
}

DefaultErrorAttributes 類本身則是在

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration 異常自動配置類中定義的,如果開發者沒有自己提供一個 ErrorAttributes 的實例的話,那麼 Spring Boot 將自動提供一個 ErrorAttributes 的實例,也就是 DefaultErrorAttributes 。

基於此 ,開發者自定義 ErrorAttributes 有兩種方式 :

1. 直接實現 ErrorAttributes 接口

2. 繼承 DefaultErrorAttributes(推薦),因為 DefaultErrorAttributes 中對異常數據的處理已經完成,開發者可以直接使用。

具體定義如下:

@Component
public class MyErrorAttributes  extends DefaultErrorAttributes {
  @Override
  public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean
includeStackTrace) {
    Map<String, Object> map = super.getErrorAttributes(webRequest,
includeStackTrace);
    if ((Integer)map.get("status") == 500) {
      map.put("message", "服務器內部錯誤!");
   }
    return map;
 }
}

定義好的 ErrorAttributes 一定要註冊成一個 Bean ,這樣,Spring Boot 就不會使用默認的DefaultErrorAttributes 了,運行效果如下圖:

自定義異常視圖

異常視圖默認就是前面所說的靜態或者動態頁面,這個也是可以自定義的,首先 ,默認的異常視圖加載邏輯在 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 類的errorHtml 方法中,這個方法用來返回異常頁面+數據,還有另外一個 error 方法,這個方法用來返回異常數據(如果是 ajax 請求,則該方法會被觸發)。

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request,
        HttpServletResponse response) {
    HttpStatus status = getStatus(request);
    Map<String, Object> model =
Collections.unmodifiableMap(getErrorAttributes(
            request, isIncludeStackTrace(request,
MediaType.TEXT_HTML)));
    response.setStatus(status.value());
    ModelAndView modelAndView = resolveErrorView(request, response, status,
model);
    return (modelAndView != null) ? modelAndView : new ModelAndView("error",
model);
}

在該方法中 ,首先會通過 getErrorAttributes 方法去獲取異常數據(實際上會調用到 ErrorAttributes的實例 的 getErrorAttributes 方法),然後調用 resolveErrorView 去創建一個 ModelAndView ,如果這裡創建失敗,那麼用戶將會看到默認的錯誤提示頁面。

正常情況下, resolveErrorView 方法會來到 DefaultErrorViewResolver 類的 resolveErrorView 方法中:

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus
status,
        Map<String, Object> model) {
    ModelAndView modelAndView = resolve(String.valueOf(status.value()),
model);
    if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
        modelAndView = resolve(SERIES_VIEWS.get(status.series()),
model);
   }
    return modelAndView;
}

在這裡,首先以異常響應碼作為視圖名分別去查找動態頁面和靜態頁面,如果沒有查找到,則再以 4xx或者 5xx 作為視圖名再去分別查找動態或者靜態頁面。

要自定義異常視圖解析,也很容易 ,由於 DefaultErrorViewResolver 是在ErrorMvcAutoConfiguration 類中提供的實例,即開發者沒有提供相關實例時,會使用默認的DefaultErrorViewResolver ,開發者提供了自己的 ErrorViewResolver 實例後,默認的配置就會失效,因此,自定義異常視圖,只需要提供 一個 ErrorViewResolver 的實例即可:

@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
  public MyErrorViewResolver(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
    super(applicationContext, resourceProperties);
 }
  @Override
  public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus
status, Map<String, Object> model) {
    return new ModelAndView("/aaa/123", model);
 }
}

實際上,開發者也可以在這裡定義異常數據(直接在 resolveErrorView 方法重新定義一個 model ,將參數中的model 數據拷貝過去並修改,注意參數中的 model 類型為 UnmodifiableMap,即不可以直接修改),而不需要自定義 MyErrorAttributes。定義完成後,提供一個名為 123 的視圖,如下圖:

如此之後,錯誤試圖就算定義成功了。

總結

實際上也可以自定義異常控制器 BasicErrorController ,不過鬆哥覺得這樣太大動干戈了,沒必要,前面幾種方式已經可以滿足我們的大部分開發需求了。如果是前後端分離架構,異常處理還有其他一些處理方案,這個以後和大家聊。

篇幅有限,其他內容就不在這裡一一展示了,這份Spring Boot實戰教程已整理成一份PDF文檔,共有200多頁。關注公種浩:程序員追風,回覆 005 獲取這份整理好的Spring Boot實戰教程。

i

最後

歡迎大家一起交流,喜歡文章記得點ge 贊喲,感謝支持!

Leave a Reply

Your email address will not be published. Required fields are marked *