背景描述
代码语言:javascript复制web项目开发过程中和前期线上运行环境,总是会或多或少出现各种异常,
比较常见的是有些运行异常或者检测异常直接抛给了前端,导致前端页面
出现了一大坨的异常堆栈信息。
代码语言:javascript复制1.但是站在产品和用户的角度,不管你服务器端出现什么异常,
或者说崩溃的东西,和我没关系;
2.站在B/S交互的角度,你Server端的运行错误与否和我
Browser端没太大关联,我任何一个操作,
只有成功和失败两种结果,不存在一直加载中,同时我也不想
看到一大堆密密麻麻看不懂的东西。
3.客户端希望看到的,是服务器端处理成功或者失败对应
响应码和数据,或者处理异常被捕获后返回的提前约定的
错误码和错误信息
问题分析
代码语言:javascript复制 对于上述问题,我们有很多处理和解决方案,比如:1.在业务编码层面(主要针对service和dao层),做足够的参数
检验和逻辑判断,降低由于编码不严谨或者逻辑分析不到位,
导致的逻辑异常
2.在服务调用层面或者说和前端直接打交道层(主要针对
controller层),对基础的请求参数做合法性校验和鉴权,
对下层服务调用用try...catch包装,可以解决下层业务层面
的检测异常和运行异常,同时也可以捕获框架层面的调用异常,
例如使用rpc框架dubbo,我们可能会出现服务调用超时异常,
参数过大异常(传输参数超过dubbo默认大小或者自定义
配置大小),请求和响应序列化异常等等,如果我们不做处理,
这些异常直接就把堆栈信息抛给前端了
解决方案
代码语言:javascript复制 对于预知和不可预知的异常,从用户使用,到前端渲染,
到服务器业务处理以及最底层数据库查询和持久化分析,
我们可以得出很多互补的解决方案: 1.服务层(逻辑处理层service和dao)对逻辑做详尽的分析,
对参数做可靠地校验,对处理结果做好封装,对捕获的异常
仔细斟酌要不要向调用方抛出. 2.调用层(参数解析和鉴权层controller)对前端请求
参数做严谨的合法性校验(非空,格式)以及参数鉴权,
对调用服务用try...catch做异常捕获,对捕获的异常做
处理,返回给前端错误码和错误信息 3.交互层(和用户直接打交道的前端页面)对响应信息做
容错处理,对服务器返回的错误码和错误信息包装转换后
变成用户可以理解的信息展示出来.前端对服务请
求也做超时容错处理,并考虑做错误上报处理. 前边的处理方式,貌似是比较严谨的,但是还有一点
需要注意,就是从上述的2到3的过程中也可能出现异常,
也就是如果controller层处理不当,那么久把产生的异常
直接抛给了前端,这肯定是一种不合理的做法.
那么我们就要考虑的springmvc框架层面对控制器返回给
前端的结果做统一异常处理,这样的活我们的
异常处理 链,通过1和2以及下边的4步骤,一环扣一环,
做到所有的异常都在服务器端处理掉,
统一异常处理描述如下: 4.框架层统一异常处理机制,对控制器层所有的漏网之鱼
做拦截处理,然后跳转到
对应的错误页面,或者如果是前后端分离的话,可以处理后
返回给前端相应的错误码.
代码实现
代码语言:javascript复制 目前springmvc框架统一异常处理方案大概有三种,
下面将一一介绍其使用方式:
1.使用@ExceptionHandler进行处理
代码语言:javascript复制
代码语言:javascript复制直接在controller层具体的防范上加上@ExceptionHandler
注解,就可以对该方法可能抛出的异常做处理,
参考@ExceptionHandler源码:
123456789101112 | @Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ExceptionHandler {/*** Exceptions handled by the annotation method. If empty, will default* to any exceptions listed in the method argument list.*/Class<? extends Throwable>[] value() default {};} |
---|
发现此注解可以传一个参数,也就是具体的要拦截的异常类型
优点:1.灵活,可以配置到具体的方法层面 2.简单,不需要再做
多余的配置
缺点:在一个很庞大的网站或者系统中,每个方法上边都要
加注解,并且容易溜掉2.使用SimpleMappingExceptionResolver实现异常处理 在springmvc主配置文件中添加一下内容:
123456789101112131415 | <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><!-- 定义默认的异常处理页面,当该异常类型的注册时使用 --><property name="defaultErrorView" value="error"></property><!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception --><property name="exceptionAttribute" value="ex"></property><!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常也页名作为值 --><property name="exceptionMappings"><props><prop key="com.typhoon.spring_shiro.exception.BusinessException">error-business</prop><prop key="com.typhoon.spring_shiro.exception.ParameterException">error-parameter</prop><!-- 这里还可以继续扩展对不同异常类型的处理 --></props></property></bean> |
---|
优点:能拦截所有可能出现的异常,可以对自定义异常做特殊处理
缺点:①.需要添加配置 ②.在多人团队开发的多模块业务中,
如果有人添加了自定义异常,需要修改配置3.实现HandlerExceptionResolver接口自定义异常处理器 我们在web层自定义异常处理器,然后添加到配置中
代码实现
123456789101112131415161718192021222324252627282930313233343536 | /*** web层统一异常处理类** @author Typhoon* @date 2017-05-22 18:17* @since V2.0*/public class WebExceptionResolver implements HandlerExceptionResolver {private static final Logger LOGGER = LoggerFactory.getLogger(WebExceptionResolver.class); @Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { LOGGER.info("捕获web层异常",ex);Map<String, Object> results = new HashMap<String, Object>();results.put("code", "999");results.put("msg", "系统异常");if(ex instanceof UnauthenticatedException || ex instanceof UnauthorizedException) {//授权异常results.put("code", "403");results.put("msg", "无权操作");} ModelAndView mv = new ModelAndView();/* 使用FastJson提供的FastJsonJsonView视图返回,不需要捕获异常 */FastJsonJsonView view = new FastJsonJsonView();view.setAttributesMap(results);mv.setView(view);return mv;//// 根据不同错误转向不同页面//if(ex instanceof BusinessException) {//return new ModelAndView("error-business", model);//}else if(ex instanceof ParameterException) {//return new ModelAndView("error-parameter", model);//} else {//return new ModelAndView("error", model);//}}} |
---|
添加配置
12 | <!-- 统一异常捕获 --><bean id="exceptionResolver" class="com.typhoon.spring_shiro.controller.resolver.WebExceptionResolver"/> |
---|
优点:①可以自定义处理逻辑,根据不同的异常类型跳转到
不同的错误页面,也可以在前后端分离的情况下,
根据不同的异常返回给前端不同的错误码和错误信息
缺点:①有增加新的异常需要抛出,可能需要修改拦截
处理逻辑(例如:新增了退费逻辑,在退费异常或者
失败情况下前端需要根据响应码做提示或跳转,那么
就需要在拦截器层增加退费异常的判断)
②需要修改spring配置
总结
代码语言:javascript复制 不同的场景可以选择不同的异常拦截方式(在某些场景下
可以配合使用),但是不能一概而定,不要根据具体的
业务区做判定。
但是在zhenaiwang我们的处理方式是选择第三种拦截方式,
因为我们做了前后端分离,所有的前端请求都是返回的json,
这个时候自定义异常处理的优势有显现出来,
注解和通用配置略显不足. *ps*:欢迎各位大神指教和拍砖.如果能给您带来收获,那是我的荣幸!!!