javascript
spring boot 跨域请求_SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition...
在 spring mvc 中,我們知道用戶發(fā)起的請求可以通過 url 匹配到我們通過@RequestMapping定義的服務(wù)端點(diǎn)上;不知道有幾個(gè)問題大家是否有過思考
一個(gè)項(xiàng)目中,能否存在完全相同的 url?
有了解 http 協(xié)議的同學(xué)可能很快就能給出答案,當(dāng)然可以,url 相同,請求方法不同即可;那么能否出現(xiàn) url 相同且請求方法 l 也相同的呢?
本文將介紹一下如何使用RequestCondition結(jié)合RequestMappingHandlerMapping,來實(shí)現(xiàn) url 匹配規(guī)則的擴(kuò)展,從而支持上面提出的 case
I. 環(huán)境相關(guān)
本文介紹的內(nèi)容和實(shí)際 case 將基于spring-boot-2.2.1.RELEASE版本,如果在測試時(shí),發(fā)現(xiàn)某些地方?jīng)]法兼容時(shí),請確定一下版本
1. 項(xiàng)目搭建
首先我們需要搭建一個(gè) web 工程,以方便后續(xù)的 servelt 注冊的實(shí)例演示,可以通過 spring boot 官網(wǎng)創(chuàng)建工程,也可以建立一個(gè) 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接收客戶端發(fā)起的一個(gè)請求之后,會(huì)通過 HanderMapping 來獲取對應(yīng)的請求處理器;而 HanderMapping 如何找到可以處理這個(gè)請求的處理器呢,這就需要 RequestCondition 來決定了
接口定義如下,主要有三個(gè)方法,
public interface RequestCondition<T> {// 一個(gè)http接口上有多個(gè)條件規(guī)則時(shí),用于合并T combine(T other);// 這個(gè)是重點(diǎn),用于判斷當(dāng)前匹配條件和請求是否匹配;如果不匹配返回null// 如果匹配,生成一個(gè)新的請求匹配條件,該新的請求匹配條件是當(dāng)前請求匹配條件針對指定請求request的剪裁// 舉個(gè)例子來講,如果當(dāng)前請求匹配條件是一個(gè)路徑匹配條件,包含多個(gè)路徑匹配模板,// 并且其中有些模板和指定請求request匹配,那么返回的新建的請求匹配條件將僅僅// 包含和指定請求request匹配的那些路徑模板。@NullableT getMatchingCondition(HttpServletRequest request);// 針對指定的請求對象request發(fā)現(xiàn)有多個(gè)滿足條件的,用來排序指定優(yōu)先級,使用最優(yōu)的進(jìn)行響應(yīng)int compareTo(T other, HttpServletRequest request);}簡單說下三個(gè)接口的作用
- combine: 某個(gè)接口有多個(gè)規(guī)則時(shí),進(jìn)行合并 - 比如類上指定了@RequestMapping的 url 為 root - 而方法上指定的@RequestMapping的 url 為 method - 那么在獲取這個(gè)接口的 url 匹配規(guī)則時(shí),類上掃描一次,方法上掃描一次,這個(gè)時(shí)候就需要把這兩個(gè)合并成一個(gè),表示這個(gè)接口匹配root/method
- getMatchingCondition: - 判斷是否成功,失敗返回 null;否則,則返回匹配成功的條件
- compareTo: - 多個(gè)都滿足條件時(shí),用來指定具體選擇哪一個(gè)
在 Spring MVC 中,默認(rèn)提供了下面幾種
類說明PatternsRequestCondition路徑匹配,即 urlRequestMethodsRequestCondition請求方法,注意是指 http 請求方法ParamsRequestCondition請求參數(shù)條件匹配HeadersRequestCondition請求頭匹配ConsumesRequestCondition可消費(fèi) MIME 匹配條件ProducesRequestCondition可生成 MIME 匹配條件
II. 實(shí)例說明
單純的看說明,可能不太好理解它的使用方式,接下來我們通過一個(gè)實(shí)際的 case,來演示使用姿勢
1. 場景說明
我們有個(gè)服務(wù)同時(shí)針對 app/wap/pc 三個(gè)平臺(tái),我們希望可以指定某些接口只為特定的平臺(tái)提供服務(wù)
2. 實(shí)現(xiàn)
首先我們定義通過請求頭中的x-platform來區(qū)分平臺(tái);即用戶發(fā)起的請求中,需要攜帶這個(gè)請求頭
定義平臺(tái)枚舉類
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;} }然后定義一個(gè)注解@Platform,如果某個(gè)接口需要指定平臺(tái),則加上這個(gè)注解即可
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Platform {PlatformEnum value() default PlatformEnum.ALL; }定義匹配規(guī)則PlatformRequestCondition繼承自RequestCondition,實(shí)現(xiàn)三個(gè)接口,從請求頭中獲取平臺(tái),根據(jù)平臺(tái)是否相同過來判定是否可以支持請求
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;}/*** 優(yōu)先級** @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);} }匹配規(guī)則指定完畢之后,需要注冊到 HandlerMapping 上才能生效,這里我們自定義一個(gè)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來手動(dòng)注冊(注意一下,不同的版本,下面的方法可能會(huì)不太一樣哦)
@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. 測試
接下來進(jìn)入實(shí)測環(huán)節(jié),定義幾個(gè)接口,分別指定不同的平臺(tái)
@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";} }如果我們的規(guī)則可以正常生效,那么在請求頭中設(shè)置不同的x-platform,返回的結(jié)果應(yīng)該會(huì)不一樣,實(shí)測結(jié)果如下
注意最后兩個(gè),一個(gè)是指定了一個(gè)不匹配我們的平臺(tái)的請求頭,一個(gè)是沒有對應(yīng)的請求頭,都是走了默認(rèn)的匹配規(guī)則;這是因?yàn)槲覀冊赑latformRequestCondition中做了兼容,無法匹配平臺(tái)時(shí),分配到默認(rèn)的Platform.ALL
然后還有一個(gè)小疑問,如果有一個(gè)服務(wù)不區(qū)分平臺(tái),那么不加上@Platform注解是否可以呢?
@GetMapping(path = "hello") public String hello() {return "hello"; }當(dāng)然是可以的實(shí)測結(jié)果如下:
在不加上@Platform注解時(shí),有一點(diǎn)需要注意,這個(gè)時(shí)候就不能出現(xiàn)多個(gè) url 和請求方法相同的,在啟動(dòng)的時(shí)候會(huì)直接拋出異常哦
III. 其他
web 系列博文
- 191206-SpringBoot 系列教程 web 篇 Listener 四種注冊姿勢
- 191122-SpringBoot 系列教程 web 篇 Servlet 注冊的四種姿勢
- 191120-SpringBoot 系列教程 Web 篇之開啟 GZIP 數(shù)據(jù)壓縮
- 191018-SpringBoot 系列教程 web 篇之過濾器 Filter 使用指南擴(kuò)展篇
- 191016-SpringBoot 系列教程 web 篇之過濾器 Filter 使用指南
- 191012-SpringBoot 系列教程 web 篇之自定義異常處理 HandlerExceptionResolver
- 191010-SpringBoot 系列教程 web 篇之全局異常處理
- 190930-SpringBoot 系列教程 web 篇之 404、500 異常頁面配置
- 190929-SpringBoot 系列教程 web 篇之重定向
- 190913-SpringBoot 系列教程 web 篇之返回文本、網(wǎng)頁、圖片的操作姿勢
- 190905-SpringBoot 系列教程 web 篇之中文亂碼問題解決
- 190831-SpringBoot 系列教程 web 篇之如何自定義參數(shù)解析器
- 190828-SpringBoot 系列教程 web 篇之 Post 請求參數(shù)解析姿勢匯總
- 190824-SpringBoot 系列教程 web 篇之 Get 請求參數(shù)解析姿勢匯總
- 190822-SpringBoot 系列教程 web 篇之 Beetl 環(huán)境搭建
- 190820-SpringBoot 系列教程 web 篇之 Thymeleaf 環(huán)境搭建
- 190816-SpringBoot 系列教程 web 篇之 Freemaker 環(huán)境搭建
- 190421-SpringBoot 高級篇 WEB 之 websocket 的使用說明
- 190327-Spring-RestTemplate 之 urlencode 參數(shù)解析異常全程分析
- 190317-Spring MVC 之基于 java config 無 xml 配置的 web 應(yīng)用構(gòu)建
- 190316-Spring MVC 之基于 xml 配置的 web 應(yīng)用構(gòu)建
- 190213-SpringBoot 文件上傳異常之提示 The temporary upload location xxx is not valid
項(xiàng)目源碼
- 工程:https://github.com/liuyueyi/spring-boot-demo[1]
- 項(xiàng)目: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/208-web-mapping[2]
1. 一灰灰 Blog
盡信書則不如,以上內(nèi)容,純屬一家之言,因個(gè)人能力有限,難免有疏漏和錯(cuò)誤之處,如發(fā)現(xiàn) bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個(gè)人博客,記錄所有學(xué)習(xí)和工作中的博文,歡迎大家前去逛逛
- 一灰灰 Blog 個(gè)人博客 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
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的spring boot 跨域请求_SpringBoot 系列教程 web 篇之自定义请求匹配条件 RequestCondition...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾讯 QQ Linux 版 3.1.1
- 下一篇: python和perl_Python与P