配置錯誤處理路徑
@ConfigurationProperties(
prefix = "server",
ignoreUnknownFields = true
)
public class ServerProperties implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
//錯誤配置參數
@NestedConfigurationProperty
private ErrorProperties error = new ErrorProperties();
}
springboot的參數配置類當中有一個錯誤參數配置類.
public class ErrorProperties {
@Value("${error.path:/error}")
private String path = "/error";
private ErrorProperties.IncludeStacktrace includeStacktrace;
這裏配置了錯誤處理路徑,默認的錯誤處理路徑是/error,我們可以通過在配置文件當中配置server.error.path進行修改
錯誤自動配置類ErrorMvcAutoConfiguration
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ResourceProperties.class})
public class ErrorMvcAutoConfiguration {
private final ServerProperties serverProperties;
private final List<ErrorViewResolver> errorViewResolvers;
public ErrorMvcAutoConfiguration(ServerProperties serverProperties, ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
this.serverProperties = serverProperties;
this.errorViewResolvers = (List)errorViewResolversProvider.getIfAvailable();
}
這裏將springboot參數配置類ServerProperties 注入,同時也注入了ErrorProperties 。
ErrorMvcAutoConfiguration 中注入了ErrorPageCustomizer對錯誤鏈接進行註冊。
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties);
}
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;
protected ErrorPageCustomizer(ServerProperties properties) {
this.properties = properties;
}
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
//註冊/error或者配置文件中的server.error.path值
ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + this.properties.getError().getPath());
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
}
public int getOrder() {
return 0;
}
}
ErrorMvcAutoConfiguration 向容器中注入了BasicErrorController對/error或者配置文件中配置的錯誤路徑進行處理
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}
public String getErrorPath() {
return this.errorProperties.getPath();
}
@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 ? new ModelAndView("error", model) : modelAndView;
}
//對其他客戶端的錯誤請求進行處理
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
errorHtml
是對網頁端錯誤請求的處理,error
是對其他客戶端錯誤請求的處理
對網頁端錯誤請求的處理
@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);
//如果沒有獲取視圖解析器,就用在ErrorMvcAutoConfiguration中注入的defaultErrorView
return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
}
對錯誤信息的處理
this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)))
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}
這裏的RequestAttributes
就是在ErrorMvcAutoConfiguration
中注入容器的DefaultErrorAttributes
//ErrorMvcAutoConfiguration 代碼
@Bean
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
DefaultErrorAttributes
中的getErrorAttributes
方法
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());//放入timestamp
this.addStatus(errorAttributes, requestAttributes);//放入status
this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);//放入exception和message
this.addPath(errorAttributes, requestAttributes);//放入path
return errorAttributes;
}
將上面的分析,我們能在頁面展示的信息有時間戳(timestamp),錯誤狀態碼(status),異常對象(exception),異常消息(message),錯誤路徑(path)
獲取視圖解析器
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
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;
}
ErrorViewResolver
就只在ErrorMvcAutoConfiguration
中注入容器的DefaultErrorViewResolver
@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
modelAndView = resolver.resolveErrorView(request, status, model);
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;// error/錯誤狀態碼 error/400
//如果有模板引擎,就在模板引擎中去找 error/錯誤狀態碼 頁面
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//如果模板引擎當中沒有找到相應的頁面,再去靜態資源下找,如果還沒有返回null
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
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;
}
如果沒有獲取視圖解析器,就用在ErrorMvcAutoConfiguration中注入的defaultErrorView。
private final ErrorMvcAutoConfiguration.SpelView defaultErrorView = new ErrorMvcAutoConfiguration.SpelView("<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>${timestamp}</div><div>There was an unexpected error (type=${error}, status=${status}).</div><div>${message}</div></body></html>");
protected WhitelabelErrorViewConfiguration() {
}
@Bean(
name = {"error"}
)
@ConditionalOnMissingBean(
name = {"error"}
)
public View defaultErrorView() {
return this.defaultErrorView;
}
我們如何定義自己的錯誤頁面
有模板引擎的話,
在模板中配置error/4xx或者error/5xx頁面(頁面後綴根據引擎來寫),
沒有模板引擎
在靜態資源下配置error/4xx或者error/5xx頁面
以上都沒有
以上都沒有錯誤頁面,就是默認來到SpringBoot默認的錯誤提示頁面;
對上述原理的實際用途,自定義錯誤json數據
自定義異常處理增強
@ControllerAdvice
public class HandlerException {
//@ExceptionHandler標註對那些錯誤進行增強,如果同一處理可以寫成Exception.class,如果想對錯誤單獨處理,配置自己的錯誤
@ExceptionHandler(Exception.class)
public String handler(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("code","1001");
map.put("msg","錯誤了");
request.setAttribute("javax.servlet.error.status_code",500);
request.setAttribute("ext",map);
e.printStackTrace();
return "forward:/error";
}
//如果Exception和BindException都進行不同的處理,那麼如果發生BindException,優先有此方法進行處理,其他異常由handler方法處理
@ExceptionHandler(BindException.class)
public String bindExceptionHandler(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("code","1002");
map.put("msg","錯誤了1002");
//設置狀態碼。是爲了讓springboot自動識別賺到相應頁面
request.setAttribute("javax.servlet.error.status_code",500);
request.setAttribute("ext",map);
e.printStackTrace();
//使用請求轉發是爲了利用springboot爲我們自動識別是網頁還是其他客戶端
return "forward:/error";
}
}
將自定義的異常信息加入加入springboot返回頁面
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
//獲取springboot定義的異常數據
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
//獲取我們自己定義的異常數據
Map<String, Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
//將我們的自己定義的異常數據放到map中返回
map.put("ext",ext);
return map;
}
}
這是利用自動配置當中對DefaultErrorAttributes注入條件
@Bean
//只有當容器中沒有ErrorAttributes類型的bean是,纔會將DefaultErrorAttributes注入容器,上面我們定義了自己的MyErrorAttributes繼承於DefaultErrorAttributes,病注入容器當中,所以此時DefaultErrorAttributes將不在注入到容器中。進行錯誤信息處理時會用我們自己編寫的MyErrorAttributes
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
只有當容器中沒有ErrorAttributes類型的bean是,纔會將DefaultErrorAttributes注入容器,上面我們定義了自己的MyErrorAttributes繼承於DefaultErrorAttributes,並注入容器當中,所以此時DefaultErrorAttributes將不在注入到容器中。進行錯誤信息處理時會用我們自己編寫的MyErrorAttributes