spring mvc 异常处理

spring mvc 异常处理


spring 统一处理异常的3中方式:

  1. 使用 @ExceptionHandler 注解方法
  2. 实现 HandlerExceptionResolver 接口
  3. 使用 @controlleradvice

使用 @ExceptionHandler 注解

如果使用注解注释方法进行统一的异常处理的话,异常处理方法必须与抛出异常的方法在同一个controller

代码:

public abstract class ResponseExceptionResolver {
    /**
     * 这是定义的处理异常的方法
     * [@param](https://my.oschina.net/u/2303379) ex
     * [@return](https://my.oschina.net/u/556800)
     */
    @ExceptionHandler(BusinessException.class)
    public String resolver(BusinessException ex) {
        return null;
}

采用这种方式代码会很冗余,每个controller都要针对的对可能抛出的相同的异常做相同的处理,不过可以定义抽象的BaseController,对抛出的自定义异常在BaseController中做统一的异常处理。具体的资源controller可以继承BaseController,也能做到统一的异常解决,还能减少重复代码。

实现 HandlerExceptionResolver 接口

代码:

@Component
public class ResponseExceptionResolver implements HandlerExceptionResolver{

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        return null;
    }
}

这种方法也可以进行全局的异常控制,它要实现resolveException(..)方法,而且返回值就只能是ModelAndView类型的,在实现方法中如果想要是简单json格式或者html格式需要先构造一个ModelAndView出来,包装错误信息,返回到一个错误的页面,显示错误信息,这样的话比较麻烦。

使用 @controlleradvice 注解

在上面第一种方法说到采用注解@ExceptionHandler的方式需要跟想要处理异常的handleMethod在同一个controller中,不过可以使用@controlleradvice + @ExceptionHandler 的方式定义全局异常处理,在spring 4.3 以上版本可以使用 @RestControllerAdvice + @ExceptionHandler 两个注解,前者会帮你把异常处理后的返回值对象自动封装成json格式

  • @RestControllerAdvice 注解定义(相当于 @ControllerAdvice + @ResponseBody

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @ControllerAdvice
    @ResponseBody public @interface RestControllerAdvice { ... }

代码:

@RestControllerAdvice
public class ResponseExceptionResolver {

    @ExceptionHandler(BusinessException.class)
    public GenericResponse<String> resolver(BusinessException ex) {
        return new GenericResponse<>(ex.getCode(), ex.getMessage(), "");
    }
}

spring mvc 异常类注册源码解析

DispatcherServlet类装载:

public class DispatcherServlet extends FrameworkServlet {
	...
	// 重点:这是当前类目录下的一个property文件,在下面静态代码块中被使用
	private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

	...

	private static final Properties defaultStrategies;
	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try {

			// 重点:这里装配资源文件
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);


		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
		}
	}
	...
}

可以看到DispatcherServlet在类加载的时候会执行静态代码块的内容,加载当前类同级目录的一个DispatcherServlet.properties文件保存在defaultStrategies静态常量中

在查看DispatcherServlet的继承关系后,在servlet容器启动后经过几轮的方法调用后会执行DispatcherServlet类的initStrategies(..)方法(web.xml在web容易启动后依次的调用顺序是<context-param> --> <listener> --> <filter> --> <servlet>),至于容器启动后怎么调用的到DispatcherServletinitStrategies(..)方法的,自行看源码调用次序

这是DispatcherServlet的继承关系:

DispatcherServlet 初始化HandlerExceptionResolvers的过程:

public class DispatcherServlet extends FrameworkServlet {
	...
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);

		// 重点:注册 HandlerExceptionResolvers
		initHandlerExceptionResolvers(context);

		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
	...

	private void initHandlerExceptionResolvers(ApplicationContext context) {
		
		... 省略代码
		
		// 上面一段省略的代码的意思大概是在application上下文配置文件中查找HandlerExceptionResolvers配置
		...	

		// 重点:如果没有找到,那就去上文配置的那个DispatcherServlet.properties资源文件中去找到默认的配置进行注册
		// Ensure we have at least some HandlerExceptionResolvers, by registering
		// default HandlerExceptionResolvers if no other resolvers are found.
		if (this.handlerExceptionResolvers == null) {

			// 重点:这个方法里面便是通过反射包名初始化默认配置然后进行注册
			this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
			}
		}
	}
	...
	protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
		
		// 重点:这里通过类的全限定名去资源文件找
		String key = strategyInterface.getName();
		String value = defaultStrategies.getProperty(key);
		if (value != null) {
			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
			List<T> strategies = new ArrayList<T>(classNames.length);
			for (String className : classNames) {
				try {

					// 重点:找到之后就初始化了
					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
					Object strategy = createDefaultStrategy(context, clazz);
					strategies.add((T) strategy);
				}
				catch (ClassNotFoundException ex) {
					...
				}
				catch (LinkageError err) {
					...
				}
			}
			return strategies;
		}
		else {
			return new LinkedList<T>();
		}
	}

	...
}

DispatcherServlet.properties文件中HandlerExceptionResolver默认加载的实现类:

...
org.springframework.web.servlet.HandlerExceptionResolver=
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
...

这里便注册了默认的HandlerExceptionResolver

关于spring自己对于HandlerExceptionResolver的实现类详解可以参照:

http://www.cnblogs.com/fangjian0423/p/springMVC-exception-analysis.html

文章参考:

https://github.com/pzxwhc/MineKnowContainer/issues/29

转载于:https://my.oschina.net/JXinChenzZ/blog/1498947

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值