筆記簡(jiǎn)述
本學(xué)習(xí)筆記是由GetMapping注解無(wú)效這個(gè)問(wèn)題引起的,最后發(fā)現(xiàn)是自己的xml配置錯(cuò)誤
調(diào)試源碼最后發(fā)現(xiàn)了spring早已經(jīng)廢棄了默認(rèn)的URL獲取方法,而是采用了最新的方法去實(shí)現(xiàn)
并記錄下新的URL的獲取以及映射的一些細(xì)節(jié)和過(guò)程,最好可以和Spring MVC URL映射 學(xué)習(xí)(上) Spring MVC URL映射 學(xué)習(xí)(下)再結(jié)合源碼調(diào)試了解其中的細(xì)節(jié)
學(xué)習(xí)spring的時(shí)候,學(xué)習(xí)GetMapping注解,了解到他是在spring4.3加的新注解,整合了@RequestMapping(method = RequestMethod.GET)
,讓代碼能夠更加簡(jiǎn)潔。
如下圖圈住的代碼,從含義來(lái)說(shuō)是一模一樣的,可是在實(shí)踐的時(shí)候,GetMapping卻不能傳遞value值,不知道自己到底哪一步錯(cuò)了。記錄在這里,等著明確知道答案了,再補(bǔ)充。
查看源碼打斷點(diǎn)調(diào)試發(fā)現(xiàn)如下信息
在獲取注解信息的時(shí)候,GetMapping的屬性
可是在匹配到RequestMapping的時(shí)候,只有method信息,并不包含value信息
導(dǎo)致了從GetMapping注解上就沒(méi)法獲取到URL信息,從而就出現(xiàn)了注冊(cè)handler的時(shí)候,URL信息不全,最后的handlerMap信息如下圖
也就導(dǎo)致了GetMapping注解無(wú)效的情況,但是還是沒(méi)有發(fā)現(xiàn)其原因
2018年03月17日00:17:08 更新 已經(jīng)發(fā)現(xiàn)了問(wèn)題所在并解決了
先說(shuō)解決方案,在xml文件中加入<mvc:annotation-driven />
,就可以正常使用GetMapping注解了
無(wú)效的原因
在起初使用GetMapping的時(shí)候,查看源碼發(fā)現(xiàn)是由DefaultAnnotationHandlerMapping類調(diào)用實(shí)現(xiàn)的,而在文檔中明確說(shuō)明了@deprecated as of Spring 3.2
,意味著從spring3.2開始就不再推薦使用該類了,而與此同時(shí)GetMapping是從spring4.3才加入的產(chǎn)物,那么必然存在著使用了GetMapping的同時(shí)又使用老的類完成URL屬性獲取操作的問(wèn)題。
直接的問(wèn)題就是使用了GetMapping之后,參數(shù)無(wú)法重新拷貝到RequestMapping中,從而使得數(shù)據(jù)丟失。
AnnotationUtils 類
static <A extends Annotation> A synthesizeAnnotation(A annotation, Object annotatedElement) {
if (annotation == null) {
return null;
}
if (annotation instanceof SynthesizedAnnotation) {
return annotation;
}
Class<? extends Annotation> annotationType = annotation.annotationType();
if (!isSynthesizable(annotationType)) {
return annotation;
}
DefaultAnnotationAttributeExtractor attributeExtractor =
new DefaultAnnotationAttributeExtractor(annotation, annotatedElement);
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
Class<?>[] exposedInterfaces = new Class<?>[] {annotationType, SynthesizedAnnotation.class};
return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler);
}
public static <A extends Annotation> A synthesizeAnnotation(Map<String, Object> attributes,
Class<A> annotationType, AnnotatedElement annotatedElement) {
Assert.notNull(annotationType, "'annotationType' must not be null");
if (attributes == null) {
return null;
}
MapAnnotationAttributeExtractor attributeExtractor =
new MapAnnotationAttributeExtractor(attributes, annotationType, annotatedElement);
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);
Class<?>[] exposedInterfaces = (canExposeSynthesizedMarker(annotationType) ?
new Class<?>[] {annotationType, SynthesizedAnnotation.class} : new Class<?>[] {annotationType});
return (A) Proxy.newProxyInstance(annotationType.getClassLoader(), exposedInterfaces, handler);
}
細(xì)看上面兩個(gè)函數(shù),方法參數(shù)中一個(gè)帶著屬性字段,另一個(gè)沒(méi)有,其實(shí)這兩個(gè)函數(shù)就是新舊的生成RequestMapping注解的方法,其中帶有屬性字段的是新的執(zhí)行函數(shù),傳遞著GetMapping注解的屬性,確保數(shù)據(jù)的連貫性。
源碼分析
URL映射 獲取
接下來(lái)就來(lái)學(xué)習(xí)下xml配置<mvc:annotation-driven />
的執(zhí)行過(guò)程,老套路直接定位到AnnotationDrivenBeanDefinitionParser類(PS:如果這點(diǎn)存在疑問(wèn)可以看看[dfgdfg](fff
如果查看這個(gè)類的parse過(guò)程,大概可以發(fā)現(xiàn)就是注冊(cè)和添加了RequestMappingHandlerMapping、RequestMappingHandlerAdapter 適配器等操作,以及額外的cors、aop等操作。
執(zhí)行RequestMappingHandlerMapping的afterPropertiesSet去實(shí)現(xiàn)實(shí)例化的步驟中完成對(duì)URL屬性的讀取和拼接、存儲(chǔ)的過(guò)程
如圖,最后RequestMappingHandlerMapping實(shí)例化完成后,就去執(zhí)行了initHandlerMethod的方法,和spring mvc獲取URL信息一樣的操作,遍歷所有的類,得到每個(gè)類的方法,再解析每個(gè)可行的方法的注解。
最后執(zhí)行到了RequestMappingHandlerMapping類的getMappingForMethod方法
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
// 獲取方法的注解信息
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
// 再獲取類的注解信息
if (typeInfo != null) {
info = typeInfo.combine(info);
// 合并類的注解信息和方法注解信息
}
}
return info;
}
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
// 獲取RequestMapping注解信息
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
上述代碼中對(duì)每個(gè)handlerType都進(jìn)行了createRequestMappingInfo處理,我感覺(jué)沒(méi)必要啊,畢竟是屬于類層級(jí)的,類的注解信息獲取一次就好了,然后和各類自身的方法合并即可,大不了加入緩存也行,而不是每次都實(shí)際解析操作,這點(diǎn)感覺(jué)怪怪的
AnnotatedElementUtils 類
public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
if (!(element instanceof Class)) {
// 如果元素不是類
A annotation = element.getAnnotation(annotationType);
// 直接獲取期望類型的注解
if (annotation != null) {
// 如果存在,就同步下,返回注解信息
return AnnotationUtils.synthesizeAnnotation(annotation, element);
}
}
AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false);
// 其他情況,先發(fā)現(xiàn)注解可能有用的屬性信息
return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element);
}
獲取GetMapping的屬性得到的數(shù)據(jù)
獲得了方法的注解信息得到的URL信息
合并處理函數(shù)和方法的URL信息得到的完整的URL信息
URL處理
- 先獲取方法的URL信息,如果有了再獲取類的URL信息,進(jìn)行合并操作
- 如果方法沒(méi)有有效的URL信息,則直接返回null
- 如果類沒(méi)有URL信息,則返回方法的URL信息
最后實(shí)例化完成,該bean中的mappingRegistry存儲(chǔ)的URL信息,然后該數(shù)據(jù)成功的在initHandlerMappings完成賦值到dispatchservice中,并且包含了適配器的賦值
URL信息合并
類URL屬性和方法URL信息如何拼接成完整的URL信息
public RequestMappingInfo combine(RequestMappingInfo other) {
String name = combineNames(other);
// 此name會(huì)組合成類的簡(jiǎn)寫名稱+方法名稱
PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
// 這就是URL信息拼接最關(guān)鍵的地方
.....
}
public PatternsRequestCondition combine(PatternsRequestCondition other) {
Set<String> result = new LinkedHashSet<String>();
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
for (String pattern1 : this.patterns) {
for (String pattern2 : other.patterns) {
result.add(this.pathMatcher.combine(pattern1, pattern2));
// 類的URL信息和方法URL信息拼接
}
}
}
else if (!this.patterns.isEmpty()) {
result.addAll(this.patterns);
// 只有類的URL信息
}
else if (!other.patterns.isEmpty()) {
result.addAll(other.patterns);
// 只有方法的URL信息
}
else {
result.add("");
}
return new PatternsRequestCondition(result, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch,
this.useTrailingSlashMatch, this.fileExtensions);
}
URL映射 處理
和Spring MVC URL映射 學(xué)習(xí)(下)描述的不同的是,在執(zhí)行g(shù)etHandlerInternal方法是,進(jìn)入了AbstractHandlerMethodMapping類中,而不是之前說(shuō)的AbstractUrlHandlerMapping類,這就是因?yàn)榫唧w的RequestMapping的不同而跳轉(zhuǎn)到不同的子類執(zhí)行而已。
AbstractHandlerMethodMapping 類
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
// 獲取請(qǐng)求的URL信息
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
// 查找到合適的執(zhí)行方法
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
// 從urlLookUp集合中完全匹配URL信息
if (directPathMatches != null) {
// 對(duì)篩選的全局匹配的URL屬性進(jìn)行匹配操作
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 把所有的URL信息添加到需要對(duì)比的集合中,進(jìn)行匹配操作
// 注意,這里面是個(gè)很耗時(shí)的操作
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
// 對(duì)匹配到的URL集合進(jìn)行排序,意味著相似的URL會(huì)被排到一起
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
// 符合 CORS pre-flight的請(qǐng)求,就返回一個(gè)EmptyHandler對(duì)象,
// 同時(shí)會(huì)拋出UnsupportedOperationException異常
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
// 存在兩個(gè)同等層級(jí)的URL匹配信息,然后spring就懵逼了,不知道選擇哪個(gè)了
// 拋出IllegalStateException異常
// 這點(diǎn)可以寫一個(gè)demo確實(shí)驗(yàn)證下
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
// 其實(shí)這個(gè)時(shí)候的mapping是RequestMappingInfo對(duì)象(一般情況)
// 匹配出合適的URL信息
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
RequestMappingInfo 類
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
// 匹配方法名稱,
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
// 匹配參數(shù)
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
// 匹配頭部信息
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
// 匹配處理請(qǐng)求的類型,也就是Content-Type
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
// 匹配相應(yīng)請(qǐng)求的類型,從request的Accept參數(shù)中獲取
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
// 有一個(gè)沒(méi)有匹配上就認(rèn)為沒(méi)有合適的映射對(duì)象
return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
// 使用了Apache Ant的匹配規(guī)則去匹配path
if (patterns == null) {
return null;
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
// 這個(gè)沒(méi)有具體獲取
if (custom == null) {
return null;
}
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
關(guān)于上述的Apache Ant 在spring mvc的具體匹配是AntPathMatcher 類的 doMatch 方法
URL匹配總結(jié)
URL匹配流程圖