Spring攔截器
SpringBoot統(tǒng)一功能處理。也就是AOP的具體實(shí)現(xiàn)。
1.統(tǒng)一用戶登錄權(quán)限校驗(yàn)
最原始的用戶登錄驗(yàn)證方法,我們通過封裝了一個(gè)方法來判斷用戶是否登錄,但如果實(shí)現(xiàn)的功能多了,那么每一個(gè)需要登錄的功能都要在對(duì)應(yīng)的接口中來調(diào)用這個(gè)函數(shù)來判讀是否登錄。
public class LoginStatus {
public static User getStatus(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
//當(dāng)前用戶未登錄
return null;
}
User user = (User) session.getAttribute("user");
if (user == null) {
//當(dāng)前用戶未登錄
return null;
}
return user;
}
}
上面的代碼雖然已經(jīng)封裝成了方法,但是如果隨著程序功能的增多,那么每一個(gè)控制器都要調(diào)用這個(gè)接口進(jìn)行判斷,就出現(xiàn)了代碼的冗余,也增加了代碼的維護(hù)成本。
這個(gè)時(shí)候就需要提供一個(gè)公共的方法來進(jìn)行統(tǒng)一的用戶登錄權(quán)限驗(yàn)證了。
1) SpringAOP 用戶統(tǒng)一驗(yàn)證的問題
統(tǒng)一驗(yàn)證我們可以使用SpringAOP的前置通知或者是環(huán)繞通知來實(shí)現(xiàn)
@Aspect // 說明該類為一個(gè)切面
@Component
public class UserAspect {
// 定義切點(diǎn),使用 AspectJ表達(dá)式語法,攔截UserController所有方法
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}
// 前置通知
@Before("pointcut()")
public void doBefore() {
System.out.println("執(zhí)行Before前置通知");
}
// 添加環(huán)繞通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object result = null;
System.out.println("執(zhí)行環(huán)繞通知的前置方法");
try {
// 執(zhí)行(攔截的)業(yè)務(wù)方法
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("執(zhí)行環(huán)繞通知的后置方法");
return result;
}
}
我們發(fā)現(xiàn)原生的SpringAOP的切面實(shí)現(xiàn)用戶登錄權(quán)限的校驗(yàn)功能,會(huì)有兩個(gè)問題
- 我們是獲取不到HttpSession對(duì)象的
- 如果我們只對(duì)一部分方法進(jìn)行攔截,像登錄和注冊(cè)這樣的方法就沒有必要攔截,這樣的很難定義排除對(duì)應(yīng)方法的規(guī)則,甚至說沒有辦法定義。
那就可以使用Spring的攔截器
2) Spring攔截器
對(duì)于上面兩個(gè)問題Spring中提供了解決方案,提供了具體實(shí)現(xiàn)的攔截器:Handlerlnterceptor,攔截器的實(shí)現(xiàn)分為兩個(gè)步驟:
- 創(chuàng)建自定義攔截器,實(shí)現(xiàn)Handlerlnterceptor接口的preHandle(執(zhí)行具體方法之前的預(yù)處理)方法
- 將自定義攔截器加入WebMvcConfigurer的addInterceptors
1.自定義攔截器
用戶登錄權(quán)限校驗(yàn),自定義攔截器代碼實(shí)現(xiàn):
/**
* 定義自定義攔截器實(shí)現(xiàn)用戶登錄校驗(yàn)
*/
@Configuration
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userInfo") != null) {
response.setStatus(200);
return true;
}
response.setStatus(403);
return false;
}
}
2.將自定義攔截器加入到系統(tǒng)配置中
將上一步自定義的攔截器加入到系統(tǒng)配置信息中,代碼實(shí)現(xiàn):
@Configuration
public class AppConfig implements WebMvcConfigurer {
//添加攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()) // 添加自定義攔截器
.addPathPatterns("/**") //攔截所有接口
.excludePathPatterns("/**/login")//排除的接口
}
}
- addPathPatterns:表示需要攔截的 URL,
*
”表示攔截任意?法(也就是所有?法) - excludePathPatterns:表示需要排除的 URL。
- 以上的攔截規(guī)則可以攔截程序中使用的URL、靜態(tài)文件(圖片、前端文件等)
排除所有靜態(tài)的資源
@Configuration
public class AppConfig implements WebMvcConfigurer {
//添加攔截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()) // 添加自定義攔截器
.addPathPatterns("/**")
.excludePathPatterns("/**/*.html")//排除所有靜態(tài)資源
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/**/img/*");
}
}
3) 攔截器實(shí)現(xiàn)原理
原本正常的調(diào)用流程是這樣的:
但是添加了攔截器后,在調(diào)用Controller之前會(huì)進(jìn)行相對(duì)應(yīng)業(yè)務(wù)處理
?所有?法都會(huì)執(zhí)? DispatcherServlet 中的 doDispatch 調(diào)度?法,doDispatch 部分源碼如下
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//此處省略上面代碼
// 調(diào)用預(yù)處理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 執(zhí)行Controller中的業(yè)務(wù)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// ......后面代碼省略
}
從上述源碼可以看出在開始執(zhí)? Controller 之前,會(huì)先調(diào)? 預(yù)處理?法 applyPreHandle,?
applyPreHandle ?法的實(shí)現(xiàn)源碼如下
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
// 獲取項(xiàng)?中使?的攔截器 HandlerIntercepto
HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
}
從上述源碼可以看出,在 applyPreHandle 中會(huì)獲取所有的攔截器 HandlerInterceptor 并執(zhí)?攔截器中
的 preHandle ?法,這樣就會(huì)咱們前?定義的攔截器對(duì)應(yīng)上了,如下圖所示 :
只有當(dāng)我們重寫的方法放回true的時(shí)候才會(huì)繼續(xù)走調(diào)用Controller的業(yè)務(wù)代碼,否則就是直接放回給前端
攔截器的實(shí)現(xiàn)原理:
- 從宏觀上來講的話,它是根據(jù)AOP的思想來去執(zhí)行的,把統(tǒng)一的方法放到前置處理器來進(jìn)行處理
- 在Spring中的攔截器實(shí)現(xiàn),就是在調(diào)度器類里的調(diào)度方法,調(diào)度方法在真正調(diào)用Controller之前,它會(huì)有一個(gè)方法先去掃描當(dāng)前Spring中所有攔截器的一個(gè)列表,然后去執(zhí)行這些攔截器,只有當(dāng)攔截器執(zhí)行通過的時(shí)候,它才會(huì)繼續(xù)走后面的流程,才會(huì)去走Controller然后返回結(jié)果給前端。
4)同一訪問前綴添加
該方法可以給所有接口添加一個(gè)訪問前綴,讓前端訪問接口時(shí)都要加上blog,比如原來是/add
,添加前綴后就是/blog/add
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("blog",pre->true);
}
}
其中第?個(gè)參數(shù)是?個(gè)表達(dá)式,設(shè)置為 true 表示啟動(dòng)前綴
2. 統(tǒng)一異常處理
同一異常處理是通過@ControllerAdvice
+@ExceptionHandler
兩個(gè)注解結(jié)合實(shí)現(xiàn)的,@ControllerAdvice
表示控制器通知類,@ExceptionHandler
是異常處理,兩個(gè)結(jié)合起來就表示出現(xiàn)異常的時(shí)候執(zhí)行某一個(gè)通知,也就是執(zhí)行某個(gè)方法,代碼實(shí)現(xiàn):
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handler(Exception e) {
Map<String,Object> map = new HashMap<>();
map.put("success",1);
map.put("status",-1);
map.put("message","服務(wù)器接口異常");
return map;
}
}
注意:方法名和返回值可以任意,重要的是注解。
這里的代碼表示的是發(fā)生任何異常都給前端返回一個(gè)HashMap,也可以指定異常進(jìn)行處理代碼如下
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public Object exceptionAdvice(Exception e) {
Map<String,Object> map = new HashMap<>();
map.put("success",1);
map.put("status",-1);
map.put("message","服務(wù)器接口異常");
return map;
}
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public Object nullPointerExceptionAdvice(NullPointerException exception) {
Map<String,Object> map = new HashMap<>();
map.put("success",1);
map.put("status",-1);
map.put("message",exception.toString());
return map;
}
}
當(dāng)有多個(gè)異常通知時(shí),匹配順序?yàn)楫?dāng)前類及其?類向上依次匹配
3. 統(tǒng)一數(shù)據(jù)返回格式
1)統(tǒng)一數(shù)據(jù)返回的好處
統(tǒng)一數(shù)據(jù)返回格式有很多好處文章來源:http://www.zghlxwxcb.cn/news/detail-436739.html
- 方便前端程序員接收和解析后端接口返回的數(shù)據(jù)
- 降低前端程序員和后端程序員的溝通成本,只需要按照指定格式實(shí)現(xiàn)功能,所有接口放回值都是固定的
- 有利于項(xiàng)目整體的維護(hù)和修改,有利于后端統(tǒng)一標(biāo)準(zhǔn)規(guī)范。
2)統(tǒng)一數(shù)據(jù)返回實(shí)現(xiàn)
統(tǒng)一的數(shù)據(jù)返回格式可以使用@ControllerAdvice
+ResponseBodyAdvice
實(shí)現(xiàn)文章來源地址http://www.zghlxwxcb.cn/news/detail-436739.html
- supports方法返回true表示對(duì)返回內(nèi)容進(jìn)行重寫,就會(huì)執(zhí)行beforeBodyWrite方法
- beforeBodyWrite方法中body就是Controller返回的內(nèi)容
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
/**
* 內(nèi)容是否需要重寫,此方法可以選擇部分控制器和方法進(jìn)行重寫
* 返回 true表示重寫
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
/**
*控制器方法返回之前會(huì)調(diào)用此方法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
Map<String,Object> result = new HashMap<>();
result.put("success",200);
result.put("status",1);
result.put("data",body);
return result;
}
}
到了這里,關(guān)于Spring 統(tǒng)一功能處理(攔截器)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!