一、异常的分类
异常层级图
Error:Error类层次描述了Java运行时系统的内部错误和资源耗尽错误。对于这类错误是无法难通过程序来解决的,所以程序不应该抛出这种类型的对象。如果出现了这样的内部错误,除了通知给用户,并尽力使程序安全地终止。当然这类情况是很少出现的
Exception:分为运行时异常和检查性异常。
检查性异常:我们经常遇到的IO异常及sql异常就属于检查式异常。对于这种异常,Java编译器要求我们必须对出现的这些异常进行catch 所以 面对这种异常不管我们是否愿意,只能自己去写一堆catch来捕捉这些异常。常见的查检性异常有:FileNotFoundException 文件不存在异常、 SQLException SQL异常等。
二、异常的基础逻辑
问题:对于异常的处理有两种,一种是捕获处理,一种是向上抛出异常。
捕获异常:
优点:在业务逻辑或者方法体内部处理,可以正常执行业务逻辑,也可以控制事务的回滚,尽量不影响上层业务
缺点:异常信息的采集不方便处理
抛出异常:
优点:现在业务拆分,使用控制层、业务层、数据层,可以将数据层、业务层的运行时异常抛出到控制层,然后控制层统一做异常处理,方便管理;
缺点:业务没有内部处理,只能中断。
三、SpringMVC异常统一处理
1、HandlerExceptionResolver接口
HandlerExceptionResolver接口中定义了一个resolveException方法,用于处理Controller中的异常。Exception ex参数即Controller抛出的异常。返回值类型是ModelAndView,可以通过这个返回值来设置异常时显示的页面。
package com.qunar.advertisement.exception; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import com.qunar.advertisement.utils.StringPrintWriter; public class QADHandlerExceptionResolver implements HandlerExceptionResolver{ private static Logger logger = Logger.getLogger(QADHandlerExceptionResolver.class); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { logger.error("Catch Exception: ",ex);//把漏网的异常信息记入日志 Map<String,Object> map = new HashMap<String,Object>(); StringPrintWriter strintPrintWriter = new StringPrintWriter(); ex.printStackTrace(strintPrintWriter); map.put("errorMsg", strintPrintWriter.getString());//将错误信息传递给view return new ModelAndView("error",map); } }
返回结果也可以是一个json字符串,不用强制是ModeAndView界面
package com.qunar.advertisement.exception; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import com.qunar.advertisement.utils.StringPrintWriter; public class QADHandlerExceptionResolver implements HandlerExceptionResolver{ private static Logger logger = Logger.getLogger(QADHandlerExceptionResolver.class); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { logger.error("Catch Exception: ",ex);//把漏网的异常信息记入日志 return responseWrite(response, rspException.toString()); } /** * * @Title: responseWrite * @Description: 异步响应打印 * @param response * @param jsonMsg * @return * @throws IOException:Object * @throws */ public ModelAndView responseWrite(HttpServletResponse response,String jsonMsg) throws IOException{ Writer writer = null; try { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); writer = response.getWriter(); writer.write(jsonMsg); writer.flush(); writer.close(); } catch (Exception e) { e.printStackTrace(); }finally { if(writer != null){ writer.close(); } } return null; } }
四、获取异常的堆栈消息
/** * * @Title: getStackTrace * @Description: 根据异常对象获取异常堆栈信息 * @param exception * @return:String * @throws */ public static String getStackTrace(Throwable exception) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(buf, true); try { exception.printStackTrace(pw); return buf.toString(); } catch (Exception e) { e.printStackTrace(); } finally { try { buf.close(); pw.close(); } catch (IOException e) { e.printStackTrace(); } } return ""; }
五、、解决使用dubbo,消费者无法获取提供者自定义异常
/* * Copyright 1999-2011 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.dubbo.rpc.filter; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import com.alibaba.dubbo.common.Constants; import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.common.logger.Logger; import com.alibaba.dubbo.common.logger.LoggerFactory; import com.alibaba.dubbo.common.utils.ReflectUtils; import com.alibaba.dubbo.common.utils.StringUtils; import com.alibaba.dubbo.rpc.Filter; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcContext; import com.alibaba.dubbo.rpc.RpcException; import com.alibaba.dubbo.rpc.RpcResult; import com.alibaba.dubbo.rpc.service.GenericService; import com.guduo.common.constant.CommonConstant; import com.guduo.common.utils.common.GuDuoExceptionUtils; /** * ExceptionInvokerFilter * 此类是为了覆盖dubbo的ExceptionFilter类--请勿随意删除 * 删除将导致消费者端捕获自定义异常 * 添加了忽略自定义异常的exception * @author william.liangf */ @Activate(group = Constants.PROVIDER) public class ExceptionFilter implements Filter { private final Logger logger; public ExceptionFilter() { this(LoggerFactory.getLogger(ExceptionFilter.class)); } public ExceptionFilter(Logger logger) { this.logger = logger; } public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { try { Result result = invoker.invoke(invocation); if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // 如果是checked异常,直接抛出 if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // 在方法签名上有声明,直接抛出 try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class<?>[] exceptionClassses = method.getExceptionTypes(); for (Class<?> exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } // 未在方法签名上定义的异常,在服务器端打印ERROR日志 logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception); // 异常类和接口类在同一jar包里,直接抛出 String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){ return result; } // 是JDK自带的异常,直接抛出 String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // 执行自定义忽略的异常过滤规则 Map<String,Object> paramsMap = new HashMap<>(); paramsMap.put(CommonConstant.CLASS_NAME, className); if(GuDuoExceptionUtils.isIgnoreExceptionFilter(paramsMap)){ return result; } // 是Dubbo本身的异常,直接抛出 if (exception instanceof RpcException) { return result; } // 否则,包装成RuntimeException抛给客户端 return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); return result; } } return result; } catch (RuntimeException e) { logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); throw e; } } }