开发中经常需要使用到controller,而在controller中往往开头逻辑便是校验这些数据的合法性。我们发现如果每个controller中的方法都花费几行代码去校验数据的话,积少成多便有大量的代码浪费在这些非业务逻辑上,使得代码不够简洁。那怎么才能使代码更优雅呢?可以引入注解。
一、如何使用
二、源码查看实现细节
一、如何使用
1、在controller的方法参数中添加@Valid注解或@Validated注解
@Slf4j
@RestController
@RequestMapping("/service")
public class CommandController {
@Autowired
private CommandManage commandManage;
@PostMapping(value = "/open", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public CommandResultResponseDTO open(@Valid @RequestBody CommandInfoRequestDTO commandInfoRequestDTO) {
return commandManage.command(commandInfoRequestDTO.getOrderInfo());
}
@PostMapping(value = "/query_command", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public QueryResultResponseDTO query(@Valid @RequestBody QueryInfoRequestDTO queryInfoRequestDTO) {
return commandManage.query(queryInfoRequestDTO);
}
}
2、对@Valid注解修饰的参数添加注解校验,可以是实现javax.validation.Validator接口的第三方工厂的注解(eg:hibernate)
@Slf4j
@Data
public class CommandInfoRequestDTO {
@Valid
@NotNull
@Size(min = 1)
private List<OrderInfoDTO> orderInfo;
@Data
public static class OrderInfoDTO {
@NotEmpty
private String seqNo;
@NotEmpty
private String cabinetCode;
@NotEmpty
private String locationCode;
@NotEmpty
@Pattern(regexp = "ON|OFF")
private String action;
@NotNull
private Date actionDate;
}
}
3、配置全局异常捕获,这里捕获了包括controller方法中抛出的所有异常(包括了参数校验异常),不同参数解析器会抛出不同类型的异常,解析源码时我们会具体分析。
注意添加类注解@RestControllerAdvice或@ControllerAdvice、方法注解@ExceptionHandler,其中ExceptionHandler value对应一组异常类型,代表该方法处理该组异常类型。
@RestControllerAdvice
@Slf4j
public class ExceptionControllerAdvice {
@Value("${i18n.prefix}")
public String messageCodePrefix;
@Autowired
MessageSource messageSource;
@ExceptionHandler(value = ConstraintViolationException.class)
public Result handleConstraintViolationException(HttpServletRequest req, HttpServletResponse response, Exception exception) {
ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception;
Set<ConstraintViolation<?>> set = constraintViolationException.getConstraintViolations();
Result result = new Result();
result.setCode(BaseCode.OPERATE_PARAM_INVALID);
StringBuilder message = new StringBuilder();
if (set != null && set.size() >= 1) {
for (ConstraintViolation constraintViolation : set) {
Path path = constraintViolation.getPropertyPath();
Iterator<Path.Node> iterator = path.iterator();
String field = new String();
while (iterator.hasNext()) {
Path.Node node = iterator.next();
field = node.getName();
}
message.append((message.length() == 0 ? "" : ", ") + "[" + field + "] " + constraintViolation.getMessage());
}
}
log.error("args error:{} ", message);
log.error("detail error:{}", constraintViolationException);
result.setMsg(message);
return result;
}
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
public Result handleMethodArgumentNotValidException(HttpServletRequest req, HttpServletResponse response, Exception exception) {
StringBuilder message = new StringBuilder();
BindingResult bindingResult = null;
if (exception instanceof MethodArgumentNotValidException) {
bindingResult = ((MethodArgumentNotValidException) exception).getBindingResult();
}
if (exception instanceof BindException) {
bindingResult = ((BindException) exception).getBindingResult();
}
List<FieldError> fieldErrorList = bindingResult.getFieldErrors();
for (FieldError x : fieldErrorList) {
message.append((message.length() == 0 ? "" : ", ") + "[" + x.getField() + "] " + x.getDefaultMessage());
}
Result result = new Result();
result.setCode(BaseCode.OPERATE_PARAM_INVALID);
log.error("args error:{} ", message);
log.error("detail error:{}", exception);
result.setMsg(message);
return result;
}
@ExceptionHandler(value = BaseException.class)
public Result handleBusinessException(HttpServletRequest req, HttpServletResponse response, Exception exception) {
BaseException businessException = (BaseException) exception;
String message = null;
String code = messageCodePrefix + businessException.getCode();
try {
Object arg[] = null;
if (code.equals(messageCodePrefix + BaseCode.SEQ_NO_DUPLICATION)) {
arg = new Object[]{businessException.getMessage()};
}
message = this.messageSource.getMessage(code, arg, LocaleContextHolder.getLocale());
} catch (Exception var11) {
log.error("obtain messageSource error ", var11.getMessage(), var11);
}
Result result = new Result();
result.setCode(businessException.getCode());
businessException.setMessage(message);
result.setMsg(message);
log.error("command service error:{}", message);
log.error("command service error detail:", businessException);
return result;
}
/**
* 抛出异常拦截
*
* @param req req
* @param exception exception
* @throws Exception exception
*/
@ExceptionHandler(value = Exception.class)
public Result handleDefaultError(HttpServletRequest req, HttpServletResponse response, Exception exception) {
Result result = new Result<>(BaseCode.OPERATE_SYSTEM_FAIL);
String code = messageCodePrefix + result.getCode();
String message = null;
try {
message = this.messageSource.getMessage(code, null, LocaleContextHolder.getLocale());
} catch (Exception var11) {
log.error("obtain messageSource error ", var11.getMessage(), var11);
}
result.setMsg(message);
log.error("error:", exception.getMessage(), exception);
return result;
}
二、源码解析
我们知道springmvc会把controller中的方法解析成一个可执行的handlerMethod,对于一个可执行的handlerMethod的第一件事便是解析参数
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 由此执行controller中的方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
1、解析参数流程
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 得到方法参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 调用方法
return doInvoke(args);
}
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
if (ObjectUtils.isEmpty(getMethodParameters())) {
return EMPTY_ARGS;
}
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// providedArgs是springmvc中自动携带的参数(eg:HttpServletRequest)
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 根据参数注解、参数类型不同,又有不同的参数解析器
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled..
if (logger.isDebugEnabled()) {
String error = ex.getMessage();
if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, error));
}
}
throw ex;
}
}
return args;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 获取参数解析器,然后缓存起来
return getArgumentResolver(parameter) != null;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
// 首先从缓存中获取
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
// 从内置的方法参数解析器中遍历,这个内置的方法参数解析器来源于RequestMappingHandlerAdapter的赋值,
//而RequestMappingHandlerAdapter中的方法参数解析器来源于afterPropertiesSet中设置的默认值,
//默认值有26种方法参数解析器
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
1.1、我们以支持@RequestBody注解的方法参数解析器RequestResponseBodyMethodProcessor为例
@Override
public boolean supportsParameter(MethodParameter parameter) {
//方法参数包含@RequestBody注解就可以使用这个方法参数解析器了
return parameter.hasParameterAnnotation(RequestBody.class);
}
再回到上面的代码图,直接再贴一份吧
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
if (ObjectUtils.isEmpty(getMethodParameters())) {
return EMPTY_ARGS;
}
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 根据参数注解、参数类型不同,又有不同的参数解析器
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 这里会走缓存,直接拿到这个参数的参数解析器,然后来解析
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled..
if (logger.isDebugEnabled()) {
String error = ex.getMessage();
if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, error));
}
}
throw ex;
}
}
return args;
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 上一步找匹配的参数解析器时已经把该参数与对应的参数解析器缓存起来了,这里可以直接从缓存中取出来
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException(
"Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +
" supportsParameter should be called first.");
}
// 这一步才真正开始解析参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
//通过自定义的fastJson解析到对应的参数对象
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
// 获取参数名称
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
// 创建数据绑定器
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
// 这里开始验证数据的合法性
validateIfApplicable(binder, parameter);
// 如果数据校验不合格,并且需要抛异常,则抛出异常
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
// 不同的参数解析器抛出的异常不同,例如ModelAttributeMethodProcessor抛出BindException
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
1.2、验证参数是否合法
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
// 是否有@Validated注解
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
//是否有@Valid注解
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
//hints为分组校验
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
//数据绑定器内置了校验器适配器,从这里开始校验
binder.validate(validationHints);
break;
}
}
}
public void validate(Object... validationHints) {
Object target = getTarget();
Assert.state(target != null, "No target to validate");
//创建BindingResult来收集validate方法返回Set约束违规的集合
BindingResult bindingResult = getBindingResult();
// Call each validator with the same binding result
//获取所有的验证器去验证
for (Validator validator : getValidators()) {
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
// 带分组的验证
((SmartValidator) validator).validate(target, bindingResult, validationHints);
}
else if (validator != null) {
// 不带分组的验证
validator.validate(target, bindingResult);
}
}
}
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
if (this.targetValidator != null) {
// 处理约束违规
processConstraintViolations(
// validate方法返回Set约束违规的集合
this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
}
}
1.3、把对应的Set转化为BindResult中的Errors
protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
for (ConstraintViolation<Object> violation : violations) {
String field = determineField(violation);
FieldError fieldError = errors.getFieldError(field);
if (fieldError == null || !fieldError.isBindingFailure()) {
try {
ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
String errorCode = determineErrorCode(cd);
Object[] errorArgs = getArgumentsForConstraint(errors.getObjectName(), field, cd);
if (errors instanceof BindingResult) {
// Can do custom FieldError registration with invalid value from ConstraintViolation,
// as necessary for Hibernate Validator compatibility (non-indexed set path in field)
BindingResult bindingResult = (BindingResult) errors;
String nestedField = bindingResult.getNestedPath() + field;
if (nestedField.isEmpty()) {
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
ObjectError error = new ObjectError(
errors.getObjectName(), errorCodes, errorArgs, violation.getMessage());
error.wrap(violation);
bindingResult.addError(error);
}
else {
Object rejectedValue = getRejectedValue(field, violation, bindingResult);
String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
FieldError error = new FieldError(errors.getObjectName(), nestedField,
rejectedValue, false, errorCodes, errorArgs, violation.getMessage());
error.wrap(violation);
bindingResult.addError(error);
}
}
else {
// got no BindingResult - can only do standard rejectValue call
// with automatic extraction of the current field value
errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
}
}
catch (NotReadablePropertyException ex) {
throw new IllegalStateException("JSR-303 validated property '" + field +
"' does not have a corresponding accessor for Spring data binding - " +
"check your DataBinder's configuration (bean property versus direct field access)", ex);
}
}
}
}
1.4、到现在参数已经解析、校验完毕,校验的错误信息都保存在bindingResult中了,接下来又回到上面代码,我们贴下来
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
//创建数据绑定器,用来验证参数、收集参数的错误信息
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
//如果有@Valid或@Validated注解的话,进行参数校验
validateIfApplicable(binder, parameter);
//如果bindingResult含有校验错误信息,并且需要抛出异常的话,则抛异常
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
1.5、如何确定是否需要抛异常?
从下面可以看到,我们也可以直接使用BindingResult参数添加到controller方法中(放到最后一位),代替使用全局ControllerAdvice
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
//从这里看到,如果该controller方法参数中的最后一个是Errors的子类的话,则不需要抛异常。
//因此我也可以直接使用BindingResult类来接收参数校验的错误信息
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
1.6、使用BindingResult输出参数校验异常
@PostMapping(value = "hi")
public Result<Test> add(@Valid Test test, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResultUtil.error(1, bindingResult.getFieldError().getDefaultMessage());
}
...
}
2、捕获全局异常
2.1、DispatcherServlet中doDispatch方法try catch了所有异常,并通过processDispatchResult方法处理异常
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
// 这里捕获了所有异常,包括参数校验不合法异常
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//处理返回结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
// 异常不为空
if (exception != null) {
// ModelAndView异常,我们前后端分离不返回view
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 处理handlerMethod的异常
mv = processHandlerException(request, response, handler, exception);
// 返回的mv 为空,errorView为false
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
2.2、获取HandlerExceptionResolver对异常进行处理
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
// handlerExceptionResolvers的赋值在DispatcherServlet的初始化initStrategies方法中的
//initHandlerExceptionResolvers方法中进行,该方法获取bean容器中所有HandlerExceptionResolver类
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 使用HandlerExceptionResolver 异常解析器处理异常
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
//从异常聚合类中获取异常解析器继续处理
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
异常聚合类的bean定义、内部异常解析器的赋值
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
//增加ExceptionHandlerExceptionResolver,并为其赋值ArgumentResolvers、ArgumentResolvers等
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
2.3、继续由ExceptionHandlerExceptionResolver类来对异常进行处理
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
// 如果有指定处理该类的异常,或者完全没有指定处理类的异常
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
//开始解析
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print warn message when warn logger is not enabled...
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// warnLogger with full stack trace (requires explicit config)
logException(ex, request);
}
return result;
}
else {
return null;
}
}
2.4、获得异常HandlerMethod,这一步就如同controller中的方法解析成RequestMappingInfo对应的HandlerMethod一样,异常解析器也把@ExceptionHandler对应的异常处理方法封装成HandlerMethod,并赋值参数解析器与返回值处理器。这里的设计理念与Controller是一致的,这也是为该类注解名称@ControllerAdvice的由来吧,后面我们会介绍如何扫描@ControllerAdvice并把他封装HandlerMethod。
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// 得到HandlerMethod
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
if (this.argumentResolvers != null) {
// 赋值参数解析器
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
//赋值返回值处理器
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
try {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
Throwable cause = exception.getCause();
if (cause != null) {
// Expose cause as provided argument as well
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
}
else {
// Otherwise, just the given exception as-is
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
}
catch (Throwable invocationEx) {
// Any other than the original exception is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (invocationEx != exception && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}
2.5、从ControllerAdvice获取到对应异常的处理方法,并把该方法封装成一个HandlerMethod返回,之后的过程便跟处理controller方法对应的HandlerMethod一样了。
@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// 如果在该类内部有@ExceptionHandler定义的异常处理方法,会优先使用该controller内部的
handlerType = handlerMethod.getBeanType();
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
// 从所有注册进来的ControllerAdvice中选取
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
//controllerAdvice的限定条件,包括是否限定包、类、注解
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
//获取处理该异常的方法
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}
return null;
}
3、现在只剩下一个疑点,如何解析@ControllerAdvice?
3.1、获取@ControllerAdvice
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
//初始化
initExceptionHandlerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// 获取所有包含@ControllerAdvice注解的bean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 把该bean封装到一个ExceptionHandlerMethodResolver中,并把@ExceptionHandler注解扫描到的异常与
//该注解对应的方法缓存到Map中
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
//如果含有@ExceptionHandler注解修饰的方法,则加入到缓存
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
//稍微带一笔,ResponseBodyAdvice也是在这一步做注入的
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
if (logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " +
handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
}
}
}
3.2、我们看一下如何把@ExceptionHandler对应异常与method缓存到map中new ExceptionHandlerMethodResolver(beanType)中。
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// 在该ControllerAdvice中找到所有包含@ExceptionHandler注解的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
// 获取该方法@ExceptionHandler注解包含的所有异常类型
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
//把异常与方法添加到缓存
addExceptionMapping(exceptionType, method);
}
}
}
至此,所有相关的源码流程解析完成。
注意:
1、解析controller方法的不同参数解析器对应产生的参数不合法的异常不同,因此@ExceptionHandler需要与之匹配。
2、@RestControllerAdvice与@ControllerAdvice的区别同@Controller与@RestController一样,都是返回值处理器不同。@RestControllerAdvice会直接输出json数据。
3、@ControllerAdvice捕获的异常不止参数校验,他捕获了所有DispatcherServlet.doDispatch()方法中的异常,因此也可以在这里做国际化输出