????????在項(xiàng)目的開(kāi)發(fā)中拆融,在某些情況下,我們需要對(duì)客戶(hù)端發(fā)出的請(qǐng)求進(jìn)行攔截啊终,常用的API攔截方式有Fliter镜豹,Interceptor,ControllerAdvice以及Aspect蓝牲。
上圖是spring中的攔截機(jī)制,如果出現(xiàn)異常的話趟脂,異常的順序是從里面到外面一步一步的進(jìn)行處理,如果到了最外層都沒(méi)有進(jìn)行處理的話搞旭,就會(huì)由tomcat容器拋出異常散怖。下面我將詳細(xì)的解釋這四個(gè)攔截方式的不同菇绵。因?yàn)橹皇茄菔揪筒皇撬械氖褂昧巳罩居涗浺奚_@里相關(guān)的依賴(lài)自己可以去https://mvnrepository.com/下載,我就不一一添加了
先做一個(gè)總的總結(jié)吧
1.過(guò)濾器:Filter
:可以獲得Http原始的請(qǐng)求和響應(yīng)信息咬最,但是拿不到響應(yīng)方法的信息
2.攔截器:Interceptor
? :可以獲得Http原始的請(qǐng)求和響應(yīng)信息翎嫡,也拿得到響應(yīng)方法的信息,但是拿不到方法響應(yīng)中參數(shù)的值
3.ControllerAdvice(Controller增強(qiáng)永乌,自spring3.2的時(shí)候推出):
主要是用于全局的異常攔截和處理,這里的異郴笊辏可以使自定義異常也可以是JDK里面的異常
用于處理當(dāng)數(shù)據(jù)庫(kù)事務(wù)業(yè)務(wù)和預(yù)期不同的時(shí)候拋出封裝后的異常,進(jìn)行數(shù)據(jù)庫(kù)事務(wù)回滾翅雏,并將異常的顯示給用戶(hù)
4.切片:Aspect
? ?主要是進(jìn)行公共方法的
? 可以拿得到方法響應(yīng)中參數(shù)的值圈驼,但是拿不到原始的Http請(qǐng)求和相對(duì)應(yīng)響應(yīng)的方法
Filter(過(guò)濾器):
可以獲得Http原始的請(qǐng)求和響應(yīng)信息,但是拿不到響應(yīng)方法的信息
filter是屬于Servlet規(guī)范的望几,不屬于Spring
springboot中的配置方法:
自定義一個(gè)Filter
import javax.servlet.*;
import java.io.IOException;
public class TimeFilter implements Filter {
/**Filter接口中的部分方法添加了default關(guān)鍵字绩脆,這樣的方法就不是一定要重寫(xiě)*/
@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");
? ? ? ? /**這里說(shuō)明一下,在JDK8以及之后的JDK版本中都不再建議使用new Date().getTime()這種方式來(lái)獲得時(shí)間*/
? ? ? ? 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");
? ? }
}
方式一:通過(guò)Bean注入的方式
注冊(cè)Filter橄抹,springboot當(dāng)中提供了FilterRegistrationBean類(lèi)來(lái)注冊(cè)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中這個(gè)類(lèi)已經(jīng)過(guò)時(shí)靴迫,我們直接繼承這個(gè)類(lèi)所繼承的接口*/
@Bean
? ? public FilterRegistrationBeantimeFilter(){
FilterRegistrationBean registrationBean =new FilterRegistrationBean();
? ? ? ? TimeFilter timeFilter =new TimeFilter();
? ? ? ? registrationBean.setFilter(timeFilter);
????????/**添加攔截的地址*/
? ? ? ? List urls =new ArrayList<>();
? ? ? ? urls.add("/*");
? ? ? ? return registrationBean;
? ? }
}
方式二:通過(guò)@WebFilter注解實(shí)現(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");
? ? ? ? /**這里說(shuō)明一下,在JDK8以及之后的JDK版本中都不再建議使用new Date().getTime()這種方式來(lái)獲得時(shí)間*/
? ? ? ? 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 的常用屬性
屬性名? ? ? ? ? ? ? ????類(lèi)型? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 描述
filterName? ? ? ? ?????String? ? ? ? ? ? ? ? ? ? ? ????指定過(guò)濾器的 name 屬性楼誓,等價(jià)于 <filter-name>
value? ? ? ? ? ? ? ? ????? String[]? ? ? ? ? ? ? ? ? ? ? ?該屬性等價(jià)于 urlPatterns 屬性玉锌。但是兩者不應(yīng)該同時(shí)使用。
urlPatterns ????????????String[] ????????????????????????指定一組過(guò)濾器的 URL 匹配模式疟羹。等價(jià)于 <url-pattern> 標(biāo)簽主守。
servletNames? ? ? ????String[]? ? ? ? ? ? ? ? ? ? ? 指定過(guò)濾器將應(yīng)用于哪些 Servlet禀倔。取值是 @WebServlet 中的 name 屬性的取值,或者是
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?????web.xml 中 <servlet-name> 的取值丸逸。
dispatcherTypes ????DispatcherType? ? ? ? ? ? ?指定過(guò)濾器的轉(zhuǎn)發(fā)模式蹋艺。具體取值包括:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????ASYNC、ERROR黄刚、FORWARD捎谨、INCLUDE、REQUEST憔维。
initParams ????????????????WebInitParam[] ????????????指定一組過(guò)濾器初始化參數(shù)涛救,等價(jià)于 <init-param> 標(biāo)簽。
asyncSupported ????????boolean ????????????????????????聲明過(guò)濾器是否支持異步操作模式业扒,等價(jià)于 <async-supported> 標(biāo)簽检吆。
description ????????????????String ????????????????????????????該過(guò)濾器的描述信息,等價(jià)于 <description> 標(biāo)簽程储。
displayName ????????????????String ????????????????????????該過(guò)濾器的顯示名蹭沛,通常配合工具使用,等價(jià)于 <display-name> 標(biāo)簽章鲤。
相較而言摊灭,方式一會(huì)更加的靈活,而方式二更加的方便败徊,但是兩者在實(shí)質(zhì)上是一樣的
Interceptor (攔截器) :
可以獲得Http原始的請(qǐng)求和響應(yīng)信息帚呼,也拿得到響應(yīng)方法的信息,但是拿不到方法響應(yīng)中參數(shù)的值
在web開(kāi)發(fā)中皱蹦,攔截器是經(jīng)常用到的功能煤杀。它可以幫我們驗(yàn)證是否登陸、預(yù)先設(shè)置數(shù)據(jù)以及統(tǒng)計(jì)方法的執(zhí)行效率等沪哺。在spring中攔截器有兩種沈自,第一種是HandlerInterceptor,第二種是MethodInterceptor辜妓。HandlerInterceptor是SpringMVC中的攔截器枯途,它攔截的是Http請(qǐng)求的信息,優(yōu)先于MethodInterceptor嫌拣。而MethodInterceptor是springAOP的柔袁。前者攔截的是請(qǐng)求的地址,而后者是攔截controller中的方法异逐,因?yàn)橄旅嬉獙spect捶索,就不詳細(xì)講述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;
/**
* 攔截器會(huì)攔截所有的控制器,不管是spring的還是自定義的
*/
@Component
public class TimeInterceptorimplements HandlerInterceptor {
/**
? ? *控制器方法調(diào)用之前會(huì)進(jìn)行
? ? *和上面的Filter一樣灰瞻,繼承的某些接口方法中也加了default關(guān)鍵字腥例,可以不用重寫(xiě)辅甥,這里為了演示就都寫(xiě)了
*/
? ? @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í)行之后會(huì)進(jìn)行,拋出異常則不會(huì)被執(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 耗時(shí):"+(System.currentTimeMillis()-start));
? ? }
/**方法被調(diào)用或者拋出異常都會(huì)被執(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 耗時(shí):"+(System.currentTimeMillis()-start));
? ? }zai
}
2.在配置類(lèi)中配置自定義的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中這個(gè)類(lèi)已經(jīng)過(guò)時(shí)燎竖,我們直接繼承這個(gè)類(lèi)所繼承的接口*/
? ? @Autowired
? ? private TimeInterceptortimeInterceptor;
? ? @Override
? ? public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
? ? }
}
ControllerAdvice(Controller增強(qiáng)璃弄,自spring3.2的時(shí)候推出):
用于處理當(dāng)數(shù)據(jù)庫(kù)事務(wù)業(yè)務(wù)和預(yù)期不同的時(shí)候拋出封裝后的異常,進(jìn)行數(shù)據(jù)庫(kù)事務(wù)回滾构回,并將異常的顯示給用戶(hù)
1.定義自己的異常類(lèi)
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.編寫(xiě)全局異常處理類(lèi)
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 {
/**指定拋出的異常類(lèi)*/
@ExceptionHandler(UserNotExistException.class)
/**如果全部異常處理返回json格式夏块,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,這樣在方法上就可以不需要添加 @ResponseBody.@ResponseBody注解的作用是將controller的方法返回的對(duì)象通過(guò)適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后纤掸,寫(xiě)入到response對(duì)象的body區(qū)脐供,通常用來(lái)返回JSON數(shù)據(jù)或者是XML數(shù)據(jù)。*/
@ResponseBody
? ? @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
/**當(dāng)出現(xiàn)該Http狀態(tài)碼的時(shí)候拋出異常*/
? ? public MaphandlerUserNotExistException(UserNotExistException exception){
Map result =new HashMap<>();
? ? ? ? result.put("id",exception.getId());
? ? ? ? result.put("message",exception.getMessage());
? ? ? ? return result;
? ? }
}
3借跪、controller中拋出異常進(jìn)行測(cè)試
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請(qǐng)求和相對(duì)應(yīng)響應(yīng)的方法,基于Controller層掏愁。對(duì)于統(tǒng)一異常處理和日志記錄非常方便歇由,有效地減少了代碼的重復(fù)率。
可以參照https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html果港,spring的官方文檔
1.創(chuàng)建Controller
2.創(chuàng)建一個(gè)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;
? ? }
}
切面的方法說(shuō)明:
@Aspect
作用是把當(dāng)前類(lèi)標(biāo)識(shí)為一個(gè)切面供容器讀取
@Before
標(biāo)識(shí)一個(gè)前置增強(qiáng)方法沦泌,相當(dāng)于BeforeAdvice的功能
@AfterReturning
后置增強(qiáng),相當(dāng)于AfterReturningAdvice京腥,方法退出時(shí)執(zhí)行
@AfterThrowing
異常拋出增強(qiáng)赦肃,相當(dāng)于ThrowsAdvice
@After
final增強(qiáng)溅蛉,不管是拋出異彻耍或者正常退出都會(huì)執(zhí)行
@Around
環(huán)繞增強(qiáng),相當(dāng)于MethodInterceptor
除了@Around外船侧,每個(gè)方法里都可以加或者不加參數(shù)JoinPoint欠气,如果有用JoinPoint的地方就加,不加也可以镜撩,JoinPoint里包含了類(lèi)名预柒、被切面的方法名,參數(shù)等屬性袁梗,可供讀取使用宜鸯。@Around參數(shù)必須為ProceedingJoinPoint,pjp.proceed相應(yīng)于執(zhí)行被切面的方法遮怜。@AfterReturning方法里淋袖,可以加returning = “XXX”,XXX即為在controller里方法的返回值锯梁,本例中的返回值是“first controller”即碗。@AfterThrowing方法里焰情,可以加throwing = "XXX"
關(guān)于切面PointCut的切入點(diǎn)
execution切點(diǎn)函數(shù)
execution函數(shù)用于匹配方法執(zhí)行的連接點(diǎn),語(yǔ)法為:
execution(方法修飾符(可選) ?返回類(lèi)型 ?方法名 ?參數(shù) ?異常模式(可選))?
例如:execution(* com.imooc.controller.UserController.*(..))
第一個(gè)*代表的是所有的返回值類(lèi)型剥懒,com.imooc.controller.UserController.*代表的是com.imooc.controller包下UserController類(lèi)的所有方法内舟,(..)代表的是所有的參數(shù)
參數(shù)部分允許使用通配符:
* ?匹配任意字符,但只能匹配一個(gè)元素
.. 匹配任意字符初橘,可以匹配任意多個(gè)元素验游,表示類(lèi)時(shí),必須和*聯(lián)合使用
+ ?必須跟在類(lèi)名后面保檐,如Horseman+批狱,表示類(lèi)本身和繼承或擴(kuò)展指定類(lèi)的所有類(lèi)
除了execution(),Spring中還支持其他多個(gè)函數(shù)展东,這里列出名稱(chēng)和簡(jiǎn)單介紹赔硫,以方便根據(jù)需要進(jìn)行更詳細(xì)的查詢(xún)
?@annotation()
表示標(biāo)注了指定注解的目標(biāo)類(lèi)方法
例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示標(biāo)注了@Transactional的方法
args()
通過(guò)目標(biāo)類(lèi)方法的參數(shù)類(lèi)型指定切點(diǎn)
例如 args(String) 表示有且僅有一個(gè)String型參數(shù)的方法
@args()
通過(guò)目標(biāo)類(lèi)參數(shù)的對(duì)象類(lèi)型是否標(biāo)注了指定注解指定切點(diǎn)
如 @args(org.springframework.stereotype.Service) 表示有且僅有一個(gè)標(biāo)注了@Service的類(lèi)參數(shù)的方法
within()
通過(guò)類(lèi)名指定切點(diǎn)
如 with(examples.chap03.Horseman) 表示Horseman的所有方法
target()
通過(guò)類(lèi)名指定,同時(shí)包含所有子類(lèi)
如 target(examples.chap03.Horseman) ?且Elephantman extends Horseman盐肃,則兩個(gè)類(lèi)的所有方法都匹配
@within()
匹配標(biāo)注了指定注解的類(lèi)及其所有子類(lèi)
如?@within(org.springframework.stereotype.Service) 給Horseman加上@Service標(biāo)注爪膊,則Horseman和Elephantman 的所有方法都匹配
@target()
所有標(biāo)注了指定注解的類(lèi)
如?@target(org.springframework.stereotype.Service)?表示所有標(biāo)注了@Service的類(lèi)的所有方法
?this()
大部分時(shí)候和target()相同,區(qū)別是this是在運(yùn)行時(shí)生成代理類(lèi)后砸王,才判斷代理類(lèi)與指定的對(duì)象類(lèi)型是否匹配
關(guān)于Aspect參考了https://www.cnblogs.com/bigben0123/p/7779357.html推盛,如有侵權(quán),請(qǐng)作者聯(lián)系刪除
具體使用哪種攔截機(jī)制還是要根據(jù)項(xiàng)目開(kāi)發(fā)的需求來(lái)決定谦铃。