攔截機制中Aspect、ControllerAdvice、Interceptor、Fliter之間的區(qū)別詳解



????????在項目的開發(fā)中,在某些情況下,我們需要對客戶端發(fā)出的請求進行攔截,常用的API攔截方式有Fliter,Interceptor,ControllerAdvice以及Aspect。

上圖是spring中的攔截機制,如果出現(xiàn)異常的話,異常的順序是從里面到外面一步一步的進行處理,如果到了最外層都沒有進行處理的話,就會由tomcat容器拋出異常。下面我將詳細的解釋這四個攔截方式的不同。因為只是演示就不是所有的使用了日志記錄。這里相關(guān)的依賴自己可以去https://mvnrepository.com/下載,我就不一一添加了

先做一個總的總結(jié)吧

1.過濾器:Filter

:可以獲得Http原始的請求和響應(yīng)信息,但是拿不到響應(yīng)方法的信息

2.攔截器:Interceptor

? :可以獲得Http原始的請求和響應(yīng)信息,也拿得到響應(yīng)方法的信息,但是拿不到方法響應(yīng)中參數(shù)的值

3.ControllerAdvice(Controller增強,自spring3.2的時候推出):

主要是用于全局的異常攔截和處理,這里的異常可以使自定義異常也可以是JDK里面的異常

用于處理當數(shù)據(jù)庫事務(wù)業(yè)務(wù)和預期不同的時候拋出封裝后的異常,進行數(shù)據(jù)庫事務(wù)回滾,并將異常的顯示給用戶

4.切片:Aspect

? ?主要是進行公共方法的

? 可以拿得到方法響應(yīng)中參數(shù)的值,但是拿不到原始的Http請求和相對應(yīng)響應(yīng)的方法


Filter(過濾器)

可以獲得Http原始的請求和響應(yīng)信息,但是拿不到響應(yīng)方法的信息

filter是屬于Servlet規(guī)范的,不屬于Spring

springboot中的配置方法:

自定義一個Filter

import javax.servlet.*;

import java.io.IOException;

public class TimeFilter implements Filter {

/**Filter接口中的部分方法添加了default關(guān)鍵字,這樣的方法就不是一定要重寫*/

@Override

? ? public void init(FilterConfig filterConfig)throws ServletException {

System.out.println("Time Filter init");

? ? }

@Override

? ? public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {

System.out.println("time filter start");

? ? ? ? /**這里說明一下,在JDK8以及之后的JDK版本中都不再建議使用new Date().getTime()這種方式來獲得時間*/

? ? ? ? long start = System.currentTimeMillis();

? ? ? ? chain.doFilter(request,response);

? ? ? ? System.out.println("time filter:"+(System.currentTimeMillis()-start));

? ? ? ? System.out.println("time filter finish");

? ? }

@Override

? ? public void destroy() {

System.out.println("time filter destroy");

? ? }

}

方式一:通過Bean注入的方式

注冊Filter,springboot當中提供了FilterRegistrationBean類來注冊Filter

import com.imooc.Filter.TimeFilter;

import com.imooc.Interceptor.TimeInterceptor;

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.WebMvcConfigurer;

import java.util.ArrayList;

import java.util.List;

@Configuration

public class WebConfigimplements WebMvcConfigurer {

/**WebMvcConfigurerAdapter在JDK8中這個類已經(jīng)過時,我們直接繼承這個類所繼承的接口*/

@Bean

? ? public FilterRegistrationBeantimeFilter(){

FilterRegistrationBean registrationBean =new FilterRegistrationBean();

? ? ? ? TimeFilter timeFilter =new TimeFilter();

? ? ? ? registrationBean.setFilter(timeFilter);

????????/**添加攔截的地址*/

? ? ? ? List urls =new ArrayList<>();

? ? ? ? urls.add("/*");

? ? ? ? return registrationBean;

? ? }

}

方式二:通過@WebFilter注解實現(xiàn)

import org.springframework.stereotype.Component;

import javax.servlet.*;

import javax.servlet.annotation.WebFilter;

import java.io.IOException;

@Component

@WebFilter(filterName ="TimeFilter",urlPatterns ="/*")

public class TimeFilterimplements Filter {

@Override

? ? public void init(FilterConfig filterConfig)throws ServletException {

System.out.println("Time Filter init");

? ? }

@Override

? ? public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {

System.out.println("time filter start");

? ? ? ? /**這里說明一下,在JDK8以及之后的JDK版本中都不再建議使用new Date().getTime()這種方式來獲得時間*/

? ? ? ? long start = System.currentTimeMillis();

? ? ? ? chain.doFilter(request,response);

? ? ? ? System.out.println("time filter:"+(System.currentTimeMillis()-start));

? ? ? ? System.out.println("time filter finish");

? ? }

@Override

? ? public void destroy() {

System.out.println("time filter destroy");

? ? }

}

@WebFilter 的常用屬性

屬性名? ? ? ? ? ? ? ????類型? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 描述

filterName? ? ? ? ?????String? ? ? ? ? ? ? ? ? ? ? ????指定過濾器的 name 屬性,等價于 <filter-name>

value? ? ? ? ? ? ? ? ????? String[]? ? ? ? ? ? ? ? ? ? ? ?該屬性等價于 urlPatterns 屬性。但是兩者不應(yīng)該同時使用。

urlPatterns ????????????String[] ????????????????????????指定一組過濾器的 URL 匹配模式。等價于 <url-pattern> 標簽。

servletNames? ? ? ????String[]? ? ? ? ? ? ? ? ? ? ? 指定過濾器將應(yīng)用于哪些 Servlet。取值是 @WebServlet 中的 name 屬性的取值,或者是

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?????web.xml 中 <servlet-name> 的取值。

dispatcherTypes ????DispatcherType? ? ? ? ? ? ?指定過濾器的轉(zhuǎn)發(fā)模式。具體取值包括:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。

initParams ????????????????WebInitParam[] ????????????指定一組過濾器初始化參數(shù),等價于 <init-param> 標簽。

asyncSupported ????????boolean ????????????????????????聲明過濾器是否支持異步操作模式,等價于 <async-supported> 標簽。

description ????????????????String ????????????????????????????該過濾器的描述信息,等價于 <description> 標簽。

displayName ????????????????String ????????????????????????該過濾器的顯示名,通常配合工具使用,等價于 <display-name> 標簽。

相較而言,方式一會更加的靈活,而方式二更加的方便,但是兩者在實質(zhì)上是一樣的


Interceptor (攔截器) :

可以獲得Http原始的請求和響應(yīng)信息,也拿得到響應(yīng)方法的信息,但是拿不到方法響應(yīng)中參數(shù)的值

在web開發(fā)中,攔截器是經(jīng)常用到的功能。它可以幫我們驗證是否登陸、預先設(shè)置數(shù)據(jù)以及統(tǒng)計方法的執(zhí)行效率等。在spring中攔截器有兩種,第一種是HandlerInterceptor,第二種是MethodInterceptor。HandlerInterceptor是SpringMVC中的攔截器,它攔截的是Http請求的信息,優(yōu)先于MethodInterceptor。而MethodInterceptor是springAOP的。前者攔截的是請求的地址,而后者是攔截controller中的方法,因為下面要將Aspect,就不詳細講述MethodInterceptor

在springboot中HandlerInterceptor的配置

1.首先定義自己的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.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

* 攔截器會攔截所有的控制器,不管是spring的還是自定義的

*/

@Component

public class TimeInterceptorimplements HandlerInterceptor {

/**

? ? *控制器方法調(diào)用之前會進行

? ? *和上面的Filter一樣,繼承的某些接口方法中也加了default關(guān)鍵字,可以不用重寫,這里為了演示就都寫了

*/

? ? @Override

? ? public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {

System.out.println("proHandle");

? ? ? ? System.out.println(((HandlerMethod)handler).getBean().getClass().getName());

? ? ? ? System.out.println(((HandlerMethod)handler).getMethod().getName());

? ? ? ? request.setAttribute("startTime",System.currentTimeMillis());

return true;

? ? ? ? /**true的話 就是選擇可以調(diào)用后面的方法? 也就是controller中的getInfo方法*/

? ? }

/**控制后方法執(zhí)行之后會進行,拋出異常則不會被執(zhí)行*/

? ? @Override

? ? public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {

System.out.println("postHandle");

? ? ? ? Long start = (Long)request.getAttribute("startTime");

? ? ? ? System.out.println("time interceptor 耗時:"+(System.currentTimeMillis()-start));

? ? }

/**方法被調(diào)用或者拋出異常都會被執(zhí)行*/

? ? @Override

? ? public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {

System.out.println("afterCompletion");

? ? ? ? Long start = (Long)request.getAttribute("startTime");

? ? ? ? System.out.println("time interceptor 耗時:"+(System.currentTimeMillis()-start));

? ? }zai

}

2.在配置類中配置自定義的Interceptor

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.imooc.Interceptor.TimeInterceptor;

import org.springframework.beans.factory.annotation.Autowired;

@Configuration

public class WebConfigimplements WebMvcConfigurer {

/**WebMvcConfigurerAdapter在JDK8中這個類已經(jīng)過時,我們直接繼承這個類所繼承的接口*/

? ? @Autowired

? ? private TimeInterceptortimeInterceptor;

? ? @Override

? ? public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(timeInterceptor);

? ? }

}


ControllerAdvice(Controller增強,自spring3.2的時候推出):

用于處理當數(shù)據(jù)庫事務(wù)業(yè)務(wù)和預期不同的時候拋出封裝后的異常,進行數(shù)據(jù)庫事務(wù)回滾,并將異常的顯示給用戶

1.定義自己的異常類

import lombok.Data;

@Data

public class UserNotExistExceptionextends RuntimeException{

private static final long serialVersionUID =4820951478405122770L;

? ? private Stringid;

? ? public UserNotExistException(String id) {

super("user not exist.......");

? ? ? ? this.id = id;

? ? }

}

2.編寫全局異常處理類

import com.imooc.Exception.UserNotExistException;

import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.HashMap;

import java.util.Map;

@ControllerAdvice

public class ControllerExceptionHandler {

/**指定拋出的異常類*/

@ExceptionHandler(UserNotExistException.class)

/**如果全部異常處理返回json格式,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,這樣在方法上就可以不需要添加 @ResponseBody.@ResponseBody注解的作用是將controller的方法返回的對象通過適當?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后,寫入到response對象的body區(qū),通常用來返回JSON數(shù)據(jù)或者是XML數(shù)據(jù)。*/

@ResponseBody

? ? @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

/**當出現(xiàn)該Http狀態(tài)碼的時候拋出異常*/

? ? public MaphandlerUserNotExistException(UserNotExistException exception){

Map result =new HashMap<>();

? ? ? ? result.put("id",exception.getId());

? ? ? ? result.put("message",exception.getMessage());

? ? ? ? return result;

? ? }

}

3、controller中拋出異常進行測試

import com.imooc.Exception.UserNotExistException;

import com.imooc.dto.User;

import com.imooc.dto.UserQueryCondition;

import lombok.extern.slf4j.Slf4j;

import org.springframework.validation.BindingResult;

import org.springframework.web.bind.annotation.*;

@RestController

public class UserController {

? ??@GetMapping(value ="/user/{id}")

????public UsergetInfo(@PathVariable String id)throws Exception{

????throw new UserNotExistException(id);

? ? ????}

}



Aspect(切面):

? ????可以拿得到方法響應(yīng)中參數(shù)的值,但是拿不到原始的Http請求和相對應(yīng)響應(yīng)的方法,基于Controller層。對于統(tǒng)一異常處理和日志記錄非常方便,有效地減少了代碼的重復率。

可以參照https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html,spring的官方文檔

1.創(chuàng)建Controller

2.創(chuàng)建一個Aspect切面

import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.springframework.stereotype.Component;

@Aspect

@Component

@Slf4j

public class TimeAspect {

@Around("execution(* com.imooc.controller.UserController.*(..))")

public ObjecthandleControllerMethod(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {

log.info("Time aspect start");

? ? ? ? Long start = System.currentTimeMillis();

? ? ? ? Object[] args = proceedingJoinPoint.getArgs();

? ? ? ? for(Object object:args)

{

log.info("arg is:"+String.valueOf(object));

? ? ? ? }

Object object =proceedingJoinPoint.proceed();

? ? ? ? System.out.println("time filter:"+(System.currentTimeMillis()-start));

? ? ? ? log.info("Time aspect finish");

? ? ? ? return object;

? ? }

}

切面的方法說明:

@Aspect

作用是把當前類標識為一個切面供容器讀取

@Before

標識一個前置增強方法,相當于BeforeAdvice的功能

@AfterReturning

后置增強,相當于AfterReturningAdvice,方法退出時執(zhí)行

@AfterThrowing

異常拋出增強,相當于ThrowsAdvice

@After

final增強,不管是拋出異?;蛘哒M顺龆紩?zhí)行

@Around

環(huán)繞增強,相當于MethodInterceptor

除了@Around外,每個方法里都可以加或者不加參數(shù)JoinPoint,如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了類名、被切面的方法名,參數(shù)等屬性,可供讀取使用。@Around參數(shù)必須為ProceedingJoinPoint,pjp.proceed相應(yīng)于執(zhí)行被切面的方法。@AfterReturning方法里,可以加returning = “XXX”,XXX即為在controller里方法的返回值,本例中的返回值是“first controller”。@AfterThrowing方法里,可以加throwing = "XXX"

關(guān)于切面PointCut的切入點

execution切點函數(shù)

execution函數(shù)用于匹配方法執(zhí)行的連接點,語法為:

execution(方法修飾符(可選) ?返回類型 ?方法名 ?參數(shù) ?異常模式(可選))?

例如:execution(* com.imooc.controller.UserController.*(..))

第一個*代表的是所有的返回值類型,com.imooc.controller.UserController.*代表的是com.imooc.controller包下UserController類的所有方法,(..)代表的是所有的參數(shù)

參數(shù)部分允許使用通配符:

* ?匹配任意字符,但只能匹配一個元素

.. 匹配任意字符,可以匹配任意多個元素,表示類時,必須和*聯(lián)合使用

+ ?必須跟在類名后面,如Horseman+,表示類本身和繼承或擴展指定類的所有類

除了execution(),Spring中還支持其他多個函數(shù),這里列出名稱和簡單介紹,以方便根據(jù)需要進行更詳細的查詢

?@annotation()

表示標注了指定注解的目標類方法

例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示標注了@Transactional的方法

args()

通過目標類方法的參數(shù)類型指定切點

例如 args(String) 表示有且僅有一個String型參數(shù)的方法

@args()

通過目標類參數(shù)的對象類型是否標注了指定注解指定切點

如 @args(org.springframework.stereotype.Service) 表示有且僅有一個標注了@Service的類參數(shù)的方法

within()

通過類名指定切點

如 with(examples.chap03.Horseman) 表示Horseman的所有方法

target()

通過類名指定,同時包含所有子類

如 target(examples.chap03.Horseman) ?且Elephantman extends Horseman,則兩個類的所有方法都匹配

@within()

匹配標注了指定注解的類及其所有子類

如?@within(org.springframework.stereotype.Service) 給Horseman加上@Service標注,則Horseman和Elephantman 的所有方法都匹配

@target()

所有標注了指定注解的類

如?@target(org.springframework.stereotype.Service)?表示所有標注了@Service的類的所有方法

?this()

大部分時候和target()相同,區(qū)別是this是在運行時生成代理類后,才判斷代理類與指定的對象類型是否匹配

關(guān)于Aspect參考了https://www.cnblogs.com/bigben0123/p/7779357.html,如有侵權(quán),請作者聯(lián)系刪除


具體使用哪種攔截機制還是要根據(jù)項目開發(fā)的需求來決定。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容