文章目錄
- 錯誤處理機制
-
- 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;
}
-
:處理預設/error請求BasicErrorController
@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;
}
如何定制錯誤響應:
如何定制錯誤的頁面
- 有模闆引擎的情況下;error/狀态碼; 【将錯誤頁面命名為 錯誤狀态碼.html 放在模闆引擎檔案夾裡面的 error檔案夾下】,發生此狀态碼的錯誤就會來到 對應的頁面;
我們可以使用4xx和5xx作為錯誤頁面的檔案名來比對這種類型的所有錯誤,精确優先(優先尋找精确的狀态碼.html);
- 頁面能擷取的資訊;
- timestamp:時間戳
- status:狀态碼
- error:錯誤提示
- exception:異常對象
- message:異常消息
- errors:JSR303資料校驗的錯誤都在這裡
- 沒有模闆引擎(模闆引擎找不到這個錯誤頁面),靜态資源檔案夾下找;
- 以上都沒有錯誤頁面,就是預設來到SpringBoot預設的錯誤提示頁面;
如何定制錯誤的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)
- 編寫一個
的實作類【或者是編寫ErrorController
的子類】,放在容器中;AbstractErrorController
- 頁面上能用的資料,或者是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容器;
如何定制和修改Servlet容器的相關配置
- 修改和server有關的配置(ServerProperties即也是使用
),在主配置檔案中可以通過這樣的形式來設定WebServerFactoryCustomizer
server.port=8081
server.context-path=/cz
server.tomcat.uri-encoding=UTF-8
//通用的Servlet容器設定
server.xxx
//Tomcat的設定
server.tomcat.xxx
- 除了在主配置檔案中進行相關的設定之外,我們還可以通過注冊
元件來在類中自定義配置,也就是嵌入式的Servlet容器的定制器,中修改Servlet容器的配置WebServerFactoryCustomizer
@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,可以通過
/*
來修改SpringMVC前端控制器預設攔截的請求路徑
server.servletPath
更換其他嵌入式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應用通過
,啟動ioc容器;
SpringBootServletInitializer