@[TOC] SpringMVC has a complete set of exception handling system, this system is very useful, today Songo spend some time to talk with you about the exception handling system in SpringMVC, we will sort out the exception system in SpringMVC from beginning to end.

1. Overview of exception resolvers

In the exception architecture of SpringMVC, the top Boss is HandlerExceptionResolver, which is an interface with only one method:

public interface HandlerExceptionResolver {
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
Copy the code

The resolveException method resolves exceptions generated during request processing and returns a ModelAndView.

HandlerExceptionResolver implementation class

There are three classes that directly implement the HandlerExceptionResolver interface:

  • HandlerExceptionResolverComposite: this a look is a combination, in the latest source code analysis we have seen xxxComposite for many times, there is no longer here.

  • Terrorattributes: This is used to save exception attributes.

  • A subclass of AbstractHandlerExceptionResolver: the more:

    • SimpleMappingExceptionResolver: through configured in advance exception classes and corresponding relationship between the View to parse the anomalies.
    • AbstractHandlerMethodExceptionResolver: dealing with use@ExceptionHandlerAnnotate custom exception types.
    • Exception handling DefaultHandlerExceptionResolver: according to different types.
    • ResponseStatusExceptionResolver: dealt with@ResponseStatusAnnotation exception.

These are the general exception parsers in SpringMVC, so let’s take a look at each one.

2.AbstractHandlerExceptionResolver

AbstractHandlerExceptionResolver the anomalies of the parser is a real working parent class, we’ll look up from his resolveException method.

@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) {
			logException(ex, request);
		}
		return result;
	}
	else {
		return null; }}Copy the code
  1. We call shouldApplyTo to see if the current parser can handle the exception thrown by the passed handler. If not, we return NULL, which is passed to the next HandlerExceptionResolver.
  2. Call the prepareResponse method to process the response.
  3. Calling the doResolveException method actually handles the exception, which is a template method that is implemented in subclasses.
  4. Call the logException method to record the exception log information.

There’s nothing to say in logging exceptions, and doResolveException is an empty template method, so there are mainly two methods here: shouldApplyTo and prepareResponse, to look at separately.

shouldApplyTo

protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
	if(handler ! =null) {
		if (this.mappedHandlers ! =null && this.mappedHandlers.contains(handler)) {
			return true;
		}
		if (this.mappedHandlerClasses ! =null) {
			for(Class<? > handlerClass :this.mappedHandlerClasses) {
				if (handlerClass.isInstance(handler)) {
					return true; }}}}return! hasHandlerMappings(); }Copy the code

There are two objects involved: mappedHandlers and mappedHandlerClasses:

  • MappedHandlers: Store processor objects (controllers or methods in controllers)
  • MappedHandlerClasses: Stores the Class of the processor.

We can configure these two objects when we configure the exception resolver, so that the exception handler can only serve one processor, but generally there is no requirement for this, so you just need to know.

If the developer originally configured mappedHandlers or mappedHandlerClasses, they are compared to the handler, otherwise true is returned to indicate support for exception handling.

prepareResponse

The prepareResponse method is simpler and deals primarily with the cached fields of the response header.

protected void prepareResponse(Exception ex, HttpServletResponse response) {
	if (this.preventResponseCaching) { preventCaching(response); }}protected void preventCaching(HttpServletResponse response) {
	response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}
Copy the code

This is AbstractHandlerExceptionResolver roughly content, you can see is very easy, then we look at its implementation class.

2.1 AbstractHandlerMethodExceptionResolver

AbstractHandlerMethodExceptionResolver mainly rewrite the shouldApplyTo method and doResolveException method, one by one to see.

shouldApplyTo

@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
	if (handler == null) {
		return super.shouldApplyTo(request, null);
	}
	else if (handler instanceof HandlerMethod) {
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		handler = handlerMethod.getBean();
		return super.shouldApplyTo(request, handler);
	}
	else if (hasGlobalExceptionHandlers() && hasHandlerMappings()) {
		return super.shouldApplyTo(request, handler);
	}
	else {
		return false; }}Copy the code

There’s nothing to say about this feeling, and the judgment logic basically calls the parent’s shouldApplyTo method to handle it.

doResolveException

@Override
@Nullable
protected final ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
	HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
	return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}
@Nullable
protected abstract ModelAndView doResolveHandlerMethodException(
		HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);
Copy the code

DoResolveException is specific exception handling methods, but it inside have no substantial operation, specific to doResolveHandlerMethodException ways to do something, and this method is an abstract method, concrete implementation in the subclass.

2.1.1 ExceptionHandlerExceptionResolver

Only a subclass is ExceptionHandlerExceptionResolver AbstractHandlerMethodExceptionResolver, Look at its doResolveHandlerMethodException method:

@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
		HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
	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();
	ArrayList<Throwable> exceptions = new ArrayList<>();
	try {
		if (logger.isDebugEnabled()) {
			logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
		}
		// Expose causes as provided arguments as well
		Throwable exToExpose = exception;
		while(exToExpose ! =null) { exceptions.add(exToExpose); Throwable cause = exToExpose.getCause(); exToExpose = (cause ! = exToExpose ? cause :null);
		}
		Object[] arguments = new Object[exceptions.size() + 1];
		exceptions.toArray(arguments);  // efficient arraycopy call in ArrayList
		arguments[arguments.length - 1] = handlerMethod;
		exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
	}
	catch (Throwable invocationEx) {
		// Any other than the original exception (or a cause) is unintended here,
		// probably an accident (e.g. failed assertion or the like).
		if(! exceptions.contains(invocationEx) && 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 instanceofRedirectAttributes) { Map<String, ? > flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); }returnmav; }}Copy the code

This method is long, but easy to understand:

  1. First, find the string@ExceptionHandlerAnnotation methods, encapsulated into a ServletInvocableHandlerMethod object (about ServletInvocableHandlerMethod object, the scene has been introduced in the previous article, specific see:Can methods that Spring Boot defines interfaces be declared private?).
  2. If a method is found, configure a parameter parser for exceptionHandlerMethod, a view parser, and so on. For these parsers, see Songo’s previous article: How to customize parameter parsers in SpringBoot. , in-depth analysis of SpringMVC parameter parser, how to unify API interface response format in Spring Boot? .
  3. Next, define an Exceptions array, and if an exception occurs with an exception chain, store the entire exception chain in the Exceptions array.
  4. The Exceptions array, together with the handlerMethod, makes up the method argument, calledexceptionHandlerMethod.invokeAndHandleThe custom exception method is executed, and the result is saved in mavContainer.
  5. If the request ends here, a ModelAndView is constructed and returned.
  6. Otherwise take the information out of mavContainer and build a new ModelAndView to return. Also, save redirection parameters if they exist (for redirection parameters, see: How can SpringMVC parameters be passed? Up posture! .

This is the general ExceptionHandlerExceptionResolver workflow, you can see, is very easy.

2.2 DefaultHandlerExceptionResolver

This is a default exception handler that handles some common types of exceptions. Let’s look at its doResolveException method:

@Override
@Nullable
protected ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
	try {
		if (ex instanceof HttpRequestMethodNotSupportedException) {
			return handleHttpRequestMethodNotSupported(
					(HttpRequestMethodNotSupportedException) ex, request, response, handler);
		}
		else if (ex instanceof HttpMediaTypeNotSupportedException) {
			return handleHttpMediaTypeNotSupported(
					(HttpMediaTypeNotSupportedException) ex, request, response, handler);
		}
		else if (ex instanceof HttpMediaTypeNotAcceptableException) {
			return handleHttpMediaTypeNotAcceptable(
					(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
		}
		else if (ex instanceof MissingPathVariableException) {
			return handleMissingPathVariable(
					(MissingPathVariableException) ex, request, response, handler);
		}
		else if (ex instanceof MissingServletRequestParameterException) {
			return handleMissingServletRequestParameter(
					(MissingServletRequestParameterException) ex, request, response, handler);
		}
		else if (ex instanceof ServletRequestBindingException) {
			return handleServletRequestBindingException(
					(ServletRequestBindingException) ex, request, response, handler);
		}
		else if (ex instanceof ConversionNotSupportedException) {
			return handleConversionNotSupported(
					(ConversionNotSupportedException) ex, request, response, handler);
		}
		else if (ex instanceof TypeMismatchException) {
			return handleTypeMismatch(
					(TypeMismatchException) ex, request, response, handler);
		}
		else if (ex instanceof HttpMessageNotReadableException) {
			return handleHttpMessageNotReadable(
					(HttpMessageNotReadableException) ex, request, response, handler);
		}
		else if (ex instanceof HttpMessageNotWritableException) {
			return handleHttpMessageNotWritable(
					(HttpMessageNotWritableException) ex, request, response, handler);
		}
		else if (ex instanceof MethodArgumentNotValidException) {
			return handleMethodArgumentNotValidException(
					(MethodArgumentNotValidException) ex, request, response, handler);
		}
		else if (ex instanceof MissingServletRequestPartException) {
			return handleMissingServletRequestPartException(
					(MissingServletRequestPartException) ex, request, response, handler);
		}
		else if (ex instanceof BindException) {
			return handleBindException((BindException) ex, request, response, handler);
		}
		else if (ex instanceof NoHandlerFoundException) {
			return handleNoHandlerFoundException(
					(NoHandlerFoundException) ex, request, response, handler);
		}
		else if (ex instanceof AsyncRequestTimeoutException) {
			returnhandleAsyncRequestTimeoutException( (AsyncRequestTimeoutException) ex, request, response, handler); }}catch (Exception handlerEx) {
	}
	return null;
}
Copy the code

As you can see, you are essentially calling different classes to handle the exception based on the type of exception. Here related processing easier to HttpRequestMethodNotSupportedException as an example, the exception handling is the response object to do some configuration, as follows:

protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
	String[] supportedMethods = ex.getSupportedMethods();
	if(supportedMethods ! =null) {
		response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ","));
	}
	response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
	return new ModelAndView();
}
Copy the code

Configure the response header, then sendError, and finally return an empty ModelAndView object.

In fact, the elder brother exception processing methods are similar, Songko will not repeat it.

2.3 ResponseStatusExceptionResolver

This is used to handle exceptions of type ResponseStatusException, or a normal exception class that uses the @responseStatus annotation. Let’s look at its doResolveException method:

@Override
@Nullable
protected ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
	try {
		if (ex instanceof ResponseStatusException) {
			return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
		}
		ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
		if(status ! =null) {
			return resolveResponseStatus(status, request, response, handler, ex);
		}
		if (ex.getCause() instanceof Exception) {
			returndoResolveException(request, response, handler, (Exception) ex.getCause()); }}catch (Exception resolveEx) {
	}
	return null;
}
Copy the code

As you can see, the first figure out whether exception type ResponseStatusException, if it is, will directly call resolveResponseStatusException method for abnormal information processing, if not, Find the @responseStatus annotation on the exception class and find the relevant exception information from it. Then call resolveResponseStatus to handle it.

As you can see, ResponseStatusExceptionResolver handle the exception type has two kinds:

  • An exception class directly inherited from ResponseStatusException, which can extract the desired information directly from it.
  • through@ResponseStatusAnnotated ordinary exception classes, in which case exception information from@ResponseStatusExtracted from annotations.

This one’s easy. There’s nothing to talk about.

2.4 SimpleMappingExceptionResolver

SimpleMappingExceptionResolver is according to the different display different error page. May be some friend unused SimpleMappingExceptionResolver, so talk simple first usage scene here.

SimpleMappingExceptionResolver configuration is very simple, provide a SimpleMappingExceptionResolver instance directly, as follows:

@Bean
SimpleMappingExceptionResolver simpleMappingExceptionResolver(a) {
    SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
    Properties mappings = new Properties();
    mappings.put("java.lang.ArithmeticException"."11");
    mappings.put("java.lang.NullPointerException"."22");
    resolver.setExceptionMappings(mappings);
    Properties statusCodes = new Properties();
    statusCodes.put("11"."500");
    statusCodes.put("22"."500");
    resolver.setStatusCodes(statusCodes);
    return resolver;
}
Copy the code

In mappings, configure the mappings between exceptions and Views. Write the full path of the exception class. 11 and 22 after the mappings represent the view names. The mapping between the view and the response statusCodes is configured in statusCodes. After configuration, our project will display 11 views if it throws a ArithmeticException exception at run time, and 22 views if it throws a NullPointerException exception at run time.

This is the usage, so we can look at the source code to make it easier to understand. Let’s go straight to the doResolveException method:

@Override
@Nullable
protected ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
	String viewName = determineViewName(ex, request);
	if(viewName ! =null) {
		Integer statusCode = determineStatusCode(request, viewName);
		if(statusCode ! =null) {
			applyStatusCodeIfPossible(request, response, statusCode);
		}
		return getModelAndView(viewName, ex, request);
	}
	else {
		return null; }}Copy the code
  1. The view name is first determined by calling the determineViewName method.
  2. Next, call determineStatusCode to see if the view has a corresponding statusCode.
  3. Call applyStatusCodeIfPossible method sets the statusCode to the response, this method is very simple, not much said.
  4. Call getModelAndView method to construct a ModelAndView object return, during construction, at the same time set the exception parameter, the exception information key default is exception.

There are two long ways of doing this, and Songo needs to say a few extra words here.

determineViewName

This is to find the view name based on the exception type. Let’s look at the specific search method:

@Nullable
protected String determineViewName(Exception ex, HttpServletRequest request) {
	String viewName = null;
	if (this.excludedExceptions ! =null) {
		for(Class<? > excludedEx :this.excludedExceptions) {
			if (excludedEx.equals(ex.getClass())) {
				return null; }}}if (this.exceptionMappings ! =null) {
		viewName = findMatchingViewName(this.exceptionMappings, ex);
	}
	if (viewName == null && this.defaultErrorView ! =null) {
		viewName = this.defaultErrorView;
	}
	return viewName;
}
Copy the code
  1. If the current exception is included in excludedExceptions, null is returned (meaning the current exception is ignored and the default is used).
  2. If exceptionMappings is not NULL, the findMatchingViewName method is called to find the corresponding view name (the exceptionMappings variable is the previous mapping). This is done by iterating through the mapping table we configured earlier.
  3. If no corresponding viewName is found and the user is configured with defaultErrorView, assign It to viewName and return viewName.

determineStatusCode

@Nullable
protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
	if (this.statusCodes.containsKey(viewName)) {
		return this.statusCodes.get(viewName);
	}
	return this.defaultStatusCode;
}
Copy the code

This is easier: go to statusCodes to see if there is a view’s corresponding status code, and return it if there is, or return a default one if there is not.

3.HandlerExceptionResolverComposite

Finally, there is a HandlerExceptionResolverComposite needs and introduce, this is a combination of the exception handler, the exception handler to the agent which really working.

@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) {
				returnmav; }}}return null;
}
Copy the code

The resolveException method is simpler, and we’ve seen it many times before.

4. Summary

Well, today I will briefly talk about the exception handling system in SpringMVC. It is not difficult as a whole, and you can taste it carefully.