在一個web項目中,總須要對一些錯誤進行界面或者json數據返回,已實現更好的用戶體驗,SpringBoot中提供了對於錯誤處理的自動配置html
ErrorMvcAutoConfiguration
這個類存放了全部關於錯誤信息的自動配置。java
訪問步驟:web
SpringBoot
註冊錯誤請求/error
。經過ErrorPageCustomizer
組件實現BasicErrorController
處理/error
,對錯誤信息進行了自適應處理,瀏覽器會響應一個界面,其餘端會響應一個json
數據DefaultErrorViewResolver
類來進行具體的解析。能夠經過模板引擎解析也能夠解析靜態資源文件,若是二者都不存在則直接返回默認的錯誤JSON
或者錯誤View
DefaultErrorAttributes
來添加具體的錯誤信息源代碼spring
//錯誤信息的自動配置 public class ErrorMvcAutoConfiguration { //響應具體的錯誤信息 @Bean public DefaultErrorAttributes errorAttributes() { return } //處理錯誤請求 @Bean public BasicErrorController basicErrorController() { return } //註冊錯誤界面 @Bean public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() { return }
//註冊錯誤界面,錯誤界面的路徑爲/error private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { //服務器基本配置 private final ServerProperties properties; public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { //獲取服務器配置中的錯誤路徑/error ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())); //註冊錯誤界面 errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage}); } //this.properties.getError() public class ServerProperties{ //錯誤信息的配置文件 private final ErrorProperties error = new ErrorProperties(); } //getPath public class ErrorProperties { @Value("${error.path:/error}") private String path = "/error";
//處理/error請求,從配置文件中取出請求的路徑 @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 = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); } //其餘客戶端行爲處理,返回一個JSON數據 @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = this.getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity(status); } else { Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); return new ResponseEntity(body, status); } }
//添加錯誤信息 public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered { public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap(); errorAttributes.put("timestamp", new Date()); this.addStatus(errorAttributes, webRequest); this.addErrorDetails(errorAttributes, webRequest, includeStackTrace); this.addPath(errorAttributes, webRequest); return errorAttributes; }
步驟:json
SpringBoot
建立錯誤請求/error
BasicErrorController
處理請求@RequestMapping( produces = {"text/html"} ) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //解析錯誤界面,返回一個ModelAndView,調用父類AbstractErrorController的方法 ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); }
public abstract class AbstractErrorController{ private final List<ErrorViewResolver> errorViewResolvers; protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { Iterator var5 = this.errorViewResolvers.iterator(); //遍歷全部的錯誤視圖解析器 ModelAndView modelAndView; do { if (!var5.hasNext()) { return null; } ErrorViewResolver resolver = (ErrorViewResolver)var5.next(); //調用視圖解析器的方法, modelAndView = resolver.resolveErrorView(request, status, model); } while(modelAndView == null); return modelAndView; } }
public interface ErrorViewResolver { ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model); }
//處理視圖跳轉 public DefaultErrorViewResolver{ public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { //將狀態碼做爲視圖名稱傳入解析 ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model); } return modelAndView; } //視圖名稱爲error文件夾下的400.html等狀態碼文件 private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; //是否存在模板引擎進行解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); //存在則返回解析之後的數據,不存在調用resolveResource方法進行解析 return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model); } //若是靜態資源文件中存在,返回靜態文件下的,若是不存在返回SpringBoot默認的 private ModelAndView resolveResource(String viewName, Map<String, Object> model) { String[] var3 = this.resourceProperties.getStaticLocations(); int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) { String location = var3[var5]; try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model); } } catch (Exception var8) { } } return null; }
應用:瀏覽器
在BasicErrorController
處理/error
請求的時候不適用瀏覽器默認請求springboot
@RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = this.getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity(status); } else { //調用父類的方法獲取全部的錯誤屬性 Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); return new ResponseEntity(body, status); } }
父類方法: protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { WebRequest webRequest = new ServletWebRequest(request); //調用ErrorAttributes接口的getErrorAttributes方法, return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace); }
添加錯誤信息 public class DefaultErrorAttributes{ public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap(); errorAttributes.put("timestamp", new Date()); this.addStatus(errorAttributes, webRequest); this.addErrorDetails(errorAttributes, webRequest, includeStackTrace); this.addPath(errorAttributes, webRequest); return errorAttributes; }
返回的json數據有:服務器
能夠經過模板引擎獲取這些值app
SpringMVC提供的註解,能夠用來定義全局異常,全局數據綁定,全局數據預處理ide
@ControllerAdivice
定義全局的異常處理
@ExceptionHandler(XXXException.class)
執行該方法須要處理什麼異常,而後返回什麼數據或者視圖//json數據返回 ,處理自定義用戶不存在異常 @ResponseBody @ExceptionHandler(UserException.class) public Map<String,String> userExceptionMethod(UserException us){ Map<String,String> map = new HashMap<>(); map.put("message",us.getMessage()); return map ; }
@ControllerAdvice
定義全局數據
@ModelAttribute(Name="key")
定義全局數據的keyController
中經過Model
獲取對應的key的值@ControllerAdvice public MyConfig{ @ModelAttribute(name = "key") public Map<String,String> defineAttr(){ Map<String,String> map = new HashMap<>(); map.put("message","幻聽"); map.put("update","許嵩"); return map ; } @Controller public UserController{ @GetMapping("/hello") public Map<String, Object> hello(Model model){ Map<String, Object> asMap = model.asMap(); System.out.println(asMap); //{key={message='上山',update='左手一式太極拳'}} return asMap ; } }
@ControllerAdvice
處理預處理數據(當須要添加的實體,屬性名字相同的時候)
Controller
的參數中添加ModelAttribute
做爲屬性賦值的前綴ControllerAdvice
修飾的類中,結合InitBinder
來綁定對應的屬性(該屬性爲ModelAttribite的value值@InitBinder
修飾的方法中經過WebDataBinder
添加默認的前綴@Getter@Setter public class Book { private String name ; private int age ; @Getter@Setter public class Music { private String name ; private String author ; //這種方式的處理,spring沒法判斷Name屬性給哪一個bean賦值,因此須要經過別名的方式來進行賦值 @PostMapping("book") public String book(Book book , Music music){ System.out.println(book); System.out.println(music); return "404" ; } //使用如下的方式 @PostMapping("/book") public String book(@ModelAttribute("b")Book book , @ModelAttribute("m")Music music){ System.out.println(book); System.out.println(music); return "404" ; } public MyCOnfiguration{ @InitBinder("b") public void b(WebDataBinder webDataBinder){ webDataBinder.setFieldDefaultPrefix("b."); } @InitBinder("m") public void m(WebDataBinder webDataBinder){ webDataBinder.setFieldDefaultPrefix("m."); } }
瀏覽器和其餘客戶端都只能獲取json
數據
@ControllerAdvice public class MyExceptionHandler { //處理UserException異常 @ResponseBody @ExceptionHandler(UserException.class) public Map<String,String> userExceptionMethod(UserException us){ Map<String,String> map = new HashMap<>(); map.put("message",us.getMessage()); map.put("status","500"); return map ; }
@ExceptionHandler(UserException.class) public String allException(UserException e,HttpServletRequest request){ Map<String,String> map = new HashMap<>(); map.put("message",e.getMessage()); map.put("load","下山"); request.setAttribute("myMessage",map); //設置狀態碼,SpringBoot經過java.servlet.error.status_code來設置狀態碼 request.setAttribute("javax.servlet.error.status_code",400); return "forward:/error" ; }
當拋出UserException
異常的時候,來到這個異常處理器,給這個請求中添加了數據,再轉發到這個error請求中,交給ErrorPageCustomizer
處理,因爲設置了請求狀態碼400
則返回的視圖爲400或4XX視圖,或者直接返回一個JSON
數據
{ "timestamp": "2020-02-19T04:17:43.394+0000", "status": 400, "error": "Bad Request", "message": "用戶名不存在異常", "path": "/crud/user/login" }
前面提到SpringBoot對錯誤信息的定義存在於DefaultErrorAttributes類的getErrorAttributes中,咱們能夠直接繼承這個類,或者實現ErrorAttributes接口,而後將咱們本身實現的錯誤處理器添加到容器中便可。
繼承DefaultErrorAttributes
和實現ErrorAttributes
接口的區別是,繼承之後仍然可使用SpringBoot默認的錯誤信息,咱們僅僅對該錯誤信息進行了加強;實現了ErrorAttributes
接口,徹底自定義錯誤信息
ErrorAttributes
接口public class MyErrorHandler implements ErrorAttributes { public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap(); errorAttributes.put("timestamp", new Date()); errorAttributes.put("status",500); errorAttributes.put("message","用戶不存在異常"); return errorAttributes; } @Override public Throwable getError(WebRequest webRequest) { return null; }
DefaultErrorAttributes
的方法public class MyErrorHandler extends DefaultErrorAttributes { public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { //調用父類方法,直接在默認錯誤信息的基礎上添加 Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace); errorAttributes.put("timestamp", new Date()); errorAttributes.put("message","用戶不存在異常"); return errorAttributes; } }
@Component
組件直接將MyErrorHandler
組件添加到容器中@Bean
在配置類中將組件添加到容器中@Bean public DefaultErrorAttributes getErrorHandler(){ return new MyErrorHandler(); }
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace); errorAttributes.put("timestamp", new Date()); errorAttributes.put("message","用戶不存在異常"); //指定從哪一個做用域中取值 webRequest.getAttribute("myMessage", RequestAttributes.SCOPE_REQUEST); return errorAttributes;
將在異常處理器中定義的錯誤信息取出,而後添加到錯誤信息中。