SpringBoot——定制错误页面及原理

优质博文:IT-BLOG-CN

一、SpringBoot 默认的错误处理机制

【1】浏览器返回的默认错误页面如下:

浏览器发送请求的请求头信息如下: text/html会在后面的源码分析中说到。

【2】如果是其他客户端,默认则响应错误的 JSON字符串,如下所示:

其他客户端发送请求的请求头信息如下: */* 源码中解释。

二、原理分析

参照ErrorMvcAutoConfiguration类: 错误处理的自动配置类,以下4项为此类的重要信息。

【1】ErrorMvcAutoConfiguration.ErrorPageCustomizer: 当系统出现 4xx或者 5xx之类的错误时,ErrorPageCustomizer就会生效(定制错误的响应规则),根据如下源码可知,将会来到/error请求。

JAVA 复制代码
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
	return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties);
}

//进入ErrorPageCustomizer方法,发现registerErrorPages方法:注册一个错误也
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
	private final ServerProperties properties;

	protected ErrorPageCustomizer(ServerProperties properties) {
		this.properties = properties;
	}

	public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
		ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + 
								this.properties.getError().getPath());
		errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
	}
}

//进入this.properties.getError().getPath()方法,获取如下信息,得到/error请求。
@Value("${error.path:/error}")
private String path = "/error";//系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)

【2】BasicErrorController 处理 /error错误请求: 注意:text/html*/*就是在此处生效。

java 复制代码
@Bean
@ConditionalOnMissingBean(
    value = {ErrorController.class},
    search = SearchStrategy.CURRENT
)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}

//进入BasicErrorController对象,获取如下信息
@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());
    }

    @RequestMapping(
        produces = {"text/html"}//产生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);
    }

    @RequestMapping
    @ResponseBody//产生json数据,其他客户端来到这个方法处理;
    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);
    }
}

如上代码中提到的错误页面解析代码,进入此方法: this.resolveErrorView(request, response, status, model);

java 复制代码
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
	Iterator var5 = this.errorViewResolvers.iterator();
	ModelAndView modelAndView;
	do {
		//从所有的ErrorViewResolver得到ModelAndView
		if(!var5.hasNext()) {
			return null;
		}

		ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
		modelAndView = resolver.resolveErrorView(request, status, model);
	} while(modelAndView == null);

	return modelAndView;
}

【3】最终的响应页面是由DefaultErrorViewResolver解析得到的: 最重要的信息是,SpringBoot默认模板引擎的/error目录下获取 'status'.xml 错误页面,也可以通过4xx.xml来统配 404.xml和 400.xml等等,但是优先获取精准的页面。如果模板引擎中不存在,则会从静态页面中获取错误页面。否则返回系统默认错误页面。

java 复制代码
 @Bean
 @ConditionalOnBean({DispatcherServlet.class})
 @ConditionalOnMissingBean
 public DefaultErrorViewResolver conventionErrorViewResolver() {
		return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
	}

//进入DefaultErrorViewResolver类中
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())) {

		//调用时viewname = status ***重要
		modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
	}

	return modelAndView;
}

private ModelAndView resolve(String viewName, Map<String, Object> model) {

	//默认SpringBoot可以去找到一个页面? error/404
	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);
}

【4】DefaultErrorAttributes: 在页面添加错误信息,供我们使用。

java 复制代码
@Bean
@ConditionalOnMissingBean(
	value = {ErrorAttributes.class},
	search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
	return new DefaultErrorAttributes();
}

//进入DefaultErrorAttributes类中,发现此方法给视图中添加了status状态等信息,供我们使用。
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
	Map<String, Object> errorAttributes = new LinkedHashMap();
	errorAttributes.put("timestamp", new Date());
	this.addStatus(errorAttributes, requestAttributes);
	this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
	this.addPath(errorAttributes, requestAttributes);
	return errorAttributes;
}

三、定制错误 JSON数据

【1】自定义异常处理类,返回定制的 JSON数据。通过上述的分析,我们得知:

①、可以完全编写一个ErrorController的实现类,或者继承AbstractErrorController的子类,放入容器中。

②、也可以自定义异常处理类,返回 JSON数据。

③、页面上的数据或 JSON返回的数据都是可以通过errorAttributes.getErrorAttributes得到的。我们可以自定义属于自己的ErrorAttributes

java 复制代码
//首先我们可以通过自定义异常处理,来确定返回的数据,但这个不够灵活,我们可以与③结合使用
/**
 * @RequestMapping启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping
 * 注解的方法上。
 */
@ControllerAdvice
public class MyExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String,Object> handlerException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<String,Object>();
        request.setAttribute("javax.servlet.error.status_code","500");
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        return map;
    }
}

//③自定义ErrorAttributes,一定要加入容器
@Component
public class MyErrorAttributes extends DefaultErrorAttributes{
    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        //获取默认的配置,在此基础上添加自己的需求
        Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
        //自定义自己需要的属性
        map.put("company","yintong");

        //获取我们在异常处理类中添加的信息,
        /*注意:当我们需要结合使用的时候异常处理必须return "forward:/error";将请求转发出去,不能直接返回map对象,
        同时要去掉@responseBody注解,否则ErrorAttributes不生效*/
        map.put("ext",requestAttributes.getAttribute("ext",requestAttributes.SCOPE_REQUEST));
        return map;
    }
}

【2】效果展示:

相关推荐
一颗知足的心2 分钟前
Go语言之路————指针、结构体、方法
开发语言·后端·golang
北执南念10 分钟前
项目代码生成工具
java
yuanpan13 分钟前
C#如何正确的停止一个多线程Task?CancellationTokenSource 的用法。
开发语言·c#
程高兴15 分钟前
单相交直交变频电路设计——matlab仿真+4500字word报告
开发语言·matlab
中国lanwp16 分钟前
springboot logback 默认加载配置文件顺序
java·spring boot·logback
cherishSpring27 分钟前
在windows使用docker打包springboot项目镜像并上传到阿里云
spring boot·docker·容器
苹果酱05671 小时前
【Azure Redis 缓存】在Azure Redis中,如何限制只允许Azure App Service访问?
java·vue.js·spring boot·mysql·课程设计
我真的不会C1 小时前
QT中的事件及其属性
开发语言·qt
Java致死1 小时前
单例设计模式
java·单例模式·设计模式
胡子发芽1 小时前
请详细解释Java中的线程池(ThreadPoolExecutor)的工作原理,并说明如何自定义线程池的拒绝策略
java