????????在項目的開發(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ā)的需求來決定。