Spring中的@CrossOrigin注冊(cè)處理方法源碼解析
前言
@CrossOrigin源碼解析主要分為兩個(gè)階段:
① @CrossOrigin注釋的方法掃描注冊(cè)。
② 請(qǐng)求匹配@CrossOrigin注釋的方法。
本文針對(duì)第①階段從源碼角度進(jìn)行解析,關(guān)于第②階段請(qǐng)參照《Spring 注解面面通 之 @CrossOrigin 處理請(qǐng)求源碼解析》。
注意:@CrossOrigin是基于@RequestMapping,@RequestMapping注釋方法掃描注冊(cè)的起點(diǎn)是RequestMappingHandlerMapping.afterPropertiesSet()。
@CrossOrigin注釋方法掃描注冊(cè)
@CrossOrigin注釋方法掃描注冊(cè)流程
1) RequestMappingHandlerMapping.afterPropertiesSet()、AbstractHandlerMethodMapping.afterPropertiesSet()、AbstractHandlerMethodMapping.initHandlerMethods()、AbstractHandlerMethodMapping.detectHandlerMethods(...)、AbstractHandlerMethodMapping.registerHandlerMethod(...)方法。
2) AbstractHandlerMethodMapping.MappingRegistry.register(...)方法。
AbstractHandlerMethodMapping.MappingRegistry.register(...)方法里開(kāi)始初始化CORS的配置,并以方法粒度將其注冊(cè)到corsLookup中,corsLookup是CORS應(yīng)用的關(guān)鍵。
/** * 進(jìn)行映射注冊(cè). */ public void register(T mapping, Object handler, Method method) { // 首先,獲取寫(xiě)入鎖. this.readWriteLock.writeLock().lock(); try { // 創(chuàng)建HandlerMethod. HandlerMethod handlerMethod = createHandlerMethod(handler, method); // 驗(yàn)證映射唯一性. assertUniqueMethodMapping(handlerMethod, mapping); if (logger.isInfoEnabled()) { logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod); } this.mappingLookup.put(mapping, handlerMethod); // 搜索直接URL. List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { this.urlLookup.add(url, mapping); } // 解析name,并設(shè)置映射名稱(chēng). String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } // 初始化CORS配置. CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { // 釋放寫(xiě)入鎖. this.readWriteLock.writeLock().unlock(); } }
3) RequestMappingHandlerMapping.initCorsConfiguration(...)方法。
① 取得當(dāng)前解析方法所屬類(lèi)的類(lèi)型。
② 在類(lèi)級(jí)別和方法級(jí)別分別查找@CrossOrigin注解。
③ 若類(lèi)級(jí)別和方法級(jí)別不存在@CrossOrigin注解,則跳過(guò)此部分處理邏輯。
④ 初始化CorsConfiguration配置對(duì)象。
⑤ 首先更新類(lèi)級(jí)別@CrossOrigin注解信息到CorsConfiguration配置對(duì)象,其次更新方法級(jí)別@CrossOrigin注解信息到CorsConfiguration配置對(duì)象。兩者之間是可以簡(jiǎn)單理解為合集關(guān)系,在類(lèi)級(jí)別@CrossOrigin注解信息基礎(chǔ)上,填加方法級(jí)別@CrossOrigin注解信息。
⑥ 若@CrossOrigin注解未標(biāo)注允許的HTTP方法,則以@RequestMapping注解標(biāo)注的HTTP方法作為允許的HTTP方法。
⑦ 調(diào)用config.applyPermitDefaultValues()為未初始化的配置設(shè)置默認(rèn)值。
/** * 初始化CORS配置. */ @Override protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { // 創(chuàng)建HandlerMethod. HandlerMethod handlerMethod = createHandlerMethod(handler, method); // 獲取方法所屬類(lèi)型. Class<?> beanType = handlerMethod.getBeanType(); // 查找類(lèi)級(jí)別注釋. CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class); // 查找方法級(jí)別注釋. CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class); // 類(lèi)和方法級(jí)別無(wú)注釋?zhuān)^(guò)處理. if (typeAnnotation == null && methodAnnotation == null) { return null; } // 初始化配置. CorsConfiguration config = new CorsConfiguration(); // 更新類(lèi)級(jí)別@CrossOrigin注解配置. updateCorsConfig(config, typeAnnotation); // 更新方法級(jí)別@CrossOrigin注解配置. updateCorsConfig(config, methodAnnotation); // 若@CrossOrigin未標(biāo)準(zhǔn)HTTP方法,則以@RequestMapping標(biāo)準(zhǔn)HTTP方法為準(zhǔn). if (CollectionUtils.isEmpty(config.getAllowedMethods())) { for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) { config.addAllowedMethod(allowedMethod.name()); } } // 為未初始化的配置設(shè)置默認(rèn)值. return config.applyPermitDefaultValues(); }
4) RequestMappingHandlerMapping.updateCorsConfig(...)方法。
① 解析@CrossOrigin注解的origins屬性到CorsConfiguration配置。
?② 解析@CrossOrigin注解的methods屬性到CorsConfiguration配置。
③ 解析@CrossOrigin注解的allowedHeaders屬性到CorsConfiguration配置。
④ 解析@CrossOrigin注解的exposedHeaders屬性到CorsConfiguration配置。
⑤ 解析@CrossOrigin注解的allowCredentials屬性到CorsConfiguration配置。allowCredentials的有效值為true或false。
⑥ 當(dāng)CorsConfiguration配置未設(shè)置maxAge且@CrossOrigin注解的maxAge屬性為大于0的有效值時(shí),解析@CrossOrigin注解的maxAge屬性到CorsConfiguration配置。
/** * 更新CORS配置. */ private void updateCorsConfig(CorsConfiguration config, @Nullable CrossOrigin annotation) { // 注解為空,跳過(guò)處理. if (annotation == null) { return; } // 解析@CrossOrigin.origins屬性到配置. for (String origin : annotation.origins()) { config.addAllowedOrigin(resolveCorsAnnotationValue(origin)); } // 解析@CrossOrigin.methods屬性到配置. for (RequestMethod method : annotation.methods()) { config.addAllowedMethod(method.name()); } // 解析@CrossOrigin.allowedHeaders屬性到配置. for (String header : annotation.allowedHeaders()) { config.addAllowedHeader(resolveCorsAnnotationValue(header)); } // 解析@CrossOrigin.exposedHeaders屬性到配置. for (String header : annotation.exposedHeaders()) { config.addExposedHeader(resolveCorsAnnotationValue(header)); } // 解析@CrossOrigin.allowCredentials屬性到配置. String allowCredentials = resolveCorsAnnotationValue(annotation.allowCredentials()); if ("true".equalsIgnoreCase(allowCredentials)) { config.setAllowCredentials(true); } else if ("false".equalsIgnoreCase(allowCredentials)) { config.setAllowCredentials(false); } else if (!allowCredentials.isEmpty()) { throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", " + "or an empty string (\"\"): current value is [" + allowCredentials + "]"); } // 解析@CrossOrigin.maxAge屬性到配置. if (annotation.maxAge() >= 0 && config.getMaxAge() == null) { config.setMaxAge(annotation.maxAge()); } }
5) CorsConfiguration.applyPermitDefaultValues()方法。
?① 若CorsConfiguration配置的allowedOrigins屬性尚未設(shè)置時(shí),則設(shè)置所有源均允許。
② 若CorsConfiguration配置的allowedMethods屬性尚未設(shè)置時(shí),則設(shè)置所有源均允許。
③ 若CorsConfiguration配置的allowedHeaders、resolvedMethods屬性尚未設(shè)置時(shí),則設(shè)置所有頭均允許。
?④ 若CorsConfiguration配置的maxAge屬性尚未設(shè)置時(shí),則設(shè)置為1800秒(30分鐘)。
/** * 默認(rèn)情況下,新建的CorsConfiguration不允許任何跨源請(qǐng)求,必須填加相應(yīng)配置以允許請(qǐng)求. * * 使用這個(gè)方法為未初始化的配置打開(kāi)默認(rèn)的跨域設(shè)置,包括:GET、HEAD、POST. * 但是請(qǐng)注意,此方法不會(huì)覆蓋任何已設(shè)置的現(xiàn)有值. * * 如果尚未設(shè)置,則應(yīng)用以下默認(rèn)值: * 允許所有源. * 允許簡(jiǎn)單HTTP方法:GET、HEAD、POST. * 允許所有HTTP頭. * 設(shè)置最大使用時(shí)間1800秒(30分鐘). */ public CorsConfiguration applyPermitDefaultValues() { // 若未設(shè)置允許源,則設(shè)置所有源均允許. if (this.allowedOrigins == null) { this.allowedOrigins = DEFAULT_PERMIT_ALL; } // 若未設(shè)置允許方法,則設(shè)置允許GET、HEAD、POST. if (this.allowedMethods == null) { this.allowedMethods = DEFAULT_PERMIT_METHODS; this.resolvedMethods = DEFAULT_PERMIT_METHODS .stream().map(HttpMethod::resolve).collect(Collectors.toList()); } // 若未設(shè)置允許頭,則設(shè)置所有頭均允許. if (this.allowedHeaders == null) { this.allowedHeaders = DEFAULT_PERMIT_ALL; } // 若未設(shè)置最大使用時(shí)間,則設(shè)置為1800秒(30分鐘). if (this.maxAge == null) { this.maxAge = 1800L; } return this; }
總結(jié)
正如文中所說(shuō),@CrossOrigin解析的目的,即是將解析后的配置注冊(cè)到AbstractHandlerMethodMapping.MappingRegistry.corsLookup屬性中,以便webmvc模塊處理請(qǐng)求使用。
?源碼解析基于spring-framework-5.0.5.RELEASE版本源碼。
到此這篇關(guān)于Spring中的@CrossOrigin注冊(cè)處理方法源碼解析的文章就介紹到這了,更多相關(guān)@CrossOrigin注冊(cè)處理方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Java HashMap自動(dòng)排序的簡(jiǎn)單剖析
這篇文章主要給大家介紹了關(guān)于Java HashMap自動(dòng)排序的簡(jiǎn)單剖析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java實(shí)現(xiàn)幀動(dòng)畫(huà)的實(shí)例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)幀動(dòng)畫(huà)的實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Java實(shí)現(xiàn)精準(zhǔn)Excel數(shù)據(jù)排序的方法詳解
在數(shù)據(jù)處理或者數(shù)據(jù)分析的場(chǎng)景中,需要對(duì)已有的數(shù)據(jù)進(jìn)行排序,在Excel中可以通過(guò)排序功能進(jìn)行整理數(shù)據(jù),而在Java中,則可以借助Excel表格插件對(duì)數(shù)據(jù)進(jìn)行批量排序,下面我們就來(lái)學(xué)習(xí)一下常見(jiàn)的數(shù)據(jù)排序方法吧2023-10-10Android設(shè)備如何保證數(shù)據(jù)同步寫(xiě)入磁盤(pán)的實(shí)現(xiàn)
這篇文章主要介紹了Android設(shè)備如何保證數(shù)據(jù)同步寫(xiě)入磁盤(pán)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09JFINAL+Ajax傳參 array 數(shù)組方法 獲取request中數(shù)組操作
這篇文章主要介紹了JFINAL+Ajax傳參 array 數(shù)組方法 獲取request中數(shù)組操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08Java連接SAP RFC實(shí)現(xiàn)數(shù)據(jù)抽取的示例詳解
這篇文章主要為大家學(xué)習(xí)介紹了Java如何連接SAP RFC實(shí)現(xiàn)數(shù)據(jù)抽取的功能,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,需要的可以了解下2023-08-08java web中 HttpClient模擬瀏覽器登錄后發(fā)起請(qǐng)求
這篇文章主要介紹了java web中 HttpClient模擬瀏覽器登錄后發(fā)起請(qǐng)求的相關(guān)資料,需要的朋友可以參考下2017-05-05