Spring中的@CrossOrigin注冊處理方法源碼解析
前言
@CrossOrigin源碼解析主要分為兩個階段:
① @CrossOrigin注釋的方法掃描注冊。
② 請求匹配@CrossOrigin注釋的方法。
本文針對第①階段從源碼角度進(jìn)行解析,關(guān)于第②階段請參照《Spring 注解面面通 之 @CrossOrigin 處理請求源碼解析》。
注意:@CrossOrigin是基于@RequestMapping,@RequestMapping注釋方法掃描注冊的起點是RequestMappingHandlerMapping.afterPropertiesSet()。
@CrossOrigin注釋方法掃描注冊
@CrossOrigin注釋方法掃描注冊流程

1) RequestMappingHandlerMapping.afterPropertiesSet()、AbstractHandlerMethodMapping.afterPropertiesSet()、AbstractHandlerMethodMapping.initHandlerMethods()、AbstractHandlerMethodMapping.detectHandlerMethods(...)、AbstractHandlerMethodMapping.registerHandlerMethod(...)方法。
2) AbstractHandlerMethodMapping.MappingRegistry.register(...)方法。
AbstractHandlerMethodMapping.MappingRegistry.register(...)方法里開始初始化CORS的配置,并以方法粒度將其注冊到corsLookup中,corsLookup是CORS應(yīng)用的關(guān)鍵。
/**
* 進(jìn)行映射注冊.
*/
public void register(T mapping, Object handler, Method method) {
// 首先,獲取寫入鎖.
this.readWriteLock.writeLock().lock();
try {
// 創(chuàng)建HandlerMethod.
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 驗證映射唯一性.
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è)置映射名稱.
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 {
// 釋放寫入鎖.
this.readWriteLock.writeLock().unlock();
}
}3) RequestMappingHandlerMapping.initCorsConfiguration(...)方法。
① 取得當(dāng)前解析方法所屬類的類型。
② 在類級別和方法級別分別查找@CrossOrigin注解。
③ 若類級別和方法級別不存在@CrossOrigin注解,則跳過此部分處理邏輯。
④ 初始化CorsConfiguration配置對象。
⑤ 首先更新類級別@CrossOrigin注解信息到CorsConfiguration配置對象,其次更新方法級別@CrossOrigin注解信息到CorsConfiguration配置對象。兩者之間是可以簡單理解為合集關(guān)系,在類級別@CrossOrigin注解信息基礎(chǔ)上,填加方法級別@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);
// 獲取方法所屬類型.
Class<?> beanType = handlerMethod.getBeanType();
// 查找類級別注釋.
CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
// 查找方法級別注釋.
CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
// 類和方法級別無注釋,跳過處理.
if (typeAnnotation == null && methodAnnotation == null) {
return null;
}
// 初始化配置.
CorsConfiguration config = new CorsConfiguration();
// 更新類級別@CrossOrigin注解配置.
updateCorsConfig(config, typeAnnotation);
// 更新方法級別@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的有效值時,解析@CrossOrigin注解的maxAge屬性到CorsConfiguration配置。
/**
* 更新CORS配置.
*/
private void updateCorsConfig(CorsConfiguration config, @Nullable CrossOrigin annotation) {
// 注解為空,跳過處理.
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è)置所有源均允許。
② 若CorsConfiguration配置的allowedMethods屬性尚未設(shè)置時,則設(shè)置所有源均允許。
③ 若CorsConfiguration配置的allowedHeaders、resolvedMethods屬性尚未設(shè)置時,則設(shè)置所有頭均允許。
?④ 若CorsConfiguration配置的maxAge屬性尚未設(shè)置時,則設(shè)置為1800秒(30分鐘)。
/**
* 默認(rèn)情況下,新建的CorsConfiguration不允許任何跨源請求,必須填加相應(yīng)配置以允許請求.
*
* 使用這個方法為未初始化的配置打開默認(rèn)的跨域設(shè)置,包括:GET、HEAD、POST.
* 但是請注意,此方法不會覆蓋任何已設(shè)置的現(xiàn)有值.
*
* 如果尚未設(shè)置,則應(yīng)用以下默認(rèn)值:
* 允許所有源.
* 允許簡單HTTP方法:GET、HEAD、POST.
* 允許所有HTTP頭.
* 設(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è)置為1800秒(30分鐘).
if (this.maxAge == null) {
this.maxAge = 1800L;
}
return this;
}總結(jié)
正如文中所說,@CrossOrigin解析的目的,即是將解析后的配置注冊到AbstractHandlerMethodMapping.MappingRegistry.corsLookup屬性中,以便webmvc模塊處理請求使用。
?源碼解析基于spring-framework-5.0.5.RELEASE版本源碼。
到此這篇關(guān)于Spring中的@CrossOrigin注冊處理方法源碼解析的文章就介紹到這了,更多相關(guān)@CrossOrigin注冊處理方法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
重寫hashCode()和equals()方法詳細(xì)介紹
這篇文章主要介紹了重寫hashCode()和equals()方法詳細(xì)介紹,涉及重寫equals()方法,重寫hashCode()方法,重寫equals()而不重寫hashCode()的風(fēng)險等相關(guān)內(nèi)容的介紹,具有一定借鑒價值,需要的朋友可以參考下2018-01-01

