javascript
springboot转发http请求_网易后端实习生分享:Springboot异常和错误处理规范
由于錯(cuò)誤在所難免,異常處理已經(jīng)成為開(kāi)發(fā)工作中不可或缺的部分。
在web開(kāi)發(fā)中,我們通常不希望用戶看到一個(gè)寫(xiě)滿StackTrace的錯(cuò)誤頁(yè)面;同時(shí),我們希望出現(xiàn)錯(cuò)誤或發(fā)生異常時(shí),開(kāi)發(fā)運(yùn)維人員可以看到詳細(xì)的錯(cuò)誤信息,以便進(jìn)行查錯(cuò)和DEBUG。
所以,在開(kāi)發(fā)過(guò)程中,應(yīng)重視異常處理。在進(jìn)行業(yè)務(wù)邏輯開(kāi)發(fā)之前,就應(yīng)該定義好自己的異常處理流程。
1. 異常處理流程概述
異常處理的對(duì)象分為兩類(lèi):
Springboot會(huì)統(tǒng)一處理第1種錯(cuò)誤,由ErrorController捕獲并進(jìn)行處理,根據(jù)請(qǐng)求的Accpet字段返回錯(cuò)誤頁(yè)面或json數(shù)據(jù)。這里貼出Springboot自己的BasicErrorController實(shí)現(xiàn):
@Controllerpublic class BasicErrorController implements ErrorController { @Value("${error.path:/error}") private String errorPath; private final ErrorAttributes errorAttributes; public BasicErrorController(ErrorAttributes errorAttributes) { Assert.notNull(errorAttributes, "ErrorAttributes must not be null"); this.errorAttributes = errorAttributes; } public String getErrorPath() { return this.errorPath; } @RequestMapping( value = {"${error.path:/error}"}, produces = {"text/html"} ) public ModelAndView errorHtml(HttpServletRequest request) { return new ModelAndView("error", this.getErrorAttributes(request, false)); } @RequestMapping({"${error.path:/error}"}) @ResponseBody public ResponseEntity> error(HttpServletRequest request) { Map body = this.getErrorAttributes(request, this.getTraceParameter(request)); HttpStatus status = this.getStatus(request); return new ResponseEntity(body, status); } //……}也可以自己寫(xiě)一個(gè)ErrorController返回自定義頁(yè)面。
對(duì)于程序中發(fā)送的異常,可以手動(dòng)進(jìn)行捕獲。如果沒(méi)有手動(dòng)捕獲或有所遺漏,Springboot提供了@ExceptionHandler(value={})對(duì)某種類(lèi)型的異常統(tǒng)一進(jìn)行處理。通常,可以通過(guò)轉(zhuǎn)發(fā)(forward)或重定向(redirect)方式將該異常轉(zhuǎn)給自己定制的ExceptionController進(jìn)行處理。ExceptionController可以根據(jù)請(qǐng)求類(lèi)型返回錯(cuò)誤頁(yè)面或json數(shù)據(jù)。
2. 自定義RuntimeException
可以定制自己的Exception進(jìn)行異常信息記錄:
public abstract class BaseRuntimeException extends RuntimeException { private static final long serialVersionUID = -1842796916322056555L; public BaseRuntimeException() { super(); } public BaseRuntimeException(String message) { super(message); } public BaseRuntimeException(String message, Throwable cause) { super(message, cause); } /** * 返回異常的錯(cuò)誤碼 */ public abstract Integer getErrorCode(); /** * 返回異常的描述(不帶應(yīng)用前綴) */ public abstract String getErrorMessage(); /** * 返回異常的日志(帶應(yīng)用前綴) */ public abstract String getErrorLog();}然后再Service層定義該服務(wù)模塊異常信息。
public class MiscServiceException extends BaseRuntimeException implements IReThrowException { private static final long serialVersionUID = -1844670008823631700L; private MiscErrorCode miscErrorCode = MiscErrorCode.SUCCESS; private String errorLog = MiscErrorCode.SUCCESS.getDesc(); public MiscServiceException() { super(); } public MiscServiceException(String message, Throwable cause) { super(message, cause); this.errorLog = message; } public MiscServiceException(String message) { super(message); this.errorLog = message; } public MiscServiceException(String message, MiscErrorCode miscErrorCode) { super(message); this.miscErrorCode = miscErrorCode; this.errorLog = message; } /** * 返回異常的錯(cuò)誤碼.* 方便日志查看追蹤. * * @see BaseRuntimeException#getErrorCode() */ @Override public Integer getErrorCode() { return miscErrorCode.getValue(); } /** * 返回異常的描述.
* 可直接用于前端錯(cuò)誤提示. * * @see BaseRuntimeException#getErrorMessage() */ @Override public String getErrorMessage() { return miscErrorCode.getDesc(); } /** * 返回異常的日志.
* 用于服務(wù)器日志打印. * * @see BaseRuntimeException#getErrorLog() */ @Override public String getErrorLog() { return errorLog; }}
在Service層生成異常對(duì)象并拋出。
if (…) throw new MiscServiceException(“l(fā)og message…”, MiscErrorCode.XXXERRIR);在Manager層繼續(xù)向上拋出。
public interface SomeService { DTO someFunc() throws MiscServiceException { //…}在Controller層捕獲異常,進(jìn)行處理——返回相關(guān)頁(yè)面。
try {//…} catch (MiscServiceException e) {log.error(e.getErrorLog());return ResponseView.fail(e.getErrorCode(), e.getErrorMessage());}如此以來(lái),代碼中定義的異常和錯(cuò)誤均可以捕捉。
由于BaseRuntimeException是一種RuntimeException,Mananger層聲明方法是不加throws Exception也可以通過(guò)編譯。小猿建議每一個(gè)Manager的方法都加上throws Exception聲明。另外,BaseRuntimeException實(shí)際上也可以直接繼承Exception,這樣編譯器會(huì)強(qiáng)制要求對(duì)其進(jìn)行異常進(jìn)行處理。
3. @ExceptionHandler
上述方案解決了一部分自定義異常。對(duì)于其他的自己未定義的Runtime Exception,例如Null Pointer Exception,Springboot提供了ExceptionHandler,用于捕獲所有代碼中沒(méi)有主動(dòng)catch的異常。通常,我們?cè)摦惓^D(zhuǎn)發(fā)(forward)或重定向(redirect)至某個(gè)自定義的Controller進(jìn)行處理。
轉(zhuǎn)發(fā)和重定向的效果不同:在瀏覽器端,轉(zhuǎn)發(fā)的請(qǐng)求,頁(yè)面不會(huì)跳轉(zhuǎn),URL不會(huì)改變;重定向的請(qǐng)求,URL會(huì)改變。重定向?qū)嶋H上是瀏覽器進(jìn)行了兩次請(qǐng)求。對(duì)于參數(shù)傳遞,兩種方式采取的方法也不同。重定向方式可以使用RedirectAttributes傳遞參數(shù),轉(zhuǎn)發(fā)方式則將參數(shù)放置在Request的Attributes中。
轉(zhuǎn)發(fā):
@ControllerAdvicepublic class CustomExceptionHandler { /** * 將異常頁(yè)轉(zhuǎn)發(fā)到錯(cuò)誤頁(yè) */ @ExceptionHandler(Exception.class) public ModelAndView handleError(HttpServletRequest req, Exception ex) { log.info("Exception e : " + ex, ex); if (BaseRuntimeException.class.isAssignableFrom(ex.getClass())) { BaseRuntimeException e = (BaseRuntimeException) ex; req.setAttribute("code", e.getErrorCode()); req.setAttribute("message", e.getErrorMessage()); } else { req.setAttribute("code", FrontCode.OTHER_ERROR.getCode()); req.setAttribute("message", FrontCode.OTHER_ERROR.getDesc()); } return new ModelAndView("forward:/exception"); }}重定向:
/** * 將異常頁(yè)使用重定向方式到錯(cuò)誤頁(yè) * * @param req * @param ex * @param mode * @return */@ExceptionHandler(Exception.class) public ModelAndView handleError(HttpServletRequest req, Exception ex ,RedirectAttributes mode) { log.info("Exception e : " + ex,ex); ModelAndView mav = new ModelAndView(); if (BaseRuntimeException.class.isAssignableFrom(ex.getClass())) { BaseRuntimeException e = (BaseRuntimeException) ex; mode.addAttribute("code",e.getErrorCode()); mode.addAttribute("message",e.getErrorMessage()); } else { mode.addAttribute("code", FrontCode.OTHER_ERROR.getCode()); mode.addAttribute("message", FrontCode.OTHER_ERROR.getDesc()); } return new ModelAndView("redirect:/exception");}4. ErrorController
在下文貼出的示例中,我們將異常處理的Controller也放在ErrorController中。其中,errorHtml方法同于對(duì)沒(méi)有經(jīng)過(guò)Controller層的錯(cuò)誤進(jìn)行處理,返回自定義錯(cuò)誤頁(yè);exception和exceptionHtml方法負(fù)責(zé)接收ExceptionHandler轉(zhuǎn)發(fā)或重定向的異常處理流,根據(jù)produces的類(lèi)型是”json/application”還是“text/html”,分別返回json和錯(cuò)誤頁(yè)面。
@Controllerpublic class CommonErrorController implements ErrorController { @Autowired private UserSecurityHelper userSecurityHelper; private static final String ERROR_PATH = "error"; private static final String EXCEPTION_PATH = "exception"; @RequestMapping(value = ERROR_PATH) public ModelAndView errorHtml(HttpServletRequest request) { ModelAndView modelAndView = new ModelAndView("/error/error"); Object statusCode = request.getAttribute("javax.servlet.error.status_code"); //當(dāng)請(qǐng)求的錯(cuò)誤類(lèi)型非404、403、402、401時(shí),返回500的錯(cuò)誤頁(yè)面 if (statusCode == null || (!statusCode.equals(HttpStatus.NOT_FOUND.value()) && !statusCode.equals(HttpStatus.UNAUTHORIZED.value()) && !statusCode.equals(HttpStatus.PAYMENT_REQUIRED.value()) && !statusCode .equals(HttpStatus.FORBIDDEN.value()))) { statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value(); } modelAndView.addObject("code", statusCode); modelAndView.addObject("message", "你很神,找到了不存在的頁(yè)面。"); return modelAndView; } /* * 使用forward轉(zhuǎn)發(fā). */ @RequestMapping(value = EXCEPTION_PATH, produces = "application/json") @ResponseBody public ResponseEntity exception(HttpServletRequest request) { Integer code = (Integer) request.getAttribute("code"); String message = (String) request.getAttribute("message"); return ResponseView.fail(code, message); } @RequestMapping(value = EXCEPTION_PATH, produces = {"text/html"}) public ModelAndView exceptionHtml(HttpServletRequest request) { EduWebUser eduWebUser = userSecurityHelper.getEduWebUser(); ModelAndView mav = new ModelAndView("/error/error"); mav.addObject("code", (Integer) request.getAttribute("code")); mav.addObject("message", (String) request.getAttribute("message")); mav.addObject("webUser", eduWebUser); return mav; } @Override public String getErrorPath() { return ERROR_PATH; }}如果使用Redirect跳轉(zhuǎn),ErrorController中接收參數(shù)的相應(yīng)代碼要隨之改變。
/*使用Redirect跳轉(zhuǎn)*/@RequestMapping(value = EXCEPTION_PATH, produces = "application/json")@ResponseBodypublic ResponseEntity exception(HttpServletRequest request , @RequestParam(required = false) String message, @RequestParam(required = false) Integer code) { Map body = new HashMap<>(4); body.put("code", code); body.put("message", message); return new ResponseEntity(body, HttpStatus.OK);}@RequestMapping(value = EXCEPTION_PATH, produces = { "text/html"})public ModelAndView exceptionHtml(HttpServletRequest request , @RequestParam(required = false) String message, @RequestParam(required = false) Integer code) { ModelAndView mav = new ModelAndView("/error/error"); EduWebUser eduWebUser = userSecurityHelper.getEduWebUser(); mav.addObject("code", code); mav.addObject("message", message); mav.addObject("webUser", eduWebUser); return mav;}5. 測(cè)試
我們定義了專(zhuān)門(mén)用于測(cè)試的Service和Controller。其中,throw測(cè)試程序中代碼捕獲異常,silent測(cè)試由ExceptionHandler捕獲的異常。
public interface ExceptionService { public void testThrowException() throws MiscServiceException; public void testSilentException();}@Service("exceptionService")public class ExceptionServiceImpl implements ExceptionService { @Override public void testThrowException() throws MiscServiceException { throw new ForumException("Log Message"); } @Override public void testSilentException() { throw new ForumException("Log Message"); }}@RestController@RequestMapping("/exception")public class ExceptionController { @Resource private ExceptionService exceptionService; @RequestMapping("/throw") public ResponseEntity testThrow() { try { exceptionService.testThrowException(); return ResponseView.success(null); } catch (MiscServiceException e) { e.printStackTrace(); return ResponseView.fail(e.getErrorCode(), e.getErrorMessage()); } } @RequestMapping("/silent") public ResponseEntity testSilent() { exceptionService.testSilentException(); return ResponseView.success(null); }}測(cè)試記錄如下。
代碼主動(dòng)捕獲異常:
Springboot通過(guò)forward方式統(tǒng)一捕獲異常:
Springboot通過(guò)redirect方式統(tǒng)一捕獲異常:
Springboot統(tǒng)一捕獲異常,返回json:
對(duì)于Controller之前發(fā)生的錯(cuò)誤和異常,返回自定義頁(yè)面:
如果對(duì)我的更新內(nèi)容很感興趣,希望可以得到您的一個(gè)點(diǎn)贊和關(guān)注,這將是對(duì)我最大的鼓勵(lì)和支持,感謝~不論多與少,我都會(huì)堅(jiān)持更新。另外,私聊我【Java學(xué)習(xí)資料】可以收獲互聯(lián)網(wǎng)大廠Java面經(jīng)和Java最新最全資料~幫助你早日拿到互聯(lián)網(wǎng)大廠的offer~
總結(jié)
以上是生活随笔為你收集整理的springboot转发http请求_网易后端实习生分享:Springboot异常和错误处理规范的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: npu算力如何计算_异构计算神器来了,它
- 下一篇: unique函数_unique函数使用场