系统出现4xx或5xx之类的错误时,ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
// 错误定制页面
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
}
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;
private final DispatcherServletPath dispatcherServletPath;
protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
this.properties = properties;
this.dispatcherServletPath = dispatcherServletPath;
}
// 注册错误页面的响应规则
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
/*
系统出现错误后,来到error请求进行处理(web.xml注册的错误页面规则)
this.properties.getError().getPath() ----> /error
*/
ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
}
public int getOrder() {
return 0;
}
}
@Bean
@ConditionalOnMissingBean(
value = {ErrorController.class},
search = SearchStrategy.CURRENT
)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}
/*
${server.error.path:${error.path:/error}}
如果 server.error.path 未配置,则使用 ${error.path:/error}
如果 error.path 未配置,则使用 "/error"
*/
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
// 产生html类型的数据,浏览器发送的请求来到这个方法处理
@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) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
}
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
/*
getErrorAttributes --> ErrorAttributes(接口) --> DefaultErrorAttributes(实现)
DefaultErrorAttributes:
*/
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
// 页面信息
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;
}
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;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 遇到错误时,SpringBoot根据errorViewName查找页面 eg:error/404.html
String errorViewName = "error/" + viewName;
// 模板引擎对页面进行解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//解析成功,返回到errorViewName指定的视图地址
//解析失败,前往静态资源文件夹下找errorViewName对应的页面 error/404.html
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
static {
Map<Series, String> views = new EnumMap(Series.class);
// 错误码
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
有模板引擎的情况下,将错误页面命名为 错误状态码.html(404.html) 放在模板引擎文件夹里面的 error文件夹下,发生此状态码的错误就会来到对应的页面
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
@package com.gp6.springboot18.exception;
public class UserNotExistException extends RuntimeException {
public UserNotExistException() {
super("用户不存在!");
}
}
package com.gp6.springboot18.controller;
import com.gp6.springboot18.exception.UserNotExistException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
/*
访问
当前项目(localhost:8080)
或
localhost:8080/index.htm
都会被模板引擎自动解析,查找templates/index.html
*/
@RequestMapping({"/","/index.html"})
public String index(){
return "login";
}
/*
测试自定义异常
*/
@RequestMapping({"testException"})
@ResponseBody
public String testException(@RequestParam("userName") String userName){
if("gp6".equals(userName)){
throw new UserNotExistException();
}
return "测试自定义异常";
}
}
package com.gp6.springboot18.config;
import com.gp6.springboot18.exception.UserNotExistException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
// 浏览器客户端返回的都是json
@ResponseBody
// 捕获UserNotExistException异常
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notExist");
map.put("message",e.getMessage());
return map;
}
}
package com.gp6.springboot18.config;
import com.gp6.springboot18.exception.UserNotExistException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
public String handleException2(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notExist");
map.put("message","该用户不存在啊");
// 如果不设置状态码,不能跳转自定义错误页面
request.setAttribute("javax.servlet.error.status_code",500);
// 转发到/error
return "forward:/error";
}
}
出现错误后,会来到/error请求,被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法)
1、编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中
2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes()得到
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的
package com.gp6.springboot18.config;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
//返回值的map就是页面和json能获取的所有字段
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("company","gp6");
//我们的异常处理器携带的数据
Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
map.put("ext",ext);
return map;
}
}