SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)

1、比如说:

我先注释掉拦截器,让问题重现一下:

SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第1张图片

SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第2张图片

这里我访问一个不存在的请求路径,就出现了错误(There was an unexpected error (type=Not Found, status=404).)。通过客户端 Postman 模拟请求一下(下图所示),默认响应一个  JSON 数据:

SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第3张图片

原理:

    可以参照 ErrorMvcAutoConfiguration 进行错误处理的自动配置。

    给容器中添加了以下组件:

    1、DefaultErrorAttributes

        @Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
	}

    2、BasicErrorController:处理默认 /error 请求

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

    下面两个方法:第一个是产生 html 类型的数据(浏览器请求),第二个是产生 JSON 数据(客户端请求)

        @RequestMapping(produces = "text/html")
	public ModelAndView errorHtml(HttpServletRequest request,
			HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map model = Collections.unmodifiableMap(getErrorAttributes(
				request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);// 去哪个页面作为错误页面
		return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
	}

	@RequestMapping
	@ResponseBody
	public ResponseEntity> error(HttpServletRequest request) {
		Map body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<>(body, status);
	}

    区分是浏览器请求还是客户端请求:

    浏览器:

    SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第4张图片

    客户端:    

    SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第5张图片


3、ErrorPageCustomizer

        @Bean
	public ErrorPageCustomizer errorPageCustomizer() {
		return new ErrorPageCustomizer(this.serverProperties);
	}

    4、DefaultErrorViewResolver

        @Bean
	@ConditionalOnBean(DispatcherServlet.class)
	@ConditionalOnMissingBean
	public DefaultErrorViewResolver conventionErrorViewResolver() {
		return new DefaultErrorViewResolver(this.applicationContext, his.resourceProperties);
	}

    规则说明:

        如果系统出现 4xx 或者 5xx 之类的错误,ErrorPageCustomizer 就会生效(定制错误的响应规则)

        @Override
	public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
		ErrorPage errorPage = new ErrorPage(
				this.properties.getServlet().getServletPrefix()
						 this.properties.getError().getPath());
		errorPageRegistry.addErrorPages(errorPage);
	}

        通过 this.properties.getError().getPath(), 得到 path 信息:

        /**
	 * Path of the error controller.
	 */
	@Value("${error.path:/error}")
	private String path = "/error";

     就会来到/error请求; 就会被 BasicErrorController 处理(参考 BasicErrorController 下的两段代码),去哪个页面显示。去哪个页面是由 DefaultErrorViewResolver 解析的:

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
	private static final Map SERIES_VIEWS;
	static {
		Map views = new EnumMap<>(Series.class);
		views.put(Series.CLIENT_ERROR, "4xx");
		views.put(Series.SERVER_ERROR, "5xx");
		SERIES_VIEWS = Collections.unmodifiableMap(views);
	}
        @Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
			Map model) {
		ModelAndView modelAndView = resolve(String.valueOf(status), model);
		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
		}
		return modelAndView;
	}

	private ModelAndView resolve(String viewName, Map model) {
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
				.getProvider(errorViewName, this.applicationContext);// 模板引擎可以解析这个页面地址就用模板引擎解析
		if (provider != null) {//模板引擎可用的情况下返回到errorViewName指定的视图地址
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);//模板引擎不可用,就在静态资源文件夹找errorViewName对应的页面 error/404.html,如果没有返回为null
	}

2、如何定制错误响应

    2.1:如果是浏览器访问不存在请求路径,跳转错误页面,错误页面是定制的,要和整体网站的风格一致。

        2.1.1、有模板引擎的情况下:根据 error/状态码,将错误页面命名为:错误状态.html(404.html、4xx.html);把它们放在模板引擎文件夹里面的 error 文件夹下,发生相应的状态的错误就会找到对应的页面。

                    我们可以使用 4xx.html 和 5xx.html 页面来匹配相应状态码所有的错误(如果状态码没有匹配到具体的错误页面,就会指向此状态码统一页面。精确优先)

        2.1.2、没有模板引擎:模板引擎找不到错误页面,静态资源文件夹下存在

        2.1.3、模板引擎下没有、静态资源下也没有:就会使用默认的页面

        @Configuration
	@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
	@Conditional(ErrorTemplateMissingCondition.class)
	protected static class WhitelabelErrorViewConfiguration {

		private final SpelView defaultErrorView = new SpelView(
				"

Whitelabel Error Page

" + "

This application has no explicit mapping for /error, so you are seeing this as a fallback.

" + "
${timestamp}
" + "
There was an unexpected error (type=${error}, status=${status}).
" + "
${message}
"); @Bean(name = "error") @ConditionalOnMissingBean(name = "error") public View defaultErrorView() { return this.defaultErrorView; } // If the user adds @EnableWebMvc then the bean name view resolver from // WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment. @Bean @ConditionalOnMissingBean public BeanNameViewResolver beanNameViewResolver() { BeanNameViewResolver resolver = new BeanNameViewResolver(); resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); return resolver; } }

    2.2:如果是客户端请求,则要统一数据,定制返回数据

   

3、定制错误页面、返回定制错误json数据,并把定制数据进行显示

1、完全可以编写一个ErrorController的实现类 或者 编写AbstractErrorController的子类,放在容器中

2、页面上能用的数据,或者是json返回能用的数据都是通过 errorAttributes.getErrorAttributes 得到

4、异常信息响应是自适应的效果(浏览器请求异常,返回异常页面;客户端请求,返回JSON数据),并且通过定制ErrorAttributes改变需要返回的内容信息:

SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第6张图片

/**
 * 自定义一个用户不存在异常,继承运行进异常
 * @author john
 *
 */
public class UserNotExistException extends RuntimeException {

	/**
	 * 
	 */
	private static final long serialVersionUID = -1531824041384080881L;

	public UserNotExistException(){
		super("用户不能为 u");
	}
}

SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第7张图片

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import com.demo.springbootweb.exception.UserNotExistException;

/**
 * MVC如果想成为异常处理器,需要 @ControllerAdvice 注解
 * @author john
 *
 */
@ControllerAdvice
public class MyExceptionHandler {
	
	/**
	 * 自适应效果:浏览请求,响应浏览器的错误页面;客户端请求,返回json错误数据
	 * @param e
	 * @param request
	 * @return
	 */
	@ExceptionHandler(UserNotExistException.class)
	public String handleException(Exception e, HttpServletRequest request){
		Map map = new HashMap();
		// 需要传自己的状态码 4xx、5xx,否则不会进入自己定制的错误页面的解析流程
		request.setAttribute("javax.servlet.error.status_code", 400);
		map.put("code", "user not exeist");
		map.put("message", e.getMessage());
		request.setAttribute("ext", map); //自定义扩展的信息
		return "forward:/error";
	}
}

SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第8张图片

import java.util.Map;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

/**
 * 给容器中加入我们自己定义的错误信息
 * @author john
 *
 */
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
	
	/**
	 * 这里返回的map就是页面和json都能获取的所有字段
	 */
	@Override
	public Map getErrorAttributes(WebRequest webRequest,
			boolean includeStackTrace) {
		Map errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
		errorAttributes.put("company", "demo");
		//  异常处理器携带的数据
		Map ext = (Map)webRequest.getAttribute("ext", 0);
		errorAttributes.put("ext", ext);
		return errorAttributes;
	}
	
}
SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第9张图片
        @ResponseBody
	@RequestMapping(value="u", method = RequestMethod.GET)
	public String getEmp(String name){
		if("u".equals(name)){
			throw new UserNotExistException();
		}
		return "success";
	}

请求路径:http://localhost:8080/crud/user/u?name=u   

最终的效果如下图所示:

客户端:

SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第10张图片

浏览器:

SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19)_第11张图片

demo地址:https://download.csdn.net/download/yufang131/10433378

感谢--尚硅谷


你可能感兴趣的:(SpringBoot web--错误处理、定制错误页面和错误数据(学习笔记19))