關于spring的統(tǒng)一功能處理(攔截器)實現(xiàn)
spring攔截器
springboot統(tǒng)一功能處理。也就是aop的具體實現(xiàn)。
1.統(tǒng)一用戶登錄權限校驗
最原始的用戶登錄驗證方法,我們通過封裝了一個方法來判斷用戶是否登錄,但如果實現(xiàn)的功能多了,那么每一個需要登錄的功能都要在對應的接口中來調(diào)用這個函數(shù)來判讀是否登錄。
public class loginstatus { public static user getstatus(httpservletrequest request) { httpsession session = request.getsession(false); if (session == null) { //當前用戶未登錄 return null; } user user = (user) session.getattribute("user"); if (user == null) { //當前用戶未登錄 return null; } return user; } }
上面的代碼雖然已經(jīng)封裝成了方法,但是如果隨著程序功能的增多,那么每一個控制器都要調(diào)用這個接口進行判斷,就出現(xiàn)了代碼的冗余,也增加了代碼的維護成本。
這個時候就需要提供一個公共的方法來進行統(tǒng)一的用戶登錄權限驗證了。
1) springaop 用戶統(tǒng)一驗證的問題
統(tǒng)一驗證我們可以使用springaop的前置通知或者是環(huán)繞通知來實現(xiàn)
@aspect // 說明該類為一個切面 @component public class useraspect { // 定義切點,使用 aspectj表達式語法,攔截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è)務方法 result = joinpoint.proceed(); } catch (throwable throwable) { throwable.printstacktrace(); } system.out.println("執(zhí)行環(huán)繞通知的后置方法"); return result; } }
我們發(fā)現(xiàn)原生的springaop的切面實現(xiàn)用戶登錄權限的校驗功能,會有兩個問題
我們是獲取不到httpsession對象的如果我們只對一部分方法進行攔截,像登錄和注冊這樣的方法就沒有必要攔截,這樣的很難定義排除對應方法的規(guī)則,甚至說沒有辦法定義。
那就可以使用spring的攔截器
2) spring攔截器
對于上面兩個問題spring中提供了解決方案,提供了具體實現(xiàn)的攔截器:handlerlnterceptor,攔截器的實現(xiàn)分為兩個步驟:
創(chuàng)建自定義攔截器,實現(xiàn)handlerlnterceptor接口的prehandle(執(zhí)行具體方法之前的預處理)方法將自定義攔截器加入webmvcconfigurer的addinterceptors
1.自定義攔截器
用戶登錄權限校驗,自定義攔截器代碼實現(xiàn):
/** * 定義自定義攔截器實現(xià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)配置信息中,代碼實現(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) 攔截器實現(xiàn)原理
原本正常的調(diào)用流程是這樣的:
但是添加了攔截器后,在調(diào)用controller之前會進行相對應業(yè)務處理
?所有?法都會執(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)用預處理 if (!mappedhandler.applyprehandle(processedrequest, response)) { return; } // 執(zhí)行controller中的業(yè)務 mv = ha.handle(processedrequest, response, mappedhandler.gethandler()); if (asyncmanager.isconcurrenthandlingstarted()) { return; } // ......后面代碼省略 }
從上述源碼可以看出在開始執(zhí)? controller 之前,會先調(diào)? 預處理?法 applyprehandle,? applyprehandle ?法的實現(xiàn)源碼如下
boolean applyprehandle(httpservletrequest request, httpservletresponse response) throws exception { for(int i = 0; i < this.interceptorlist.size(); this.interceptorindex = i++) { // 獲取項?中使?的攔截器 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 中會獲取所有的攔截器 handlerinterceptor 并執(zhí)?攔截器中 的 prehandle ?法,這樣就會咱們前?定義的攔截器對應上了,如下圖所示 :
只有當我們重寫的方法放回true的時候才會繼續(xù)走調(diào)用controller的業(yè)務代碼,否則就是直接放回給前端
攔截器的實現(xiàn)原理:
- 從宏觀上來講的話,它是根據(jù)aop的思想來去執(zhí)行的,把統(tǒng)一的方法放到前置處理器來進行處理
- 在spring中的攔截器實現(xiàn),就是在調(diào)度器類里的調(diào)度方法,調(diào)度方法在真正調(diào)用controller之前,它會有一個方法先去掃描當前spring中所有攔截器的一個列表,然后去執(zhí)行這些攔截器,只有當攔截器執(zhí)行通過的時候,它才會繼續(xù)走后面的流程,才會去走controller然后返回結果給前端。
4)同一訪問前綴添加
該方法可以給所有接口添加一個訪問前綴,讓前端訪問接口時都要加上blog,比如原來是/add,添加前綴后就是/blog/add
@configuration public class appconfig implements webmvcconfigurer { @override public void configurepathmatch(pathmatchconfigurer configurer) { configurer.addpathprefix("blog",pre->true); } }
其中第?個參數(shù)是?個表達式,設置為 true 表示啟動前綴
2. 統(tǒng)一異常處理
同一異常處理是通過@controlleradvice+@exceptionhandler兩個注解結合實現(xiàn)的,@controlleradvice表示控制器通知類,@exceptionhandler是異常處理,兩個結合起來就表示出現(xiàn)異常的時候執(zhí)行某一個通知,也就是執(zhí)行某個方法,代碼實現(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","服務器接口異常"); return map; } }
注意:方法名和返回值可以任意,重要的是注解。
這里的代碼表示的是發(fā)生任何異常都給前端返回一個hashmap,也可以指定異常進行處理代碼如下
@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","服務器接口異常"); 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; } }
當有多個異常通知時,匹配順序為當前類及其?類向上依次匹配
3. 統(tǒng)一數(shù)據(jù)返回格式
1)統(tǒng)一數(shù)據(jù)返回的好處
統(tǒng)一數(shù)據(jù)返回格式有很多好處
2)統(tǒng)一數(shù)據(jù)返回實現(xiàn)
統(tǒng)一的數(shù)據(jù)返回格式可以使用@controlleradvice+responsebodyadvice實現(xiàn)
- supports方法返回true表示對返回內(nèi)容進行重寫,就會執(zhí)行beforebodywrite方法
- beforebodywrite方法中body就是controller返回的內(nèi)容
@controlleradvice public class responseadvice implements responsebodyadvice { /** * 內(nèi)容是否需要重寫,此方法可以選擇部分控制器和方法進行重寫 * 返回 true表示重寫 */ @override public boolean supports(methodparameter returntype, class convertertype) { return true; } /** *控制器方法返回之前會調(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; } }
關于關于spring的統(tǒng)一功能處理(攔截器)實現(xiàn)的文章就介紹至此,更多相關spring的統(tǒng)一功能處理內(nèi)容請搜索碩編程以前的文章,希望以后支持碩編程!