日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

Spring Boot優(yōu)雅地處理404異常問題

瀏覽:18日期:2023-08-01 09:18:05

背景

在使用SpringBoot的過程中,你肯定遇到過404錯誤。比如下面的代碼:

@RestController@RequestMapping(value = '/hello')public class HelloWorldController { @RequestMapping('/test') public Object getObject1(HttpServletRequest request){ Response response = new Response(); response.success('請求成功...'); response.setResponseTime(); return response; }}

當我們使用錯誤的請求地址(POST http://127.0.0.1:8888/hello/test1?id=98)進行請求時,會報下面的錯誤:

{ 'timestamp': '2020-11-19T08:30:48.844+0000', 'status': 404, 'error': 'Not Found', 'message': 'No message available', 'path': '/hello/test1'}

雖然上面的返回很清楚,但是我們的接口需要返回統(tǒng)一的格式,比如:

{ 'rtnCode':'9999', 'rtnMsg':'404 /hello/test1 Not Found'}

這時候你可能會想有Spring的統(tǒng)一異常處理,在Controller類上加@RestControllerAdvice注解。但是這種做法并不能統(tǒng)一處理404錯誤。

404錯誤產(chǎn)生的原因

產(chǎn)生404的原因是我們調(diào)了一個不存在的接口,但是為什么會返回下面的json報錯呢?我們先從Spring的源代碼分析下。

{ 'timestamp': '2020-11-19T08:30:48.844+0000', 'status': 404, 'error': 'Not Found', 'message': 'No message available', 'path': '/hello/test1'}

為了代碼簡單起見,這邊直接從DispatcherServlet的doDispatch方法開始分析。(如果不知道為什么要從這邊開始,你還要熟悉下SpringMVC的源代碼)。

... 省略部分代碼....// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());... 省略部分代碼

Spring MVC會根據(jù)請求URL的不同,配置的RequestMapping的不同,為請求匹配不同的HandlerAdapter。

對于上面的請求地址:http://127.0.0.1:8888/hello/test1?id=98匹配到的HandlerAdapter是HttpRequestHandlerAdapter。

我們直接進入到HttpRequestHandlerAdapter中看下這個類的handle方法。

@Override@Nullablepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null;}

這個方法沒什么內(nèi)容,直接是調(diào)用了HttpRequestHandler類的handleRequest(request, response)方法。所以直接進入這個方法看下吧。

@Overridepublic void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // For very general mappings (e.g. '/') we need to check 404 first Resource resource = getResource(request); if (resource == null) { logger.trace('No matching resource found - returning 404'); // 這個方法很簡單,就是設(shè)置404響應(yīng)碼,然后將Response的errorState狀態(tài)從0設(shè)置成1 response.sendError(HttpServletResponse.SC_NOT_FOUND); // 直接返回 return; } ... 省略部分方法}

這個方法很簡單,就是設(shè)置404響應(yīng)碼,將Response的errorState狀態(tài)從0設(shè)置成1,然后就返回響應(yīng)了。整個過程并沒有發(fā)生任何異常,所以不能觸發(fā)Spring的全局異常處理機制。

到這邊還有一個問題沒有解決:就是下面的404提示信息是怎么返回的。

{ 'timestamp': '2020-11-19T08:30:48.844+0000', 'status': 404, 'error': 'Not Found', 'message': 'No message available', 'path': '/hello/test1'}

我們繼續(xù)往下看。Response響應(yīng)被返回,進入org.apache.catalina.core.StandardHostValve類的invoke方法進行處理。(不要問我為什么知道是在這里?Debug的能力是需要自己摸索出來的,自己調(diào)試多了,你也就會了)

@Overridepublic final void invoke(Request request, Response response) throws IOException, ServletException { Context context = request.getContext(); if (context == null) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, sm.getString('standardHost.noContext')); return; } if (request.isAsyncSupported()) { request.setAsyncSupported(context.getPipeline().isAsyncSupported()); } boolean asyncAtStart = request.isAsync(); boolean asyncDispatching = request.isAsyncDispatching(); try { context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER); if (!asyncAtStart && !context.fireRequestInitEvent(request.getRequest())) { return; } try { if (!asyncAtStart || asyncDispatching) {context.getPipeline().getFirst().invoke(request, response); } else {if (!response.isErrorReportRequired()) { throw new IllegalStateException(sm.getString('standardHost.asyncStateError'));} } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); container.getLogger().error('Exception Processing ' + request.getRequestURI(), t); if (!response.isErrorReportRequired()) {request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);throwable(request, response, t); } } response.setSuspended(false); Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); if (!context.getState().isAvailable()) { return; } // 在這里判斷請求是不是發(fā)生了錯誤,錯誤的話就進入StandardHostValve的status(Request request, Response response)方法。 // Look for (and render if found) an application level error page if (response.isErrorReportRequired()) { if (t != null) {throwable(request, response, t); } else {status(request, response); } } if (!request.isAsync() && !asyncAtStart) { context.fireRequestDestroyEvent(request.getRequest()); } } finally { // Access a session (if present) to update last accessed time, based // on a strict interpretation of the specification if (ACCESS_SESSION) { request.getSession(false); } context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER); } }

這個方法會根據(jù)返回的響應(yīng)判斷是不是發(fā)生了錯了,如果發(fā)生了error,則進入StandardHostValve的status(Request request, Response response)方法。這個方法“兜兜轉(zhuǎn)轉(zhuǎn)”又進入了StandardHostValve的custom(Request request, Response response,ErrorPage errorPage)方法。這個方法中將請求重新forward到了'/error'接口。

private boolean custom(Request request, Response response, ErrorPage errorPage) { if (container.getLogger().isDebugEnabled()) { container.getLogger().debug('Processing ' + errorPage); } try { // Forward control to the specified location ServletContext servletContext =request.getContext().getServletContext(); RequestDispatcher rd =servletContext.getRequestDispatcher(errorPage.getLocation()); if (rd == null) {container.getLogger().error( sm.getString('standardHostValue.customStatusFailed', errorPage.getLocation()));return false; } if (response.isCommitted()) {rd.include(request.getRequest(), response.getResponse()); } else {// Reset the response (keeping the real error code and message)response.resetBuffer(true);response.setContentLength(-1);// 1: 重新forward請求到/error接口rd.forward(request.getRequest(), response.getResponse());response.setSuspended(false); } return true; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); container.getLogger().error('Exception Processing ' + errorPage, t); return false; } }

上面標號1處的代碼重新將請求forward到了/error接口。所以如果我們開著Debug日志的話,你會在后臺看到下面的日志。

[http-nio-8888-exec-7] DEBUG org.springframework.web.servlet.DispatcherServlet:891 - DispatcherServlet with name ’dispatcherServlet’ processing POST request for [/error]2020-11-19 19:04:04.280 [http-nio-8888-exec-7] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping:313 - Looking up handler method for path /error2020-11-19 19:04:04.281 [http-nio-8888-exec-7] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping:320 - Returning handler method [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]2020-11-19 19:04:04.281 [http-nio-8888-exec-7] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory:255 - Returning cached instance of singleton bean ’basicErrorController’

上面是/error的請求日志。到這邊還是沒說明為什么能返回json格式的404返回格式。我們繼續(xù)往下看。

到這邊為止,我們好像沒有任何線索了。但是如果仔細看上面日志的話,你會發(fā)現(xiàn)這個接口的處理方法是:

org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]

我們打開BasicErrorController這個類的源代碼,一切豁然開朗。

@Controller@RequestMapping('${server.error.path:${error.path:/error}}')public class BasicErrorController extends AbstractErrorController { @RequestMapping(produces = 'text/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 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); } ... 省略部分方法}

BasicErrorController是Spring默認配置的一個Controller,默認處理/error請求。BasicErrorController提供兩種返回錯誤一種是頁面返回、當你是頁面請求的時候就會返回頁面,另外一種是json請求的時候就會返回json錯誤。

自定義404錯誤處理類

我們先看下BasicErrorController是在哪里進行配置的。

在IDEA中,查看BasicErrorController的usage,我們發(fā)現(xiàn)這個類是在ErrorMvcAutoConfiguration中自動配置的。

@Configuration@ConditionalOnWebApplication(type = Type.SERVLET)@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })// Load before the main WebMvcAutoConfiguration so that the error View is available@AutoConfigureBefore(WebMvcAutoConfiguration.class)@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })public class ErrorMvcAutoConfiguration { @Bean@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {return new BasicErrorController(errorAttributes, this.serverProperties.getError(),this.errorViewResolvers);}... 省略部分代碼}

從上面的配置中可以看出來,只要我們自己配置一個ErrorController,就可以覆蓋掉BasicErrorController的行為。

@Controller@RequestMapping('${server.error.path:${error.path:/error}}')public class CustomErrorController extends BasicErrorController { @Value('${server.error.path:${error.path:/error}}') private String path; public CustomErrorController(ServerProperties serverProperties) { super(new DefaultErrorAttributes(), serverProperties.getError()); } /** * 覆蓋默認的JSON響應(yīng) */ @Override public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); Map<String, Object> map = new HashMap<String, Object>(16); Map<String, Object> originalMsgMap = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); String path = (String)originalMsgMap.get('path'); String error = (String)originalMsgMap.get('error'); String message = (String)originalMsgMap.get('message'); StringJoiner joiner = new StringJoiner(',','[',']'); joiner.add(path).add(error).add(message); map.put('rtnCode', '9999'); map.put('rtnMsg', joiner.toString()); return new ResponseEntity<Map<String, Object>>(map, status); } /** * 覆蓋默認的HTML響應(yīng) */ @Override public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { //請求的狀態(tài) HttpStatus status = getStatus(request); response.setStatus(getStatus(request).value()); Map<String, Object> model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML)); ModelAndView modelAndView = resolveErrorView(request, response, status, model); //指定自定義的視圖 return (modelAndView == null ? new ModelAndView('error', model) : modelAndView); }}

默認的錯誤路徑是/error,我們可以通過以下配置進行覆蓋:

server: error: path: /xxx

更詳細的內(nèi)容請參考Spring Boot的章節(jié)。

簡單總結(jié)#

如果在過濾器(Filter)中發(fā)生異常,或者調(diào)用的接口不存在,Spring會直接將Response的errorStatus狀態(tài)設(shè)置成1,將http響應(yīng)碼設(shè)置為500或者404,Tomcat檢測到errorStatus為1時,會將請求重現(xiàn)forward到/error接口; 如果請求已經(jīng)進入了Controller的處理方法,這時發(fā)生了異常,如果沒有配置Spring的全局異常機制,那么請求還是會被forward到/error接口,如果配置了全局異常處理,Controller中的異常會被捕獲; 繼承BasicErrorController就可以覆蓋原有的錯誤處理方式。

到此這篇關(guān)于Spring Boot優(yōu)雅地處理404異常的文章就介紹到這了,更多相關(guān)Spring Boot 404異常內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標簽: Spring
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
日韩高清欧美激情| 蜜臀av性久久久久蜜臀aⅴ流畅| 亚洲成人一区| 蜜臀国产一区| 色天使综合视频| 欧美91福利在线观看| 麻豆精品蜜桃| 亚洲成人日韩| 亚洲一区亚洲| 日韩中文字幕一区二区三区| 丝袜美腿亚洲色图| 亚洲精品免费观看| 日本少妇一区二区| 欧美一级二区| 婷婷五月色综合香五月| 亚洲免费毛片| 国产精品久久久久久久久久白浆| 国产精品久久久久久久久免费高清 | 欧美欧美黄在线二区| 日本不卡中文字幕| 欧美日韩a区| 美女久久久精品| 天堂日韩电影| 一区在线免费观看| 亚洲在线成人| 欧美一区成人| 国产69精品久久| 激情欧美国产欧美| 一区久久精品| 91九色综合| 国产盗摄——sm在线视频| 天堂资源在线亚洲| 亚洲视频电影在线| 捆绑调教美女网站视频一区 | 亚洲精品一区三区三区在线观看| 亚洲激情久久| 亚洲一二三区视频| 国产精品亲子伦av一区二区三区 | 欧美日韩99| 都市激情国产精品| 99国产精品久久久久久久 | 日韩中文字幕一区二区三区| 日韩国产欧美三级| 捆绑调教美女网站视频一区| 日韩精品首页| 天堂va在线高清一区| 亚洲激情久久| 国产精品丝袜在线播放| 久久久精品网| 综合一区在线| 国产一区国产二区国产三区 | 不卡一区综合视频| 日韩精品亚洲专区| 激情黄产视频在线免费观看| 99riav1国产精品视频| 国产无遮挡裸体免费久久| 肉色欧美久久久久久久免费看| 亚洲一区二区免费看| 国产极品嫩模在线观看91精品| 日韩成人三级| 免费人成黄页网站在线一区二区| 国产精品亚洲欧美日韩一区在线| 91精品一区二区三区综合在线爱 | 久久美女性网| 国产欧美激情| 91超碰国产精品| 久久不见久久见免费视频7| 日韩成人亚洲| 国产欧美日韩一区二区三区四区| 婷婷激情一区| 欧美日一区二区三区在线观看国产免 | 三级一区在线视频先锋| 精品国产a一区二区三区v免费| 亚洲欧美日本国产专区一区| 久久精品女人| 亚洲人成亚洲精品| 亚洲天堂1区| 麻豆国产91在线播放| 日韩精品一二三四| 欧美粗暴jizz性欧美20| 老司机精品视频网| 亚洲欧洲日韩| 国产综合色产| 91嫩草亚洲精品| 日本欧美一区二区| 免播放器亚洲| 日韩不卡视频在线观看| 国产高清视频一区二区| 欧美精品激情| 国产中文在线播放| **爰片久久毛片| 免费日本视频一区| 伊人久久大香线蕉av不卡| 精品国产一区二区三区噜噜噜| 美女被久久久| 伊人久久视频| 精品丝袜久久| 国产精品一区高清| 视频一区日韩精品| 亚洲一卡久久| 国产伊人精品| 日本а中文在线天堂| 国产伦理久久久久久妇女| 男女性色大片免费观看一区二区| 欧美日韩精品免费观看视完整 | 悠悠资源网久久精品| 欧美日韩激情| 免费在线亚洲| 欧美精品91| 91九色综合| 亚洲精品在线a| 丝袜美腿亚洲一区| 美女久久网站| 日韩中文欧美在线| 久久av在线| 日韩亚洲在线| 国产高清一区| 午夜久久美女| 国产精品色网| 午夜在线精品偷拍| 黄色av日韩| 亚洲欧美日韩专区| 国产精品普通话对白| 狠狠色综合网| 亚洲精华国产欧美| 亚洲欧美久久久| 在线国产日韩| 亚洲免费观看高清完整版在线观| 在线看片日韩| 日韩不卡手机在线v区| 91国内精品| 麻豆国产精品一区二区三区| 国产一区二区三区四区| 色欧美自拍视频| 日韩免费视频| 91精品国产成人观看| 激情五月色综合国产精品| 欧美搞黄网站| 91精品电影| 蜜臀91精品一区二区三区| 亚洲毛片网站| 国产精品亚洲欧美日韩一区在线 | 香蕉精品久久| 欧美成人午夜| 亚洲一区二区av| 久久精品72免费观看| 国产精品s色| 日韩精品专区| 亚洲一区久久| 久久国产精品色av免费看| 国产精品探花在线观看| 国产a久久精品一区二区三区| 精品三级久久| 中国女人久久久| 日本aⅴ免费视频一区二区三区| 久久福利精品| 欧美日韩一区自拍| 国产在线看片免费视频在线观看| 亚洲高清av| 免费高清在线一区| 国产伦理久久久久久妇女| 日本一区二区高清不卡| 亚洲精品一区二区在线看| 日韩av一区二区三区| 国产一区二区三区视频在线| 色婷婷亚洲mv天堂mv在影片| 欧美/亚洲一区| 深夜福利一区| 精品一区av| 99xxxx成人网| 久久福利在线| 欧洲激情综合| 国产乱子精品一区二区在线观看 | 亚洲国产成人二区| 午夜国产精品视频| 久久国产三级| 国产高潮在线| 丝袜美腿成人在线| 精品一区二区三区在线观看视频| 激情视频一区二区三区| 日韩高清二区| 日韩成人高清| 日韩一二三区在线观看| 国产中文字幕一区二区三区| 最新亚洲一区| 美女视频免费精品| 久久福利精品| 超碰在线99| 免费视频最近日韩| 超级白嫩亚洲国产第一| 亚洲美女91| 亚洲成av人片一区二区密柚| 日本免费新一区视频| 久久久久久免费视频| 欧美日韩在线精品一区二区三区激情综合| 91精品韩国| 一区免费在线| 伊伊综合在线| 国产精品最新| 视频在线在亚洲|