SpringMVC 异常处理系统

时间:2022-04-08 00:14:19

一、异常的分类

 

  异常层级图

  SpringMVC 异常处理系统

  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;  
        }  
    }  

}