191222-SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition

在 spring mvc 中,我们知道用户发起的请求可以通过 url 匹配到我们通过@RequestMapping定义的服务端点上;不知道有几个问题大家是否有过思考

一个项目中,能否存在完全相同的 url?

有了解 http 协议的同学可能很快就能给出答案,当然可以,url 相同,请求方法不同即可;那么能否出现 url 相同且请求方法 l 也相同的呢?

本文将介绍一下如何使用RequestCondition结合RequestMappingHandlerMapping,来实现 url 匹配规则的扩展,从而支持上面提出的 case

I. 环境相关

本文介绍的内容和实际 case 将基于spring-boot-2.2.1.RELEASE版本,如果在测试时,发现某些地方没法兼容时,请确定一下版本

1. 项目搭建

首先我们需要搭建一个 web 工程,以方便后续的 servelt 注册的实例演示,可以通过 spring boot 官网创建工程,也可以建立一个 maven 工程,在 pom.xml 中如下配置

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository -->
</parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version>
</properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies><build><pluginManagement><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></pluginManagement>
</build>
<repositories><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/libs-snapshot-local</url><snapshots><enabled>true</enabled></snapshots></repository><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/libs-milestone-local</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-releases</id><name>Spring Releases</name><url>https://repo.spring.io/libs-release-local</url><snapshots><enabled>false</enabled></snapshots></repository>
</repositories>

2. RequestCondition 介绍

在 spring mvc 中,通过DispatchServlet接收客户端发起的一个请求之后,会通过 HanderMapping 来获取对应的请求处理器;而 HanderMapping 如何找到可以处理这个请求的处理器呢,这就需要 RequestCondition 来决定了

接口定义如下,主要有三个方法,

public interface RequestCondition<T> {// 一个http接口上有多个条件规则时,用于合并T combine(T other);// 这个是重点,用于判断当前匹配条件和请求是否匹配;如果不匹配返回null// 如果匹配,生成一个新的请求匹配条件,该新的请求匹配条件是当前请求匹配条件针对指定请求request的剪裁// 举个例子来讲,如果当前请求匹配条件是一个路径匹配条件,包含多个路径匹配模板,// 并且其中有些模板和指定请求request匹配,那么返回的新建的请求匹配条件将仅仅// 包含和指定请求request匹配的那些路径模板。@NullableT getMatchingCondition(HttpServletRequest request);// 针对指定的请求对象request发现有多个满足条件的,用来排序指定优先级,使用最优的进行响应int compareTo(T other, HttpServletRequest request);}

简单说下三个接口的作用

  • combine: 某个接口有多个规则时,进行合并 - 比如类上指定了@RequestMapping的 url 为 root - 而方法上指定的@RequestMapping的 url 为 method - 那么在获取这个接口的 url 匹配规则时,类上扫描一次,方法上扫描一次,这个时候就需要把这两个合并成一个,表示这个接口匹配root/method
  • getMatchingCondition: - 判断是否成功,失败返回 null;否则,则返回匹配成功的条件
  • compareTo: - 多个都满足条件时,用来指定具体选择哪一个

在 Spring MVC 中,默认提供了下面几种

类说明PatternsRequestCondition路径匹配,即 urlRequestMethodsRequestCondition请求方法,注意是指 http 请求方法ParamsRequestCondition请求参数条件匹配HeadersRequestCondition请求头匹配ConsumesRequestCondition可消费 MIME 匹配条件ProducesRequestCondition可生成 MIME 匹配条件

II. 实例说明

单纯的看说明,可能不太好理解它的使用方式,接下来我们通过一个实际的 case,来演示使用姿势

1. 场景说明

我们有个服务同时针对 app/wap/pc 三个平台,我们希望可以指定某些接口只为特定的平台提供服务

2. 实现

首先我们定义通过请求头中的x-platform来区分平台;即用户发起的请求中,需要携带这个请求头

定义平台枚举类

public enum PlatformEnum {PC("pc", 1), APP("app", 1), WAP("wap", 1), ALL("all", 0);@Getterprivate String name;@Getterprivate int order;PlatformEnum(String name, int order) {this.name = name;this.order = order;}public static PlatformEnum nameOf(String name) {if (name == null) {return ALL;}name = name.toLowerCase().trim();for (PlatformEnum sub : values()) {if (sub.name.equals(name)) {return sub;}}return ALL;}
}

然后定义一个注解@Platform,如果某个接口需要指定平台,则加上这个注解即可

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Platform {PlatformEnum value() default PlatformEnum.ALL;
}

定义匹配规则PlatformRequestCondition继承自RequestCondition,实现三个接口,从请求头中获取平台,根据平台是否相同过来判定是否可以支持请求

public class PlatformRequestCondition implements RequestCondition<PlatformRequestCondition> {@Getter@Setterprivate PlatformEnum platform;public PlatformRequestCondition(PlatformEnum platform) {this.platform = platform;}@Overridepublic PlatformRequestCondition combine(PlatformRequestCondition other) {return new PlatformRequestCondition(other.platform);}@Overridepublic PlatformRequestCondition getMatchingCondition(HttpServletRequest request) {PlatformEnum platform = this.getPlatform(request);if (this.platform.equals(platform)) {return this;}return null;}/*** 优先级** @param other* @param request* @return*/@Overridepublic int compareTo(PlatformRequestCondition other, HttpServletRequest request) {int thisOrder = this.platform.getOrder();int otherOrder = other.platform.getOrder();return otherOrder - thisOrder;}private PlatformEnum getPlatform(HttpServletRequest request) {String platform = request.getHeader("x-platform");return PlatformEnum.nameOf(platform);}
}

匹配规则指定完毕之后,需要注册到 HandlerMapping 上才能生效,这里我们自定义一个PlatformHandlerMapping

public class PlatformHandlerMapping extends RequestMappingHandlerMapping {@Overrideprotected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {return buildFrom(AnnotationUtils.findAnnotation(handlerType, Platform.class));}@Overrideprotected RequestCondition<?> getCustomMethodCondition(Method method) {return buildFrom(AnnotationUtils.findAnnotation(method, Platform.class));}private PlatformRequestCondition buildFrom(Platform platform) {return platform == null ? null : new PlatformRequestCondition(platform.value());}
}

最后则是需要将我们的 HandlerMapping 注册到 Spring MVC 容器,在这里我们借助WebMvcConfigurationSupport来手动注册(注意一下,不同的版本,下面的方法可能会不太一样哦)

@Configuration
public class Config extends WebMvcConfigurationSupport {@Overridepublic RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {PlatformHandlerMapping handlerMapping = new PlatformHandlerMapping();handlerMapping.setOrder(0);handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));return handlerMapping;}
}

3. 测试

接下来进入实测环节,定义几个接口,分别指定不同的平台

@RestController
@RequestMapping(path = "method")
public class DemoMethodRest {@Platform@GetMapping(path = "index")public String allIndex() {return "default index";}@Platform(PlatformEnum.PC)@GetMapping(path = "index")public String pcIndex() {return "pc index";}@Platform(PlatformEnum.APP)@GetMapping(path = "index")public String appIndex() {return "app index";}@Platform(PlatformEnum.WAP)@GetMapping(path = "index")public String wapIndex() {return "wap index";}
}

如果我们的规则可以正常生效,那么在请求头中设置不同的x-platform,返回的结果应该会不一样,实测结果如下

注意最后两个,一个是指定了一个不匹配我们的平台的请求头,一个是没有对应的请求头,都是走了默认的匹配规则;这是因为我们在PlatformRequestCondition中做了兼容,无法匹配平台时,分配到默认的Platform.ALL

然后还有一个小疑问,如果有一个服务不区分平台,那么不加上@Platform注解是否可以呢?

@GetMapping(path = "hello")
public String hello() {return "hello";
}

当然是可以的实测结果如下:

在不加上@Platform注解时,有一点需要注意,这个时候就不能出现多个 url 和请求方法相同的,在启动的时候会直接抛出异常哦

III. 其他

web 系列博文

  • 191206-SpringBoot 系列教程 web 篇 Listener 四种注册姿势
  • 191122-SpringBoot 系列教程 web 篇 Servlet 注册的四种姿势
  • 191120-SpringBoot 系列教程 Web 篇之开启 GZIP 数据压缩
  • 191018-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南扩展篇
  • 191016-SpringBoot 系列教程 web 篇之过滤器 Filter 使用指南
  • 191012-SpringBoot 系列教程 web 篇之自定义异常处理 HandlerExceptionResolver
  • 191010-SpringBoot 系列教程 web 篇之全局异常处理
  • 190930-SpringBoot 系列教程 web 篇之 404、500 异常页面配置
  • 190929-SpringBoot 系列教程 web 篇之重定向
  • 190913-SpringBoot 系列教程 web 篇之返回文本、网页、图片的操作姿势
  • 190905-SpringBoot 系列教程 web 篇之中文乱码问题解决
  • 190831-SpringBoot 系列教程 web 篇之如何自定义参数解析器
  • 190828-SpringBoot 系列教程 web 篇之 Post 请求参数解析姿势汇总
  • 190824-SpringBoot 系列教程 web 篇之 Get 请求参数解析姿势汇总
  • 190822-SpringBoot 系列教程 web 篇之 Beetl 环境搭建
  • 190820-SpringBoot 系列教程 web 篇之 Thymeleaf 环境搭建
  • 190816-SpringBoot 系列教程 web 篇之 Freemaker 环境搭建
  • 190421-SpringBoot 高级篇 WEB 之 websocket 的使用说明
  • 190327-Spring-RestTemplate 之 urlencode 参数解析异常全程分析
  • 190317-Spring MVC 之基于 java config 无 xml 配置的 web 应用构建
  • 190316-Spring MVC 之基于 xml 配置的 web 应用构建
  • 190213-SpringBoot 文件上传异常之提示 The temporary upload location xxx is not valid

项目源码

  • 工程:https://github.com/liuyueyi/spring-boot-demo[1]
  • 项目: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/208-web-mapping[2]

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

  • 一灰灰 Blog 个人博客 https://blog.hhui.top[3]
  • 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top[4]

参考资料

[1]

https://github.com/liuyueyi/spring-boot-demo: https://github.com/liuyueyi/spring-boot-demo

[2]

https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/208-web-mapping: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/208-web-mapping

[3]

https://blog.hhui.top: https://blog.hhui.top

[4]

http://spring.hhui.top: http://spring.hhui.top

spring boot 跨域请求_SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition...相关推荐

  1. 本地Vue前端请求本地Spring Boot跨域问题(CROS错误)

    一.Vue前端 请求的url为 : GET /WebServer/home/get-user-info?id=1 二.Spring Boot后端 是一个Get请求的RestFul接口地址,且后端应用的 ...

  2. 实战系列-Spring Boot跨域解决方案

    导语   在实际工作开发中经常会遇到跨域请求,这个时候就需要前后端来共同协调来解决问题,那么在Spring Boot中怎么解决跨域请求问题呢?下面就来看看 什么是跨域   为了保证浏览器的安全,不同源 ...

  3. spring boot跨域问题

    跨域是指不同域名之间相互访问.跨域,指的是浏览器不能执行其他网站的脚本.它是浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制.也就是如果在A网站中,我们希望使用Ajax来获得B网站 ...

  4. webmvcconfigurer配置跨域_为什么加了 Spring Security 会导致 Spring Boot 跨域失效呢?...

    点击上方 IT牧场 ,选择 置顶或者星标 技术干货每日送达 作者:欧阳我去 链接:https://segmentfault.com/a/1190000019485883 作为一个后端开发,我们经常遇到 ...

  5. Spring Boot跨域解决方案

    一.什么是跨域 为保证浏览器的安全,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,这称之为同源策略,如果一个请求地址里的协议.域名.端口号都相同,就属于同源.依据浏览器同源策略,非同源脚 ...

  6. return error怎么定义_SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势

    200105-SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势 虽然 http 的提供了一整套完整.定义明确的状态码,但实际的业务支持中,后端并不总会遵守这套规则 ...

  7. boot返回码规范 spring_SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势

    200105-SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势 虽然 http 的提供了一整套完整.定义明确的状态码,但实际的业务支持中,后端并不总会遵守这套规则 ...

  8. kuayu react_React+Spring实现跨域问题的完美解决方法

    最近小编在学习react,在学习过程中遇到React+Spring实现跨域问题,下面小编记录了整个问题过程,给大家做个参考. react 跨域访问后台,默认是有跨域问题,并且火弧和谷歌浏览器,对跨域问 ...

  9. react+spring 记录跨域问题的解决方法

    react+spring 记录跨域问题的解决方法 参考文章: (1)react+spring 记录跨域问题的解决方法 (2)https://www.cnblogs.com/cq-jiang/p/954 ...

最新文章

  1. 14种轻量级网络综述 — 主干网络篇
  2. SVN使用_获取某版本后改动的文件列表
  3. c语言 获得回车按键控制输入法,android调用输入软键盘回车键跟删除键
  4. android控件属性
  5. 记一次Java AES 加解密 对应C# AES加解密 的一波三折
  6. BZOJ 1878 HH的项链 | 主席树
  7. 安装和使用Ant Design Vue 图标库
  8. mysql字段作用_mysql用户表host字段作用
  9. 20160417_无为_常州
  10. HDU 6241 Color a Tree
  11. 【MySQL】Unknown column 'column_name' in 'field list'
  12. lazada发货_Lazada发货要求及注意事项
  13. mysql有关时间教程_MySQL教程21-日期和时间类型
  14. Java动态数组的实现
  15. win7计算机相机,笔记本win7怎么拍照_win7电脑照相机如何打开
  16. 同方有云,加速领跑信创新时代
  17. 微信公众号第三方平台开发PYTHON教程 PART 5
  18. mysql 2203_mysql 错误2203 1061 及安装最后出现1067错误的解决办法
  19. GAN的介绍和简单代码的实现
  20. Ubuntu系统腾讯会议使用ffmpeg屏幕录制+声音录制

热门文章

  1. Python数据可视化——matplotlib使用
  2. SAP 电商云 Spartacus UI DeliveryComponent 的依赖设计
  3. 使用工具分析 SAP UI5 应用前端执行的性能问题
  4. RxJs SwitchMapTo 操作符之移花接木
  5. SAP Spartacus B2B Popover Directive 和 Popover Component 的生命周期管理
  6. SAP Spartacus cost center Add按钮的href属性生成逻辑分析
  7. 一个风格诡异的ABAP学习网站
  8. 关于用Restful API下载网易云笔记时遇到的图片外链不能打开的问题
  9. vboxdrv.sh failed modprobe vboxdrv failed. Please use 'dmesg' to find out why
  10. nodejs库express是如何接收inbound json请求的