Spring boot Web MVC : 缺省错误属性处理工具 DefaultErrorAttributes

概述

DefaultErrorAttributesSpring boot提供的处理错误属性的缺省工具。它主要有两个功能 :

  1. 处理某个业务请求遇到异常时记录异常到请求属性 – HandlerExceptionResolver接口定义的能力;

    此功能体现在方法resolveException的实现中。在这个方法中,它将异常添加为业务请求的一个属性,属性名称使用常量ERROR_ATTRIBUTE的值,然后返回null ModelAndView,告诉调用者自己未能处理该异常。

    常量ERROR_ATTRIBUTE值实为org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR

  2. 从错误页面处理请求属性中获取错误属性 – ErrorAttributes接口定义的能力;

    当用户业务请求出现异常,并且该异常未被Spring MVC处理,或者调用了response.sendErrorServlet容器就会构造并触发(forward)一个对错误处理页面的请求,并将如下信息添加为错误页面处理请求的属性 :

    • javax.servlet.error.request_uriString,错误发生时所请求的URI路径

    • javax.servlet.error.exceptionThrowable,所发生的错误/异常

    • javax.servlet.error.messageString,所发生的错误/异常信息

    • javax.servlet.error.status_codeInteger ,HTTP协议的状态代码

      注意 : 以上这些内容属于 Java Servlet规范。

    然后错误页面处理Web控制器BasicErrorController就会使用DefaultErrorAttributes获取该请求中的以上属性,从而获得发生错误的业务请求的如下信息 :

    1. timestamp – 属性信息被从请求中提取的时间
    2. statusHTTP状态码
    3. error – 跟 HTTP状态码对应的原因描述文字

      HttpStatus中获取,如果获取失败,则会使用Http Status xxx字样(这里xxxHTTP状态码)

    4. exception – 导致请求处理失败的异常对象本身
    5. message – 错误/异常消息
    6. errors – 如果是一个BindingResult异常的话,这里会是BindingResult异常中的ObjectError对象
    7. trace – 错误/异常栈信息
    8. path – 错误/异常抛出时所请求的URL路径

注意 1 : 上面我们提到,业务请求异常时,DefaultErrorAttributes解析异常过程中会将异常记录为请求的属性org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR,而Servlet容器捕获到异常发起错误页面处理请求时,也会将异常记录为错误页面处理请求的属性javax.servlet.error.exception,这样请求属性中就会存在两个保存异常的属性,那么DefaultErrorAttributes会使用哪个呢?事实上,DefaultErrorAttributes先尝试从属性org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR中获取,如果获取不到,则会尝试从属性javax.servlet.error.exception中获取。

注意 2 : 以上的描述使用 “业务请求” 和 “错误页面处理请求” 以区分这是有关系的两个不同的请求,它们使用同一份属性(attributes),对应同一个响应对象。

关于应用

1. 引入

Spring Boot + Spring Web MVC应用为例,缺省情况下,应用启动时,自动配置机制ErrorMvcAutoConfiguration会定义两个bean :

  • DefaultErrorAttributes errorAttributes() – 定义一个DefaultErrorAttributes组件
  • BasicErrorController basicErrorController(ErrorAttributes errorAttributes) – 定义一个全局缺省的错误处理控制器,使用到了上面定义的DefaultErrorAttributes组件

2. 使用

业务请求处理中出现异常时,作为一个HandlerExceptionResolver,DefaultErrorAttributes bean组件逻辑被应用,从而相应异常被记录到请求属性。
错误页面请求到达时,BasicErrorController控制器方法#errorHtml或者#error会使用DefaultErrorAttributes bean组件从请求属性中获取相应的错误属性然后渲染错误页面。

源代码解析

package org.springframework.boot.web.servlet.error;



/**
 * Default implementation of  ErrorAttributes. Provides the following attributes
 * when possible:
 *
 * 1. timestamp - The time that the errors were extracted
 * 2. status - The status code
 * 3. error - The error reason
 * 4. exception - The class name of the root exception (if configured)
 * 5. message - The exception message
 * 6. errors - Any ObjectErrors from a BindingResult exception
 * 7. trace - The exception stack trace
 * 8. path - The URL path when the exception was raised
 * 
 *
 * @see ErrorAttributes
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
		implements ErrorAttributes, HandlerExceptionResolver, Ordered {

	private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName()
			+ ".ERROR";

	private final boolean includeException;

	/**
	 * Create a new DefaultErrorAttributes instance that does not include the
	 * "exception" attribute.
	 */
	public DefaultErrorAttributes() {
		this(false);
	}

	/**
	 * Create a new DefaultErrorAttributes instance.
	 * @param includeException whether to include the "exception" attribute
	 */
	public DefaultErrorAttributes(boolean includeException) {
		this.includeException = includeException;
	}

	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE;
	}

	// 解析异常,具体的做法是 :
	// 1. 将异常 ex 作为属性 ERROR_ATTRIBUTE 的值添加到请求 request 上
	// 2. 返回 null ModelAndView (要点提示 : null 相当于告诉调用者自己未能处理该异常)
	@Override
	public ModelAndView resolveException(HttpServletRequest request,
			HttpServletResponse response, Object handler, Exception ex) {
		storeErrorAttributes(request, ex);
		return null;
	}

	// 将异常 ex 作为属性 ERROR_ATTRIBUTE 的值添加到请求 request 上
	private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
		request.setAttribute(ERROR_ATTRIBUTE, ex);
	}

	// 获取请求对象 webRequest 中所包含的错误属性的有关信息,以及当前时间,组成一个 
	// Map<String, Object> 对象并返回
	@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;
	}

	// 获取请求属性对象 requestAttributes 中指定名称为 "javax.servlet.error.status_code" 的属性值,
	// 试图将其转换成一个 HttpStatus, 然后将该 HttpStatus 的错误码添加为 errorAttributes 的属性
	// "status",并将其对应的 reasonPhrase 字符串添加为 errorAttributes 的属性 "error" 。
	// 如果不能转成 HttpStatus, 则添加的 "status"/"error"属性为 : 999/"None"
	// 这里请求属性对象参数 requestAttributes 通常就是一个请求对象
	private void addStatus(Map<String, Object> errorAttributes,
			RequestAttributes requestAttributes) {
		Integer status = getAttribute(requestAttributes,
				"javax.servlet.error.status_code");
		if (status == null) {
			errorAttributes.put("status", 999);
			errorAttributes.put("error", "None");
			return;
		}
		errorAttributes.put("status", status);
		try {
			errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
		}
		catch (Exception ex) {
			// Unable to obtain a reason
			errorAttributes.put("error", "Http Status " + status);
		}
	}

	// 从请求 webRequest 中提取错误异常对象有关信息:根异常类名,异常消息,异常堆栈跟踪信息,
	// 然后添加到 errorAttributes
	private void addErrorDetails(Map<String, Object> errorAttributes,
			WebRequest webRequest, boolean includeStackTrace) {
		Throwable error = getError(webRequest);
		if (error != null) {
			while (error instanceof ServletException && error.getCause() != null) {
				error = ((ServletException) error).getCause();
			}
			if (this.includeException) {
				errorAttributes.put("exception", error.getClass().getName());
			}
			addErrorMessage(errorAttributes, error);
			if (includeStackTrace) {
				addStackTrace(errorAttributes, error);
			}
		}
		Object message = getAttribute(webRequest, "javax.servlet.error.message");
		if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
				&& !(error instanceof BindingResult)) {
			errorAttributes.put("message",
					StringUtils.isEmpty(message) ? "No message available" : message);
		}
	}

	// 将错误 error 的错误消息提取出来作为属性 "message" 添加到 errorAttributes
	private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
		BindingResult result = extractBindingResult(error);
		if (result == null) {
			errorAttributes.put("message", error.getMessage());
			return;
		}
		if (result.hasErrors()) {
			errorAttributes.put("errors", result.getAllErrors());
			errorAttributes.put("message",
					"Validation failed for object='" + result.getObjectName()
							+ "'. Error count: " + result.getErrorCount());
		}
		else {
			errorAttributes.put("message", "No errors");
		}
	}

	// 检查错误 error 是否属于类型 BindingResult, 如果是做强制类型转换并返回,
	// 否则如果 error 类型是 MethodArgumentNotValidException, 则获取它的属性 bindingResult
	// 并返回,否则返回 null
	private BindingResult extractBindingResult(Throwable error) {
		if (error instanceof BindingResult) {
			return (BindingResult) error;
		}
		if (error instanceof MethodArgumentNotValidException) {
			return ((MethodArgumentNotValidException) error).getBindingResult();
		}
		return null;
	}

	// 获取错误 error 的异常栈跟踪信息字符串,作为属性 "trace" 添加到 errorAttributes
	private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
		StringWriter stackTrace = new StringWriter();
		error.printStackTrace(new PrintWriter(stackTrace));
		stackTrace.flush();
		errorAttributes.put("trace", stackTrace.toString());
	}

	// 获取请求 requestAttributes 属性 "javax.servlet.error.request_uri" 的属性值,
	// 它表示异常发生时所请求的路径,将它作为属性 "path" 添加到 errorAttributes
	// 这里请求属性对象参数 requestAttributes 通常就是一个请求对象
	private void addPath(Map<String, Object> errorAttributes,
			RequestAttributes requestAttributes) {
		String path = getAttribute(requestAttributes, "javax.servlet.error.request_uri");
		if (path != null) {
			errorAttributes.put("path", path);
		}
	}

	// 获取请求对象 webRequest 属性名称为 ERROR_ATTRIBUTE 或者 "javax.servlet.error.exception"
	// 的属性值,应该是一个异常
	@Override
	public Throwable getError(WebRequest webRequest) {
		Throwable exception = getAttribute(webRequest, ERROR_ATTRIBUTE);
		if (exception == null) {
			exception = getAttribute(webRequest, "javax.servlet.error.exception");
		}
		return exception;
	}

	// 获取请求属性对象 requestAttributes 中指定名称为 name 的属性的值,
	// 这里请求属性对象参数 requestAttributes 通常就是一个请求对象
	@SuppressWarnings("unchecked")
	private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
		return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
	}

}

### Spring Boot 中获取 URL 查询参数的方法 在 Spring Boot 应用程序中,可以通过 `@RequestParam` 注解来获取 URL 中问号后面跟随的查询参数。此注解用于绑定 Web 请求中的查询参数到控制器方法的形参上。 对于带有查询字符串 `/test/login2?id=789` 的情况,可以定义如下控制器方法: ```java @RestController public class TestController { @GetMapping(path = "test/login2") public String loginTest2( @RequestParam(value = "id", required = false, defaultValue = "123") Integer id) { return id.toString(); } } ``` 上述代码片段展示了如何利用 `@RequestParam` 来接收名为 `id` 的查询参数[^1]。如果请求路径中未提供该参数,则会采用设定的默认值 `"123"`;而当设置 `required=false` 时表明这个参数不是必需存在的。 另外需要注意的是,在某些情况下可能会遇到 URL 参数中的特殊字符(比如连字符 `-`)被错误解析的问题。这通常是因为 Spring MVC 默认执行了解码操作所致[^2]。为了确保这些特殊字符能够正确传输并被捕获,可能需要调整编码配置或者自定义处理器来进行适配。 #### 处理缺失或异常的情况 有时开发者希望为那些可选性的查询参数指定缺省值以便更好地控制逻辑流程。此时可以在 `@RequestParam` 上面增加属性 `defaultValue` 并赋予相应的初始值得以实现这一目的。例如上面例子中的 `id` 字段如果没有传入的话就会自动赋给它整数值 `123`[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值