摘要:攔截器攔截下那些沒(méi)有與注解標(biāo)注的方法請(qǐng)求,并進(jìn)行用戶認(rèn)證。直接根據(jù)編寫(xiě)的代碼生成原生的代碼,所以不會(huì)存在任何性能問(wèn)題解決方案為了解決攔截器中使用反射的性能問(wèn)題,我們學(xué)習(xí)的設(shè)計(jì)思路,在啟動(dòng)時(shí)直接完成所有反射注解的讀取,存入內(nèi)存。
問(wèn)題描述 權(quán)限認(rèn)證
權(quán)限認(rèn)證一直是比較復(fù)雜的問(wèn)題,如果是實(shí)驗(yàn)這種要求不嚴(yán)格的產(chǎn)品,直接逃避掉權(quán)限認(rèn)證。
軟件設(shè)計(jì)與編程實(shí)踐的實(shí)驗(yàn),后臺(tái)直接用Spring Data REST,好使是好使,但是不能在實(shí)際項(xiàng)目中運(yùn)用,直接把api自動(dòng)生成了,誰(shuí)調(diào)用都行。
在商業(yè)項(xiàng)目中,沒(méi)有權(quán)限是不行的。
注解關(guān)于權(quán)限,一直沒(méi)有找到很好的解決方案。直到網(wǎng)上送檢項(xiàng)目,因功能簡(jiǎn)單,且用戶角色單一,潘老師提出了利用注解實(shí)現(xiàn)權(quán)限認(rèn)證的方案。
兩個(gè)注解,AdminOnly標(biāo)注只能給管理員用的方法,Anonymous標(biāo)注對(duì)外的無(wú)需認(rèn)證的接口,其他的未標(biāo)注的是給普通用戶使用的。
示例代碼示例代碼地址:auth-annotation - mengyunzhi
開(kāi)發(fā)環(huán)境:Java 1.8 + Spring Boot 2.1.2.RELEASE
實(shí)現(xiàn) 攔截器根據(jù)三類方法,對(duì)用戶權(quán)限進(jìn)行攔截,使用攔截器 + AOP的模式實(shí)現(xiàn)。
攔截器攔截下那些沒(méi)有AdminOnly與Anonymous注解標(biāo)注的方法請(qǐng)求,并進(jìn)行用戶認(rèn)證。
攔截器過(guò)完之后,去執(zhí)行請(qǐng)求方法。
AOP切AdminOnly注解的前置通知,植入一段管理員認(rèn)證的切面邏輯。
對(duì)Anonymous注解不進(jìn)行任何處理,實(shí)現(xiàn)了匿名用戶的訪問(wèn)。
區(qū)別這樣一看,攔截器就和AOP很像。那是因?yàn)槲覀冞@個(gè)例子還遠(yuǎn)沒(méi)有發(fā)揮出AOP的實(shí)際價(jià)值。
AOP比這個(gè)例子中看上去,強(qiáng)大得多。
最近學(xué)習(xí)了設(shè)計(jì)模式中的代理模式,與AOP息息相關(guān),我會(huì)在以后的文章中與大家一同學(xué)習(xí)。
攔截器聲明攔截器,第三個(gè)參數(shù)就是當(dāng)前被攔截的方法。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { HandlerMethod handlerMethod = (HandlerMethod) handler; }
基本思路
利用反射獲取當(dāng)前方法中是否標(biāo)注有AdminOnly與Anonymous注解,如果沒(méi)有,則進(jìn)行普通用戶認(rèn)證。
AdminOnly adminOnly = handlerMethod.getMethodAnnotation(AdminOnly.class); Anonymous anonymous = handlerMethod.getMethodAnnotation(Anonymous.class); if (adminOnly != null && anonymous != null) { return true; } boolean result = false; // 進(jìn)行用戶認(rèn)證 return result;性能優(yōu)化 反射
每次請(qǐng)求,都要走攔截器,調(diào)用getMethodAnnotation方法。
我們?nèi)タ纯?b>getMethodAnnotation方法的源碼實(shí)現(xiàn):
org.springframework.web.method.HandlerMethod中的getMethodAnnotation方法:
@Nullable public A getMethodAnnotation(Class annotationType) { return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); }
該方法又調(diào)用了AnnotatedElementUtils.findMergedAnnotation方法,我們?cè)冱c(diǎn)進(jìn)去看看:
org.springframework.core.annotation.AnnotatedElementUtils中的findMergedAnnotation實(shí)現(xiàn):
@Nullable public static A findMergedAnnotation(AnnotatedElement element, Class annotationType) { // Shortcut: directly present on the element, with no merging needed? A annotation = element.getDeclaredAnnotation(annotationType); if (annotation != null) { return AnnotationUtils.synthesizeAnnotation(annotation, element); } // Exhaustive retrieval of merged annotation attributes... AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false); return (attributes != null ? AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element) : null); }
該方法是調(diào)用AnnotatedElement接口中聲明的getDeclaredAnnotation方法進(jìn)行注解獲?。?/p>
AnnotatedElement接口,存在于java反射包中:
話不多說(shuō),反射,就存在性能問(wèn)題!
個(gè)人理解同樣是Java,我們看看Google對(duì)于Android反射的態(tài)度就好了。
我記得之前我去過(guò)Google Android的官網(wǎng),官方不推薦在Android中使用框架,這可能帶來(lái)嚴(yán)重的性能問(wèn)題,其中就有考慮到傳統(tǒng)Java框架中大量使用的反射。
這是國(guó)外一篇關(guān)于反射的文章,反射到底有多慢?:How Slow is Reflection in Android?
文中提到了一項(xiàng)規(guī)范,即用戶期待應(yīng)用的啟動(dòng)時(shí)間的平均值為2s。
NYTimes Android App中使用Google的Gson進(jìn)行數(shù)據(jù)解析,這個(gè)在我們后臺(tái)使用的還是挺廣泛的,和阿里的fastjson齊名,都是非?;鸬?b>json庫(kù)。
NYTimes的工程師發(fā)現(xiàn)Gson中使用反射來(lái)獲取數(shù)據(jù)類型,導(dǎo)致應(yīng)用啟動(dòng)時(shí)增加了大約700ms的延遲。
ActiveAndroid是一個(gè)使用反射實(shí)現(xiàn)的庫(kù),特意去Github逛了一手,4000多star,這是相當(dāng)流行的開(kāi)源項(xiàng)目了!
Scribd:1093ms for call com.activeandroid.ActiveAndroid.initialize。
Myntra:1421ms for call com.activeandroid.ActiveAndroid.initialize。
Data-Binding
打臉?Android不是不推薦使用框架嗎?那為什么Google又推出了Data-Binding呢?
注意,Google考慮的是第三方框架高額的開(kāi)銷而引發(fā)性能問(wèn)題。
去看看Data-Binding的優(yōu)點(diǎn),最重要的一條就是該框架不使用反射,使用動(dòng)態(tài)代碼生成技術(shù),不會(huì)因?yàn)槭褂迷摽蚣芏斐尚阅軉?wèn)題。
直接根據(jù)編寫(xiě)的代碼生成原生Android的代碼,所以不會(huì)存在任何性能問(wèn)題!
解決方案為了解決攔截器中使用反射的性能問(wèn)題,我們學(xué)習(xí)SpringBoot的設(shè)計(jì)思路,在啟動(dòng)時(shí)直接完成所有反射注解的讀取,存入內(nèi)存。
之后每次攔截器直接從內(nèi)存中讀取,提高性能。
監(jiān)聽(tīng)容器啟動(dòng)事件,在容器啟動(dòng)時(shí)執(zhí)行以下代碼,掃描所有控制器,及其方法上的注解,如果符合條件,則放到HashMap中。
// 初始化組件掃描Scanner,禁用默認(rèn)的filter ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); // 添加過(guò)濾條件,要求組件上有RestController注解 scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class)); // 在當(dāng)前項(xiàng)目包下掃描所有符合條件的組件 for (BeanDefinition beanDefinition : scanner.findCandidateComponents(basePackageName)) { // 獲取當(dāng)前組件的完整類名 String name = beanDefinition.getBeanClassName(); try { // 利用反射獲取相關(guān)類 Class> clazz = Class.forName(name); // 初始化方法名List List攔截器修改methodNameList = new ArrayList<>(); // 獲取當(dāng)前類(不包括父類,所以要求控制器間不能繼承)中所有聲明方法 for (Method method : clazz.getDeclaredMethods()) { // 獲取方法上的注解 AdminOnly adminOnly = method.getAnnotation(AdminOnly.class); Anonymous anonymous = method.getAnnotation(Anonymous.class); // 如果該方法不存在AdminOnly和Anonymous注解 if (adminOnly == null && anonymous == null) { // 添加到List中 methodNameList.add(method.getName()); } } // 添加到Map中 AuthAnnotationConfig.getAnnotationsMap().put(clazz, methodNameList); } catch (ClassNotFoundException e) { logger.error("掃描注解配置時(shí),發(fā)生了ClassNotFoundException異常"); } }
原來(lái)的攔截器是這樣的:
AdminOnly adminOnly = handlerMethod.getMethodAnnotation(AdminOnly.class); Anonymous anonymous = handlerMethod.getMethodAnnotation(Anonymous.class); if (adminOnly != null && anonymous != null) { return true; } boolean result = false; // 進(jìn)行用戶認(rèn)證 return result;
現(xiàn)在是這樣的:
logger.debug("獲取當(dāng)前請(qǐng)求方法的組件類型"); Class> clazz = handlerMethod.getBeanType(); logger.debug("獲取當(dāng)前處理請(qǐng)求的方法名"); String methodName = handlerMethod.getMethod().getName(); logger.debug("獲取當(dāng)前類中需認(rèn)證的方法名"); ListauthMethodNames = AuthAnnotationConfig.getAnnotationsMap().get(clazz); logger.debug("如果List為空或者不包含在認(rèn)證方法中,釋放攔截"); if (authMethodNames == null || !authMethodNames.contains(methodName)) { return true; } logger.debug("進(jìn)行用戶認(rèn)證"); boolean result = false; // 用戶認(rèn)證 return result;
之前用了兩次反射,現(xiàn)在是調(diào)用了handlerMethod.getBeanType()和handlerMethod.getMethod().getName()。
再去看看這兩個(gè)的實(shí)現(xiàn):
getBeanType
public Class> getBeanType() { return this.beanType; }
getMethod
public Method getMethod() { return this.method; }
都是在org.springframework.web.method.HandlerMethod類中直接返回屬性,我們推斷:這個(gè)HandlerMethod,應(yīng)該是Spring在容器啟動(dòng)時(shí)就已經(jīng)構(gòu)造好的方法對(duì)象,在攔截器執(zhí)行期間,沒(méi)有調(diào)用反射。
注解的注解現(xiàn)在是注解少,我們寫(xiě)兩行,感覺(jué)問(wèn)題不大:
// 獲取方法上的注解 AdminOnly adminOnly = method.getAnnotation(AdminOnly.class); Anonymous anonymous = method.getAnnotation(Anonymous.class);
以后如果認(rèn)證注解多了呢?
我們期待這樣,有一個(gè)通用的注解來(lái)判定當(dāng)前方法是否要被攔截,而AdminOnly和Anonymous應(yīng)繼承該注解的功能,這樣以后再想添加不被攔截器攔截的注解,就不需要修改啟動(dòng)時(shí)掃描的方法了。
// 獲取授權(quán)注解 AdminAuth adminAuth = method.getAnnotation(AdminAuth.class);
我們期望像Spring Boot一樣,在注解上加注解,實(shí)現(xiàn)復(fù)合注解。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @ControllerAdvice @ResponseBody public @interface RestControllerAdvice { }構(gòu)造注解
如果對(duì)Java自定義注解不了解,可以去慕課網(wǎng)學(xué)習(xí)相關(guān)課程:全面解析Java注解 - 慕課網(wǎng)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface AdminAuth { }
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}),該注解可以標(biāo)注在方法上,也可以標(biāo)注在其他注解上。
@Retention(RetentionPolicy.RUNTIME),該注解一直保留到程序運(yùn)行期間。
給注解加注解AdminOnly:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @AdminAuth public @interface AdminOnly { }
Anonymous:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @AdminAuth public @interface Anonymous { }解析注解
加注解很簡(jiǎn)單,重要的是怎么解析該注解。
調(diào)用反射包中的Method類提供的getAnnotation方法,只會(huì)告訴我們當(dāng)前標(biāo)注了什么注解。
比如:
@AdminOnly public void test() { }
我們可以通過(guò)getAnnotation獲取AdminOnly,但是獲取不到注解在@AdminOnly上的@AdminAuth注解。
怎么獲取注解的注解呢?
找了一上午,不得不說(shuō),我解決這個(gè)問(wèn)題還是靠一定的運(yùn)氣的。在我要放棄的時(shí)候,在Google搜出了SpringFramework中的注解工具類AnnotationUtils:
隨手打開(kāi)文檔:Class AnnotationUtils - Spring Core Docs
第四個(gè)方法就是我想要的:
使用該工具類,能直接獲取方法上標(biāo)注在注解上的注解:
@AdminOnly public void test() { }
AdminAuth adminAuth = AnnotationUtils.getAnnotation(method, AdminAuth.class);
這種方法能獲取到標(biāo)注在test方法上繼承而來(lái)的@AdminAuth注解。
最終代碼:
@Component public class InitAnnotationsConfig implements ApplicationListener總結(jié){ // 基礎(chǔ)包名 private static final String basePackageName = "com.mengyunzhi.checkApplyOnline"; // 日志 private static final Logger logger = LoggerFactory.getLogger(InitAnnotationsConfig.class); @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 初始化組件掃描Scanner,禁用默認(rèn)的filter ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); // 添加過(guò)濾條件,要求組件上有RestController注解 scanner.addIncludeFilter(new AnnotationTypeFilter(RestController.class)); // 在當(dāng)前項(xiàng)目包下掃描所有符合條件的組件 for (BeanDefinition beanDefinition : scanner.findCandidateComponents(basePackageName)) { // 獲取當(dāng)前組件的完整類名 String name = beanDefinition.getBeanClassName(); try { // 利用反射獲取相關(guān)類 Class> clazz = Class.forName(name); // 初始化方法名List List methodNameList = new ArrayList<>(); // 獲取當(dāng)前類(不包括父類,所以要求控制器間不能繼承)中所有聲明方法 for (Method method : clazz.getDeclaredMethods()) { // 獲取授權(quán)注解 AdminAuth adminAuth = AnnotationUtils.getAnnotation(method, AdminAuth.class); // 如果該方法不被授權(quán),則需要認(rèn)證 if (adminAuth == null) { // 添加到List中 methodNameList.add(method.getName()); } } // 添加到Map中 AuthAnnotationConfig.getAnnotationsMap().put(clazz, methodNameList); } catch (ClassNotFoundException e) { logger.error("掃描注解配置時(shí),發(fā)生了ClassNotFoundException異常"); } } } }
學(xué)會(huì)了一個(gè)解決問(wèn)題的新辦法:某個(gè)框架應(yīng)該也遇到過(guò)你所遇到的問(wèn)題,去找找框架中的工具類,這可能會(huì)很有幫助。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://hztianpu.com/yun/73201.html
摘要:認(rèn)證服務(wù)器和瀏覽器控制臺(tái)也沒(méi)有報(bào)錯(cuò)信息。這里簡(jiǎn)單介紹下如何查閱源碼,首先全局搜索自己的配置因?yàn)檫@個(gè)地址是認(rèn)證服務(wù)器請(qǐng)求授權(quán)的,所以,請(qǐng)求認(rèn)證的過(guò)濾器肯定包含他。未完待續(xù),下一篇介紹資源服務(wù)器和認(rèn)證服務(wù)器的集成。 基于spring-security-oauth2-實(shí)現(xiàn)單點(diǎn)登錄 文章代碼地址:鏈接描述可以下載直接運(yùn)行,基于springboot2.1.5,springcloud Green...
摘要:為了達(dá)到很好的效果,我們使用來(lái)對(duì)的緩存進(jìn)行管理配置會(huì)話管理器,對(duì)會(huì)話時(shí)間進(jìn)行控制手動(dòng)清空緩存由于驗(yàn)證用戶名和密碼之前,一般需要驗(yàn)證驗(yàn)證碼的。 前言 本文主要講解的知識(shí)點(diǎn)有以下: Shiro授權(quán)過(guò)濾器使用 Shiro緩存 與Ehcache整合 Shiro應(yīng)用->實(shí)現(xiàn)驗(yàn)證碼功能 記住我功能 一、授權(quán)過(guò)濾器測(cè)試 我們的授權(quán)過(guò)濾器使用的是permissionsAuthorization...
摘要:的自身注解的用法。所以自定義注解的作用很廣。但是在這里,我僅僅基于的來(lái)實(shí)現(xiàn)適用于它的自定義注解。其他的自定義的注解的編寫(xiě)思路和這個(gè)也是類似的。 基于shiro的自定義注解的擴(kuò)展 根據(jù)我的上一篇文章,權(quán)限設(shè)計(jì)的雜談中,涉及到了有關(guān)于前后端分離中,頁(yè)面和api接口斷開(kāi)表與表層面的關(guān)聯(lián),另辟蹊徑從其他角度找到方式進(jìn)行關(guān)聯(lián)。這里我們主要采取了shiro的自定義注解的方案。本篇文章主要解決以下的...
閱讀 4175·2021-09-22 10:02
閱讀 3454·2019-08-30 15:52
閱讀 3139·2019-08-30 12:51
閱讀 844·2019-08-30 11:08
閱讀 2154·2019-08-29 15:18
閱讀 3186·2019-08-29 12:13
閱讀 3709·2019-08-29 11:29
閱讀 1978·2019-08-29 11:13