天天看點

SpringBoot系列(六)--- Web開發(三)錯誤處理機制配置嵌入式Servlet容器使用外置的Servlet容器

文章目錄

  • 錯誤處理機制
    • SpringBoot預設的錯誤處理機制
    • 如何定制錯誤響應:
      • 如何定制錯誤的頁面
      • 如何定制錯誤的json資料
  • 配置嵌入式Servlet容器
    • 如何定制和修改Servlet容器的相關配置
    • 注冊Servlet三大元件 Servlet、Filter、Listener
    • 更換其他嵌入式Servlet容器
      • Jetty
      • Undertow
  • 使用外置的Servlet容器

項目位址:

連結:https://pan.baidu.com/s/15qQUTPeQ4mpg59Q_RgA6bQ

提取碼:re3p

複制這段内容後打開百度網盤手機App,操作更友善哦

錯誤處理機制

SpringBoot預設的錯誤處理機制

  • 把攔截器關掉,直接在主配置檔案注釋掉注入就可以直接關掉了
  • 然後我們現在通過浏覽器随便通路一個不存在的連接配接,會出現下面這樣的錯誤提示。
    SpringBoot系列(六)--- Web開發(三)錯誤處理機制配置嵌入式Servlet容器使用外置的Servlet容器
  • 我們可以檢視一下浏覽器發送請求的請求頭,如下:
    SpringBoot系列(六)--- Web開發(三)錯誤處理機制配置嵌入式Servlet容器使用外置的Servlet容器
  • SpringBoot還另外規定了用戶端通路無效連結的錯誤機制,如果我們通過用戶端使用PostMan通路會傳回一個預設的json資料:
{
    "timestamp": "2021-02-26T06:42:41.611+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "",
    "path": "/cz/aaa"
}
           
  • 原理:
  • 可以參照

    ErrorMvcAutoConfiguration

    ;錯誤處理的自動配置;
  • 給容器中添加了以下元件
    • DefaultErrorAttributes

幫我們在頁面共享資訊;
@Override
	public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, requestAttributes);
		addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
		addPath(errorAttributes, requestAttributes);
		return errorAttributes;
	}
           
  • BasicErrorController

    :處理預設/error請求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    
    @RequestMapping(produces = "text/html")//産生html類型的資料;浏覽器發送的請求來到這個方法處理
	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 ? new ModelAndView("error", model) : modelAndView);
	}

	@RequestMapping
	@ResponseBody    //産生json資料,其他用戶端來到這個方法處理;
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<Map<String, Object>>(body, status);
	}
           
  • ErrorPageCustomizer

@Value("${error.path:/error}")
	private String path = "/error";  系統出現錯誤以後來到error請求進行處理;(web.xml注冊的錯誤頁面規則)
           

​ 4、DefaultErrorViewResolver:

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

	private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //預設SpringBoot可以去找到一個頁面?  error/404
		String errorViewName = "error/" + viewName;
        
        //模闆引擎可以解析這個頁面位址就用模闆引擎解析
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
				.getProvider(errorViewName, this.applicationContext);
		if (provider != null) {
            //模闆引擎可用的情況下傳回到errorViewName指定的視圖位址
			return new ModelAndView(errorViewName, model);
		}
        //模闆引擎不可用,就在靜态資源檔案夾下找errorViewName對應的頁面   error/404.html
		return resolveResource(errorViewName, model);
	}
           
  • 步驟:
  • 一但系統出現4xx或者5xx之類的錯誤;ErrorPageCustomizer就會生效(定制錯誤的響應規則);就會來到/error請求;就會被BasicErrorController處理;

​ 1)響應頁面;去哪個頁面是由DefaultErrorViewResolver解析得到的;

protected ModelAndView resolveErrorView(HttpServletRequest request,
      HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
    //所有的ErrorViewResolver得到ModelAndView
   for (ErrorViewResolver resolver : this.errorViewResolvers) {
      ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
      if (modelAndView != null) {
         return modelAndView;
      }
   }
   return null;
}
           

如何定制錯誤響應:

如何定制錯誤的頁面

  1. 有模闆引擎的情況下;error/狀态碼; 【将錯誤頁面命名為 錯誤狀态碼.html 放在模闆引擎檔案夾裡面的 error檔案夾下】,發生此狀态碼的錯誤就會來到 對應的頁面;

我們可以使用4xx和5xx作為錯誤頁面的檔案名來比對這種類型的所有錯誤,精确優先(優先尋找精确的狀态碼.html);

SpringBoot系列(六)--- Web開發(三)錯誤處理機制配置嵌入式Servlet容器使用外置的Servlet容器
  • 頁面能擷取的資訊;
    • ​ timestamp:時間戳
    • status:狀态碼
    • error:錯誤提示
    • exception:異常對象
    • message:異常消息
    • errors:JSR303資料校驗的錯誤都在這裡
  1. 沒有模闆引擎(模闆引擎找不到這個錯誤頁面),靜态資源檔案夾下找;
  2. 以上都沒有錯誤頁面,就是預設來到SpringBoot預設的錯誤提示頁面;
SpringBoot系列(六)--- Web開發(三)錯誤處理機制配置嵌入式Servlet容器使用外置的Servlet容器
SpringBoot系列(六)--- Web開發(三)錯誤處理機制配置嵌入式Servlet容器使用外置的Servlet容器

如何定制錯誤的json資料

  • 自定義異常處理&傳回定制json資料;轉發到/error進行自适應響應效果處理
@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        //傳入我們自己的錯誤狀态碼  4xx 5xx
        /**
         * Integer statusCode = (Integer) request
         .getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code","user.notexist");
        map.put("message","使用者出錯啦");

        request.setAttribute("ext",map);
        //轉發到/error
        return "forward:/error";
    }
}

           

将我們的定制資料攜帶出去;

  • 出現錯誤以後,會來到/error請求,會被

    BasicErrorController

    處理,響應出去可以擷取的資料是由

    getErrorAttributes

    得到的(是

    AbstractErrorController(ErrorController)

    規定的方法);
  1. 編寫一個

    ErrorController

    的實作類【或者是編寫

    AbstractErrorController

    的子類】,放在容器中;
  2. 頁面上能用的資料,或者是json傳回能用的資料都是通過

    errorAttributes.getErrorAttributes

    得到;容器中

    DefaultErrorAttributes.getErrorAttributes();

    預設進行資料處理的;
  • 自定義ErrorAttributes
//給容器中加入我們自己定義的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    //傳回值的map就是頁面和json能擷取的所有字段
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes((WebRequest) requestAttributes, includeStackTrace);
        map.put("person","cz");

        //我們的異常處理器攜帶的資料
        Map<String,Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
        map.put("ext",ext);
        return map;
    }
}

           

最終的效果:響應是自适應的,可以通過定制ErrorAttributes改變需要傳回的内容。就不在隻有原先的預設屬性了,将我們傳回的資訊就會是如下:

  • timestamp:時間戳
  • tstatus:狀态碼
  • terror:錯誤提示
  • texception:異常對象
  • tmessage:異常消息
  • terrors:JSR303資料校驗的錯誤都在這裡
  • author: cz

配置嵌入式Servlet容器

  • SpringBoot預設使用Tomcat作為嵌入式的Servlet容器;
SpringBoot系列(六)--- Web開發(三)錯誤處理機制配置嵌入式Servlet容器使用外置的Servlet容器

如何定制和修改Servlet容器的相關配置

  • 修改和server有關的配置(ServerProperties即也是使用

    WebServerFactoryCustomizer

    ),在主配置檔案中可以通過這樣的形式來設定
server.port=8081
server.context-path=/cz

server.tomcat.uri-encoding=UTF-8

//通用的Servlet容器設定
server.xxx
//Tomcat的設定
server.tomcat.xxx
           
  • 除了在主配置檔案中進行相關的設定之外,我們還可以通過注冊

    WebServerFactoryCustomizer

    元件來在類中自定義配置,也就是嵌入式的Servlet容器的定制器,中修改Servlet容器的配置
@Bean  //一定要将這個定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
    return new EmbeddedServletContainerCustomizer() {

        //定制嵌入式的Servlet容器相關的規則
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            container.setPort(8083);
        }
    };
}
           

注冊Servlet三大元件 Servlet、Filter、Listener

Servlet三大元件分别是

Servlet、Filter、Listener

,如果我們原先熟悉SpringMVC開發的應該知道,我們在Webapp下面的web.xml中,經常需要配置這三大元件用來過濾監聽相關的請求,而在SpringBoot中,由于SpringBoot預設是以

jar

包的方式啟動嵌入式的Servlet容器來啟動SpringBoot的web應用,是以并沒有沒有

web.xml

檔案。但是我們依舊可以通過SpringBoot特有的相關注冊Bean進行注冊,分别是:

  • ServletRegistrationBean

  • 編寫MyServlet
public class MyServlet extends HttpServlet {

    //處理get請求
    public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
        doPost(request,response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
        response.getWriter().write("hello Servlet");
    }
}
           
  • 在MyServerConfig中編寫如下:
@Configuration
public class MyServerConfig {

    //注冊三大元件
    @Bean
    public ServletRegistrationBean myServlet(){
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
        //啟動順序
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }
 //配置嵌入式的Servlet容器
    @Bean//一定要将這個定制器加入到容器中
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
            //定制嵌入式的Servlet容器相關的規則
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                factory.setPort(8083);
            }
        };
    }
}
           
  • FilterRegistrationBean

  • 編寫MyFilter
public class MyFilter 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 success....");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}
           
  • 在MyServerConfig中編寫如下:
@Bean
    public FilterRegistrationBean myFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new MyFilter());
        registrationBean.setUrlPatterns(Arrays.asList("/hello","/"));
        return registrationBean;
    }
           
  • ServletListenerRegistrationBean

  • 編寫MyListener
public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized...web應用啟動");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed...目前web項目銷毀");
    }
}

           
  • 在MyServerConfig中編寫如下:
@Bean
    public ServletListenerRegistrationBean myListener(){
        ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
        return registrationBean;
    }
           
SpringBoot幫我們自動配置SpringMVC的時候,會自動的注冊SpringMVC的前端控制器,即

DIspatcherServlet

,我們通過查閱

DispatcherServletAutoConfiguration

發現,會預設攔截,

“/”

的所有請求,包括靜态資源,但是不攔截jsp請求,

/*

會攔截jsp,可以通過

server.servletPath

來修改SpringMVC前端控制器預設攔截的請求路徑

更換其他嵌入式Servlet容器

SpringBoot是預設支援Tomcat的,也就是在pom.xml中通過如下依賴引入的

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <!--     引入web子產品預設就是使用嵌入式的Tomcat作為Servlet容器;-->
</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>

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

Undertow

<!-- 引入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>

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

使用外置的Servlet容器

在我們之前學習的SpringBoot應用,是通過嵌入式Servlet容器,應用打成可執行的jar,這樣做的優點顯而易見,就是簡單、便攜,但是缺點就是預設不支援JSP、優化定制比較複雜(使用定制器ServerProperties、自定義WebServerFactoryCustomizer,自己編寫嵌入式Servlet容器的建立工廠ConfigurableWebServerFactory);我們可以使用外置的Servlet容器,也就是外面安裝Tomcat,然後應用

war

包的方式打包。

步驟:

  • 必須建立一個war項目,利用idea建立好目錄結構
    SpringBoot系列(六)--- Web開發(三)錯誤處理機制配置嵌入式Servlet容器使用外置的Servlet容器
  • 将嵌入式的Tomcat指定為provided;
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-tomcat</artifactId>
   <scope>provided</scope>
</dependency>

           
  • 編寫一個

    SpringBootServletInitializer

    的子類,并調用

    configure

    方法
public class ServletInitializer extends SpringBootServletInitializer {

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

}
           
  • 啟動伺服器就可以使用;

jar包和war包啟動的差別

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

war包:啟動伺服器,伺服器啟動SpringBoot應用通過

SpringBootServletInitializer

,啟動ioc容器;