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