zxpnet网站 zxpnet网站
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

zxpnet

一个爱学习的java开发攻城狮
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 大后端课程视频归档
  • 南航面试题
  • 并发编程

  • 性能调优

  • java8语法

  • lombok

  • 日志

  • 工具类

  • spring

  • mybatis

  • springboot

  • redis

  • zookeeper

  • springcloud

  • dubbo

  • netty

  • springsecurity

  • mq消息中间件

  • shiro

  • beetle

  • 模板引擎

  • jpa

  • 数据结构与算法

  • 数据库知识与设计

  • gradle

  • maven

  • bus

  • 定时任务

  • docker

  • centos

  • 加解密

  • biz业务

    • 公共业务

      • 单例模式
      • Untitled
      • 接口与抽象类的应用(包括各自设计模式)
      • SpringBoot使用ApplicationEvent&Listener完成业务解耦
      • 业务异常处理
      • ThreadLocal用法
      • 自定义RequestMappingHandlerMapping实现程序版本控制
        • 需求
        • 原理
          • 第一步:自定义RequestMappingHandlerMapping
    • 商城系统

    • 许愿池

    • 微信公众号

    • 企业微信

    • 分销返利系统

  • pigx项目

  • 开源项目

  • 品达通用权限项目-黑马

  • 货币交易项目coin-尚学堂

  • php

  • backend
  • biz业务
  • 公共业务
shollin
2021-06-24
目录

自定义RequestMappingHandlerMapping实现程序版本控制

  • 需求
  • 原理
    • 第一步:自定义RequestMappingHandlerMapping

# 需求

# 原理

RequestMappingHandlerMapping 解析过程

1、在DispatcherServlet中,根据请求对象调用getHander方法获取HandlerExecutionChain对象

2、在getHander方法中也是遍历上面默认加载的三个HandlerMapping,当然第一个就是RequestMappingHandlerMapping对象,调用其getHandler方法,根据请求path,找到一个最为匹配的HandlerMethod来处理请求

3、根据请求路径获取HandlerInterceptor,然后和上面获得的HandlerMethod一起构成HandlerExecutionChain返回给DispatcherServlet

20170709195625055

# 第一步:自定义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

第二步:添加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

第三步:写入配置

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
public class VersionMappingAutoConfiguration {
   @Bean
   public WebMvcRegistrations bladeWebMvcRegistrations() {
      return new BladeWebMvcRegistrations();
   }
}
1
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

第五步:相关业务逻辑处理 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

参考项目: BladeTool

参考文章:

基于SpringBoot的项目API版本控制_哈-CSDN博客_springboot接口版本控制 (opens new window)

SpringMvc 将@RequestMapping注册到HandlerMapping_weixin_30292745的博客-CSDN博客 (opens new window)

ThreadLocal用法
支付业务设计

← ThreadLocal用法 支付业务设计→

最近更新
01
国际象棋
09-15
02
成语
09-15
03
自然拼读
09-15
更多文章>
Theme by Vdoing | Copyright © 2019-2023 zxpnet | 粤ICP备14079330号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式