1. 程式人生 > 實用技巧 >【SpringBoot】 Aspect,Filter、Interceptor、ControllerAdvice區別——記一次RestControllerAdvice無法攔截Filter內丟擲異常

【SpringBoot】 Aspect,Filter、Interceptor、ControllerAdvice區別——記一次RestControllerAdvice無法攔截Filter內丟擲異常

記一次RestControllerAdvice無法攔截Filter內丟擲異常

今天有同事用到Shiro使用JWT的時候在Filter裡做身份驗證,然後在裡面catch捕獲並丟擲了自定義異常。我們這邊是用的RestControllerAdvice做統一異常處理,然後這個異常並沒有被RestControllerAdvice所攔截到

原因

請求進來 會按照 filter -> interceptor -> controllerAdvice -> aspect -> controller的順序呼叫

當controller返回異常 也會按照controller -> aspect -> controllerAdvice -> interceptor -> filter來依次丟擲

這種Filter發生的404、405、500錯誤都會到Spring預設的異常處理。如果你在配置檔案配置了server.error.path的話,就會使用你配置的異常處理地址,如果沒有就會使用你配置的error.path路徑地址,如果還是沒有,預設使用/error來作為發生異常的處理地址。如果想要替換預設的非Controller異常處理直接實現Spring提供的ErrorController介面就行了

解決方案

新建一個ErrorControllerImpl 實現ErrorController 把ErrorPath 指向error 再寫一個方法把Error丟擲 然後Controller全域性統一異常處理RestControllerAdvice就能捕獲到異常了


import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

/**
 * @author Joe
 * createTime 2020/07/27 14:39
 * mail [email protected]
 */
@Controller
public class ErrorControllerImpl implements ErrorController {

    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping("/error")
  	
    public void handleError(HttpServletRequest request) throws Throwable {
        if (request.getAttribute("javax.servlet.error.exception") != null) {
            throw (Throwable) request.getAttribute("javax.servlet.error.exception");
        }
    }
}

參考stackoverflow連結 https://stackoverflow.com/questions/34595605/how-to-manage-exceptions-thrown-in-filters-in-spring

1、切面(Aspect)

是什麼

切面,指切入目標方法,擴充套件目標方法功能,卻不修改目標方法程式碼,將擴充套件功能程式碼從目標方法程式碼中分離出來。可以針對方法級攔截,並獲得方法的引數和返回值,但獲取不到http請求,多配合註解使用。

應用場景

• 許可權校驗

•欄位加密解密

怎麼用

package com.dy.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * this is a acpect
 * 切入點
 * 在那些方法上起作用
 * 在什麼時候起作用
 *
 * @author duoyuan
 * @create 2019-12-20 20:52
 **/
@Aspect
@Component
public class TimeAspect {
    @Around("execution(* com.duoyuan.controller.UserController.*(..))")
    public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("time aspect start");
        Object[] args = proceedingJoinPoint.getArgs();
        for (Object arg : args) {
            System.out.println(arg.getClass().getName());
            System.out.println("arg is " + arg);
        }
        long startTime = new Date().getTime();
        Object obj = proceedingJoinPoint.proceed();
        System.out.println("time aspect 耗時" + (new Date().getTime() - startTime));
        System.out.println("time aspect end");
        return obj;
    }
}

2、過濾器(Filter)

是什麼

過濾器就是一個實現了特殊介面的Java類,實現對請求資源的過濾的功能。和框架無關,在所有過濾元件的最外層,顆粒度比較大。

應用場景

•驗證token合法性、有效性

怎麼用

springboot中一般有兩種配置方式:

(1)直接實現java.servlet.Filter介面

package com.dy.webFilter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;
import java.util.Date;

@Component
public class TimerFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Time  filter init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Time filter start");
        long startTime = new Date().getTime();
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("time filter:"+(new Date().getTime()-startTime));
        System.out.println("time filter finish");
    }

    @Override
    public void destroy() {
        System.out.println("Time filter destroy");
    }
}

(2)在WebConfig中配置,這種配置方式是因為使用第三方的Filter沒有@Component註解。

package com.dy.config;

import com.nbkj.webFilter.TimerFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
 * Web配置
 *
 * @author duoyuan
 * @Configuration 這個註解宣告這個類是配置類
 * @create 2019-12-20 18:00
 **/

@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean timeFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        TimerFilter timerFilter = new TimerFilter();
        registrationBean.setFilter(timerFilter);
        List<String> urls = new ArrayList<>();
        urls.add("/*");
        registrationBean.setUrlPatterns(urls);
        return registrationBean;
    }
}

3、攔截器(Intereptor)

是什麼

攔截器是在面向切面程式設計中應用的,就是在你的service或者一個方法前呼叫一個方法,或者在方法後呼叫一個方法。是基於JAVA的反射機制。攔截器不是在web.xml,比如struts在struts.xml中配置。可以獲取被攔截的controller的方法,獲取不到引數。

應用場景

• 執行安全檢查

•格式化請求頭和主體

•審查或者記錄日誌

•根據請求內容授權或者限制使用者訪問

•根據請求頻率限制使用者訪問

怎麼用

package com.dy.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.persistence.Convert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

/**
 * this is spring interceptor
 *
 * @author duoyuan
 * @create 2019-12-24 18:16
 **/

@Component
public class TimeInterceptor implements HandlerInterceptor {

    /**
     * 控制器方法處理之前
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
        System.out.println("preHandle");
        System.out.println(((HandlerMethod) handler).getBean().getClass().getName());
        System.out.println(((HandlerMethod) handler).getMethod().getName());
        httpServletRequest.setAttribute("startTime", new Date().getTime());
        return false;
    }

    /**
     * 控制器方法處理之後
     * 控制器方法呼叫不拋異常呼叫
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
        Long startTime = (Long) httpServletRequest.getAttribute("startTime");
        System.out.println("time interceptor 耗時" + (new Date().getTime() - startTime));
    }

    /**
     * 控制器方法拋不拋異常都會被呼叫
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param e
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        System.out.println("afterCompletion");
        Long startTime = (Long) httpServletRequest.getAttribute("startTime");
        System.out.println("time interceptor 耗時" + (new Date().getTime() - startTime));
        System.out.println("ex is" + e);
    }
}

package com.dy.config;

import com.nbkj.interceptor.TimeInterceptor;
import com.nbkj.webFilter.TimerFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * Web配置
 *
 * @author duoyuan
 * @Configuration 這個註解宣告這個類是配置類
 * @create 2019-12-20 18:00
 **/

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private TimeInterceptor timeInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor);
    }
    
}

4、ControllerAdvice

是什麼

controller的一個增強,最常用的是和@ExceptionHandler一起用來做全域性異常。

應用場景

• 配合@ExceptionHandler做全域性異常處理

• 配合@InitBinder對特殊請求引數做轉換

• 配合@ModelAttribute使用

怎麼用

package com.dy.handler;

import com.wusong.order.common.Resp;
import com.wusong.order.common.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @description: 全域性異常處理
 * @author:duoyuan
 * @create: 2019-12-16 下午1:45
 **/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {


    /**
     * @description: 業務異常統一處理
     * @param e
     * @return:
     * @author:
     * @Date: 2019-12-16 下午12:26
    */
    @ExceptionHandler(value = ServiceException.class)
    @ResponseBody
    public Object serviceExceptionHandle(ServiceException e) {
        log.error("異常{}", e);
        Object obj = Resp.error(e);
        return obj;
    }
}