spring 全局异常处理机制
我们在做业务开发或者框架开发的时候,常常涉及到异常的处理。通常会定义一个和当前业务相关的统一异常,业务层涉及到异常的处理常常把异常统一抛出的控制层。控制层在每个接口中单独处理try{}catch(Exception e){} 。这样做存在以下几个问题
- 异常捕获冗余
- 有的开发同学可能忘了在控制层处理异常
- 后期需要统一异常拦截时,代码改动太多
其实在spring 中我们大可不必这样做,spring 已经尽量的帮开发人员减少工作量了。今天我们一起来分析一下spring到底是如何做到的。
接口方式
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
开发异常处理器只需要集成它
@Component
public class BaseRuntimeExceptionHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try{
if(ex instanceof BizRuntimeException){
BizRuntimeException bx = (BizRuntimeException)ex;
response.getWriter().println(bx.getMessage());
}else {
response.getWriter().println(ex.getMessage());
}
}catch (Exception e){
return new ModelAndView();
}
return new ModelAndView();
}
}
配置成spring的bean即可
DispatcherServlet中初始化9大对象
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
//初始化异常处理器
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
处理过程中可能会碰到异常经过多了异常处理器处理,这个时候需要把多个异常处理器,spring 中提供了org.springframework.web.servlet.handler.HandlerExceptionResolverComposite这个类。这个类是异常的聚合类,可以把多个异常聚合在一起。
public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
@Nullable
private List<HandlerExceptionResolver> resolvers;
private int order = Ordered.LOWEST_PRECEDENCE;
public void setExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.resolvers = exceptionResolvers;
}
public List<HandlerExceptionResolver> getExceptionResolvers() {
return (this.resolvers != null ? Collections.unmodifiableList(this.resolvers) : Collections.emptyList());
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@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;
}
}
这个类内部维护了**private List resolvers;**这个存的就是我们自定义的异常处理器
配置异常处理器到HandlerExceptionResolverComposite
@SpringBootApplication
public class SofaRpcConsumerApplication extends WebMvcConfigurationSupport {
public static void main(String[] args) throws IOException {
SpringApplication.run(SofaRpcConsumerApplication.class, args);
}
@Autowired
private BaseOneRuntimeExceptionHandlerExceptionResolver baseOneRuntimeExceptionHandlerExceptionResolver;
@Autowired
private BaseRuntimeExceptionHandlerExceptionResolver baseRuntimeExceptionHandlerExceptionResolver;
@Override
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add(baseOneRuntimeExceptionHandlerExceptionResolver);
exceptionResolvers.add(baseRuntimeExceptionHandlerExceptionResolver);
}
注解方式
重点说明一下注解的方式如何配置异常处理器,以及spring是如何实现
@ControllerAdvice
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {};
}
从定义中可以看出@ControllerAdvice注解是@Component注释的派生,只能放到类上
@RestControllerAdvice
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
@AliasFor(annotation = ControllerAdvice.class)
String[] value() default {};
@AliasFor(annotation = ControllerAdvice.class)
String[] basePackages() default {};
@AliasFor(annotation = ControllerAdvice.class)
Class<?>[] basePackageClasses() default {};
@AliasFor(annotation = ControllerAdvice.class)
Class<?>[] assignableTypes() default {};
@AliasFor(annotation = ControllerAdvice.class)
Class<? extends Annotation>[] annotations() default {};
}
@ExceptionHandler
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};
}
@ExceptionHandler中的value可以指定要处理的异常
处理异常代码
@RestControllerAdvice
public class BaseRuntimeExceptionHandlerExceptionResolver{
@ExceptionHandler
public ReturnV handException(Exception e){
if(e instanceof BizRuntimeException){
return ReturnV.fail;
}else {
return ReturnV.fail;
}
}
}
接下来分析一下spring是如何初始化和执行的
初始化
异常处理器ExceptionHandlerExceptionResolver
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {}
通过类的继承关系看到ExceptionHandlerExceptionResolver继承了
InitializingBean,
@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);
}
}
具体看initExceptionHandlerAdviceCache();
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
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");
}
}
}
ControllerAdviceBean是处理@ControllerAdvice和映射注解的,通过 findAnnotatedBeans 方法找到IOC容器中标注@ControllerAdvice注解的bean
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
if (!ScopedProxyUtils.isScopedTarget(name)) {
ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
if (controllerAdvice != null) {
// Use the @ControllerAdvice annotation found by findAnnotationOnBean()
// in order to avoid a subsequent lookup of the same annotation.
adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
}
}
}
OrderComparator.sort(adviceBeans);
return adviceBeans;
}
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
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");
}
}
}
重点的这个异常方法处理器ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
这个构造会把当前类中标注@ExceptionHandler注解的方法找到映射到内存,最后也会把this.exceptionHandlerAdviceCache.put(adviceBean, resolver)
ControllerAdviceBean和ExceptionHandlerMethodResolver绑定再一起
执行
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();
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 (or its cause) is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (invocationEx != exception && invocationEx != exception.getCause() && 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;
}
}
具体的逻辑都在上面的代码中
-
按照类型标注@ControllerAdvice类型找到对应的ExceptionHandlerMethodResolver,再按照异常类型找到对应的Method封装成ServletInvocableHandlerMethod,如果配置对应的HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler时绑定对应的参数处理器和返回值处理器
-
得到抛出的异常,经过参数处理器的处理,经过反射调用。返回值经过返回值处理器。完成调用
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
@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);
}
执行流程中涉及到一些本地缓存,没具体展开。
整个spring全局异常处理到此。微信公众号奋进的IT创业者