springboot下全局异常的拦截

 springboot下的异常拦截,通常可以通过在每个节点引入一个exceptionHandler来实现,使用@ControllerAdvice在类上来声明这是一个controller层的异常捕获器,使用@ExceptionHandler在方法上声明捕获什么类型的异常,两者搭配使用已经可以解决大部分的异常问题.     


/**
 * @author liucw
 * 全局捕获异常类,只要作用在@RequestMapping上,所有的异常都会被捕获
 */
@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHanlder {

   /* @Autowired
    private LogClient logClient;*/

    /**
     * 手工调用数据验证不通过
     */
    @ExceptionHandler(value = ValidException.class)
    public Result handleValidException(ValidException e) {
        Result errorResult = new Result();
        errorResult.setCode(ResultCodeEnum.EROR_DATA);
        errorResult.setMsg(e.getMessage());
        return errorResult;
    }

}

  如果想要捕获service层的异常,通常需要额外自定义一个异常类,然后在@ExceptionHandler捕获自定义的异常.

 

public class BusinessException extends RuntimeException {
    private Integer code;
    private String message;

    public BusinessException(ResultCodeEnum errorCode) {
        super(errorCode.getMsg());
        this.code = errorCode.getCode();
        this.message=errorCode.getMsg();
    }

}

 然后拦截的时候使用@ExceptionHandler(BusinessException.class)即可.

     实际项目中经常会有在filter抛出的异常,例如我们项目在gateway的OncePerRequestFilter中做权限校验时抛出了很多自定义的异常,这些异常时无法被全局异常捕捉到的.通常来说,在springboot项目中如果想要拦截filter的异常,只需让filter继承zuulfilter类,然后声明他的filterType类型是error,那么他就能捕获所有继承zuulFilter并且filtertaye类别不是error的所有filter抛出的异常.由此可以看出,他只能拦截继承zuulfilter的抛出去的异常,并不能拦截通用的filter的异常.

    所幸springboot本身提供了一个全局异常拦截器AbstractErrorController,我们可以仿造他写一个.

/*
 * Copyright 2012-2018 the original author or authors.
 *
 * 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 org.springframework.boot.autoconfigure.web.servlet.error;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

/**
 * Basic global error {@link Controller}, rendering {@link ErrorAttributes}. More specific
 * errors can be handled either using Spring MVC abstractions (e.g.
 * {@code @ExceptionHandler}) or by adding servlet
 * {@link AbstractServletWebServerFactory#setErrorPages server error pages}.
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Michael Stummvoll
 * @author Stephane Nicoll
 * @see ErrorAttributes
 * @see ErrorProperties
 */
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

	private final ErrorProperties errorProperties;

	/**
	 * Create a new {@link BasicErrorController} instance.
	 * @param errorAttributes the error attributes
	 * @param errorProperties configuration properties
	 */
	public BasicErrorController(ErrorAttributes errorAttributes,
			ErrorProperties errorProperties) {
		this(errorAttributes, errorProperties, Collections.emptyList());
	}

	/**
	 * Create a new {@link BasicErrorController} instance.
	 * @param errorAttributes the error attributes
	 * @param errorProperties configuration properties
	 * @param errorViewResolvers error view resolvers
	 */
	public BasicErrorController(ErrorAttributes errorAttributes,
			ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
		super(errorAttributes, errorViewResolvers);
		Assert.notNull(errorProperties, "ErrorProperties must not be null");
		this.errorProperties = errorProperties;
	}

	@Override
	public String getErrorPath() {
		return this.errorProperties.getPath();
	}

	@RequestMapping(produces = "text/html")
	public ModelAndView errorHtml(HttpServletRequest request,
			HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
				request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

	@RequestMapping
	@ResponseBody
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<>(body, status);
	}

	/**
	 * Determine if the stacktrace attribute should be included.
	 * @param request the source request
	 * @param produces the media type produced (or {@code MediaType.ALL})
	 * @return if the stacktrace attribute should be included
	 */
	protected boolean isIncludeStackTrace(HttpServletRequest request,
			MediaType produces) {
		IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
		if (include == IncludeStacktrace.ALWAYS) {
			return true;
		}
		if (include == IncludeStacktrace.ON_TRACE_PARAM) {
			return getTraceParameter(request);
		}
		return false;
	}

	/**
	 * Provide access to the error properties.
	 * @return the error properties
	 */
	protected ErrorProperties getErrorProperties() {
		return this.errorProperties;
	}

}

这是一个实现类AbstractErrorController的异常捕获类,我们可以仿造他写一个

package com.gpm.gateway.controller;

import com.gpm.common.exception.BusinessException;
import com.gpm.common.exception.OauthException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 用于拦截filter的异常,然后再次抛出给全局异常捕获
 * @ClassName:GlobalExceptionController
 * @Description:TODO
 * @Author:linda
 * @Date:2019-7-9
 */
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
@Slf4j
public class GlobalExceptionController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

    private final ErrorAttributes errorAttributes;

    private final static  String EXCEPTION_NAME ="exception";
    private final static  String OAUTH_EXCEPTION_NAME ="OauthException";
    private final static  String BUSINESS_EXCEPTION_NAME ="BusinessException";
    private final static  String ILLEGAL_ARGUMENT_EXCEPTION_NAME ="IllegalArgumentException";


    public GlobalExceptionController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
        super(errorAttributes);
        this.errorProperties = serverProperties.getError();
        this.errorAttributes = new DefaultErrorAttributes(true);
    }

    @Override
    public String getErrorPath() {
        return errorProperties.getPath();
    }

    @RequestMapping
    @ResponseBody
    public void error(HttpServletRequest request) throws Exception {
        Map<String, Object> errorMap = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.APPLICATION_JSON));
        Object exception = errorMap.get(EXCEPTION_NAME);
        String errorMessage = errorMap.get("message").toString();
        if(exception==null){
          throw  new BusinessException(errorMessage);
        }else{
            String exceptionName = exception.toString();
//            String exceptionString = exceptionName.substring(exception.toString().lastIndexOf("."), exceptionName.length() - 1);
          throw new Exception();
//            int i = exception.toString().lastIndexOf(".");
            if(exceptionName.contains(OAUTH_EXCEPTION_NAME )){
                throw  new OauthException(errorMessage);
            }else if(exceptionName.contains(BUSINESS_EXCEPTION_NAME)){
                throw  new BusinessException(errorMessage);
            }else if(exceptionName.contains(ILLEGAL_ARGUMENT_EXCEPTION_NAME)){
                throw  new IllegalArgumentException(errorMessage);
            }else{
                throw new Exception(errorMessage);
            }
        }
    }


    @Override
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
                                                     boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }


    private boolean isIncludeStackTrace(HttpServletRequest request,
                                        MediaType produces) {
        ErrorProperties.IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }

    private ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }

}

有个细节和BasicErrorController有所差别,就是构造器的注入上,

  public GlobalExceptionController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
        super(errorAttributes);
        this.errorProperties = serverProperties.getError();
        this.errorAttributes = new DefaultErrorAttributes(true);
    }

我们自己注入的可以看到有这么一句this.errorAttributes = new DefaultErrorAttributes(true).我特意注入的是DefaultErr

orAttributes而不是errorAttributes,原因就在于在AbstractErrorController的

protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
			boolean includeStackTrace) {
		WebRequest webRequest = new ServletWebRequest(request);
		return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
	}

方法里面调用了DefaultErrorAttributes的

@Override
	public Map<String, Object> getErrorAttributes(WebRequest webRequest,
			boolean includeStackTrace) {
		Map<String, Object> errorAttributes = new LinkedHashMap<>();
		errorAttributes.put("timestamp", new Date());
		addStatus(errorAttributes, webRequest);
		addErrorDetails(errorAttributes, webRequest, includeStackTrace);
		addPath(errorAttributes, webRequest);
		return errorAttributes;
	}

这个方法里的

private void addErrorDetails(Map<String, Object> errorAttributes,
			WebRequest webRequest, boolean includeStackTrace) {
		Throwable error = getError(webRequest);
		if (error != null) {
			while (error instanceof ServletException && error.getCause() != null) {
				error = ((ServletException) error).getCause();
			}
			if (this.includeException) {
				errorAttributes.put("exception", error.getClass().getName());
			}
			addErrorMessage(errorAttributes, error);
			if (includeStackTrace) {
				addStackTrace(errorAttributes, error);
			}
		}
		Object message = getAttribute(webRequest, "javax.servlet.error.message");
		if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
				&& !(error instanceof BindingResult)) {
			errorAttributes.put("message",
					StringUtils.isEmpty(message) ? "No message available" : message);
		}
	}

判断了是否要把exception的信息放入进map.判断的条件就是这个this.includeException,而这个是一开始

	/**
	 * Create a new {@link DefaultErrorAttributes} instance.
	 * @param includeException whether to include the "exception" attribute
	 */
	public DefaultErrorAttributes(boolean includeException) {
		this.includeException = includeException;
	}

构造注入的,所以我们才采用DefaultErrorAttributes作为我们自定义的GlobalExceptionController 的属性注入.没错费了一大圈,其实就是为了获得返回的这个map信息中包含了exception信息,这个exception可以获取抛出的异常类别,否则map中是没有这个属性的,不利于我们自己去实现异常的逻辑判断.此时我们再手动抛出异常,全局异常拦截器就可以拦截了.其实还有一个方案,就是直接在这里进行全局异常的拦截,不需要全局异常拦截器了.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值