自定义RequestMappingHandlerMapping实现程序版本控制
# 需求
# 原理
RequestMappingHandlerMapping 解析过程
1、在DispatcherServlet
中,根据请求对象调用getHander方法获取HandlerExecutionChain对象
2、在getHander方法中也是遍历上面默认加载的三个HandlerMapping,当然第一个就是RequestMappingHandlerMapping对象,调用其getHandler方法,根据请求path,找到一个最为匹配的HandlerMethod来处理请求
3、根据请求路径获取HandlerInterceptor,然后和上面获得的HandlerMethod一起构成HandlerExecutionChain返回给DispatcherServlet
# 第一步:自定义RequestMappingHandlerMapping
/**
* url版本号处理 和 header 版本处理
*
* <p>
* url: /v1/user/{id}
* header: Accept application/vnd.blade.VERSION+json
* </p>
*
* 注意:c 代表客户端版本
*
* @author L.cm
*/
public class BladeRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Nullable
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappinginfo = super.getMappingForMethod(method, handlerType);
if (mappinginfo != null) {
RequestMappingInfo apiVersionMappingInfo = getApiVersionMappingInfo(method, handlerType);
return apiVersionMappingInfo == null ? mappinginfo : apiVersionMappingInfo.combine(mappinginfo);
}
return null;
}
@Nullable
private RequestMappingInfo getApiVersionMappingInfo(Method method, Class<?> handlerType) {
// url 上的版本,优先获取方法上的版本
UrlVersion urlVersion = AnnotatedElementUtils.findMergedAnnotation(method, UrlVersion.class);
// 再次尝试类上的版本
if (urlVersion == null || StringUtil.isBlank(urlVersion.value())) {
urlVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, UrlVersion.class);
}
// Media Types 版本信息
ApiVersion apiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
// 再次尝试类上的版本
if (apiVersion == null || StringUtil.isBlank(apiVersion.value())) {
apiVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, ApiVersion.class);
}
boolean nonUrlVersion = urlVersion == null || StringUtil.isBlank(urlVersion.value());
boolean nonApiVersion = apiVersion == null || StringUtil.isBlank(apiVersion.value());
// 先判断同时不纯在
if (nonUrlVersion && nonApiVersion) {
return null;
}
// 如果 header 版本不存在
RequestMappingInfo.Builder mappingInfoBuilder = null;
if (nonApiVersion) {
mappingInfoBuilder = RequestMappingInfo.paths(urlVersion.value());
} else {
mappingInfoBuilder = RequestMappingInfo.paths(StringPool.EMPTY);
}
// 如果url版本不存在
if (nonUrlVersion) {
String vsersionMediaTypes = new BladeMediaType(apiVersion.value()).toString();
mappingInfoBuilder.produces(vsersionMediaTypes);
}
return mappingInfoBuilder.build();
}
@Override
protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod> handlerMethods) {
// 打印路由信息 spring boot 2.1 去掉了这个 日志的打印
if (logger.isInfoEnabled()) {
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
RequestMappingInfo mapping = entry.getKey();
HandlerMethod handlerMethod = entry.getValue();
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
}
super.handlerMethodsInitialized(handlerMethods);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
第二步:添加WebMvcRegistrations
/**
* url版本号处理
*
* @author L.cm
*/
public class BladeWebMvcRegistrations implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new BladeRequestMappingHandlerMapping();
}
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return null;
}
@Override
public ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
第三步:写入配置
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
public class VersionMappingAutoConfiguration {
@Bean
public WebMvcRegistrations bladeWebMvcRegistrations() {
return new BladeWebMvcRegistrations();
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
第四步:写注解相关类
/**
* header 版本 处理
*
* @author L.cm
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ApiVersion {
/**
* header 路径中的版本
*
* @return 版本号
*/
String value() default "";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
第五步:相关业务逻辑处理 SpringMvcContract
/**
* 支持 blade-boot 的 版本 处理
*
* @see org.springblade.core.cloud.annotation.UrlVersion
* @see org.springblade.core.cloud.annotation.ApiVersion
* @author L.cm
*/
public class BladeSpringMvcContract extends SpringMvcContract {
public BladeSpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
super(annotatedParameterProcessors, conversionService);
}
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
if (RequestMapping.class.isInstance(methodAnnotation) || methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
Class<?> targetType = method.getDeclaringClass();
// url 上的版本,优先获取方法上的版本
UrlVersion urlVersion = AnnotatedElementUtils.findMergedAnnotation(method, UrlVersion.class);
// 再次尝试类上的版本
if (urlVersion == null || StringUtil.isBlank(urlVersion.value())) {
urlVersion = AnnotatedElementUtils.findMergedAnnotation(targetType, UrlVersion.class);
}
if (urlVersion != null && StringUtil.isNotBlank(urlVersion.value())) {
String versionUrl = "/" + urlVersion.value();
data.template().uri(versionUrl);
}
// 注意:在父类之前 添加 url版本,在父类之后,处理 Media Types 版本
super.processAnnotationOnMethod(data, methodAnnotation, method);
// 处理 Media Types 版本信息
ApiVersion apiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
// 再次尝试类上的版本
if (apiVersion == null || StringUtil.isBlank(apiVersion.value())) {
apiVersion = AnnotatedElementUtils.findMergedAnnotation(targetType, ApiVersion.class);
}
if (apiVersion != null && StringUtil.isNotBlank(apiVersion.value())) {
BladeMediaType BladeMediaType = new BladeMediaType(apiVersion.value());
data.template().header(HttpHeaders.ACCEPT, BladeMediaType.toString());
}
}
}
/**
* 参考:https://gist.github.com/rmfish/0ed59a9af6c05157be2a60c9acea2a10
* @param annotations 注解
* @param paramIndex 参数索引
* @return 是否 http 注解
*/
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean httpAnnotation = super.processAnnotationsOnParameter(data, annotations, paramIndex);
// 在 springMvc 中如果是 Get 请求且参数中是对象 没有声明为@RequestBody 则默认为 Param
if (!httpAnnotation && StringPool.GET.equals(data.template().method().toUpperCase())) {
for (Annotation parameterAnnotation : annotations) {
if (!(parameterAnnotation instanceof RequestBody)) {
return false;
}
}
data.queryMapIndex(paramIndex);
return true;
}
return httpAnnotation;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
参考项目: BladeTool
参考文章:
基于SpringBoot的项目API版本控制_哈-CSDN博客_springboot接口版本控制 (opens new window)
SpringMvc 将@RequestMapping注册到HandlerMapping_weixin_30292745的博客-CSDN博客 (opens new window)