SpringBoot源码分析 定制对浏览器和其他客户端的出现异常时候的错误显示

本文分析了SpringBoot源码中错误处理的流程,特别是在浏览器访问出错时返回视图层,其他客户端出错时返回定制JSON数据的机制。通过@ExceptionHandler注解捕获错误,当处理器返回逻辑页面时,错误被转发到BasicErrorController。BasicErrorController包含处理浏览器和非浏览器错误的方法,两者均调用DefaultErrorAttributes.getErrorAttributes进行错误数据获取。要定制错误信息,可以继承DefaultErrorAttributes并重写getErrorAttributes方法,确保在容器中唯一。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

浏览器访问出现错误是返回视图层

其他客户端出现错误时候返回定制的json数据

先写一个ExceptionHandler

@ControllerAdvice
public class MyExceptionHandler {
	//出现运行时异常就会进入这个方法,将信息放到域中,然后转发到/error
    @ExceptionHandler(RunTimeException.class)
    public String handleException(Exception e, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        //传入我们自己的错误状态码  4xx 5xx
        /**
         * Integer statusCode = (Integer) request
         .getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_coede", 500);
        map.put("code", "user.notexist");
        map.put("message", "用户出错啦");
        request.setAttribute("ext", map);
        //转发到/error
        return "forward:/error";
    }
}

首先先分析下springboot的自动配置
先看ErrorMvcAutoConfiguration

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class,
		WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
   //这里new了一个BasicErrorController,当出现错误的时候BasicErrorController里面就有对应的方法会被执行
    @Bean
	@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
	public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
		return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
				this.errorViewResolvers);
	}
}

BasicErrorController

@Controller
//被异常处理器拦截到 return "forward:/error" 就会跳到这个控制器里面来
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    //该类只有两个处理器  处理的都是同一套逻辑
    
    //这个处理器是对浏览器访问处理的
    //返回的是ModelAndView
    	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
	public ModelAndView errorHtml(HttpServletRequest request,
			HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> 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
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<>(body, status);
	}


}

//这两个处理器都有个共同的方法getErrorAttributes,将错误的信息都取出来,返回给客户端

getErrorAttributes

@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
      implements ErrorAttributes, HandlerExceptionResolver, Ordered {
      
    //get出了timestamp、status、error、path、message
     @Override
	public Map<String, Object> getErrorAttributes(WebRequest webRequest,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, webRequest);
		addErrorDetails(errorAttributes, webRequest, includeStackTrace);
		addPath(errorAttributes, webRequest);
		return errorAttributes;
	}
      
}

综上,执行顺序是

  1. @ExceptionHandler(…)拦截到了错误

    1.1.如果处理器的返回值是数据@ResponseBody,则直接将数据返回给客户端。结束。

    1.2. 如果处理器的返回的是逻辑页面,如果是 return “forward:/error”;则将数据转发到处理/error请求的controller ----> BasicErrorController

  2. BasicErrorController 里面有两个方法,一个是处理浏览器错误的(返回的是视图页面),另一个是处理其他客户端错误的(返回的是json数据)

    这两个方法的共同点是都调用了getErrorAttributes方法

  3. getErrorAttributes---->这个方法是抽象类AbstractErrorController的
    这个方法里面调用了getErrorAttributes

  4. getErrorAttributes ---->这个方法是DefaultErrorAttributes类的

	@Override
	public Map<String, Object> getErrorAttributes(WebRequest webRequest,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, webRequest);
		addErrorDetails(errorAttributes, webRequest, includeStackTrace);
		addPath(errorAttributes, webRequest);
		return errorAttributes;
	}
//这里面并没有把异常处理器中request中放置的数据存进去,故客户端显示错误信息的时候并没有显示我们自己定制的错误,所以如果要显示自己定制的错误,我们需要重写这个方法,从webRequest中getAttribute数据
  1. 所以要定制自己需要的错误数据,只需要继承DefaultErrorAttributes,重写getErrorAttributes 方法
  2. 在容器中,DefaultErrorAttributes只能有一个
//类ErrorMvcAutoConfiguration
@Bean
//这里有个条件  如果没有ErrorAttribute才创建,因为DefaultErrorAttributes实现了ErrorAttribute,所以也就是   没有DefaultErrorAttributes才会new一个DefaultErrorAttributes
@ConditionalOnMissingBean(value = ErrorAttribute.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
   return new DefaultErrorAttributes(
         this.serverProperties.getError().isIncludeException());
}
  1. 故我们实现DefaultErrorAttributes并重写方法getErrorAttributes
@Component
public class MyExceptionAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, 
                                                  boolean includeStackTrace) {
	        //调用父类的方法
	        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
	        //添加自己的错误信息
	        Object ext = webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
	        map.put("ext", ext);
	        //返回错误信息
	        return map;
      }

最后效果
浏览器 返回的是页面
在这里插入图片描述

其他客户端 返回的是json数据
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值