你知道@RequestMapping的name属性有什么用吗?带你了解URI Builder模式(UriComponents/UriComponentsBuilder)【享学Spring MVC】(中)

简介: 你知道@RequestMapping的name属性有什么用吗?带你了解URI Builder模式(UriComponents/UriComponentsBuilder)【享学Spring MVC】(中)

URI Builder


Spring MVC作为一个web层框架,避免不了处理URI、URL等和HTTP协议相关的元素,因此它提供了非常好用、功能强大的URI Builder模式来完成,这就是本文重点需要讲述的脚手架~

Spring MVC从3.1开始提供了一种机制,可以通过UriComponentsBuilder和UriComponents面向对象的构造和编码URI。


UriComponents


它表示一个不可变的URI组件集合,将组件类型映射到字符串值。


URI:统一资源标识符。 URL:统一资源定位符。

还是傻傻分不清楚?这里我推荐一篇通俗易懂的 文章 供你参考


它包含用于所有组件的方便getter,与java.net.URI类似,但具有更强大的编码选项和对URI模板变量的支持。


// @since 3.1  自己是个抽象类。一般构建它我们使用UriComponentsBuilder构建器
public abstract class UriComponents implements Serializable {
  // 捕获URI模板变量名
  private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
  @Nullable
  private final String scheme;
  @Nullable
  private final String fragment;
  // 唯一构造,是protected 的
  protected UriComponents(@Nullable String scheme, @Nullable String fragment) {
    this.scheme = scheme;
    this.fragment = fragment;
  }
  ... // 省略它俩的get方法(无set方法)
  @Nullable
  public abstract String getSchemeSpecificPart();
  @Nullable
  public abstract String getUserInfo();
  @Nullable
  public abstract String getHost();
  // 如果没有设置port,就返回-1
  public abstract int getPort();
  @Nullable
  public abstract String getPath();
  public abstract List<String> getPathSegments();
  @Nullable
  public abstract String getQuery();
  public abstract MultiValueMap<String, String> getQueryParams();
  // 此方法是public且是final的哦~
  // 注意它的返回值还是UriComponents
  public final UriComponents encode() {
    return encode(StandardCharsets.UTF_8);
  }
  public abstract UriComponents encode(Charset charset);
  // 这是它最为强大的功能:对模版变量的支持
  // 用给定Map映射中的值替换**所有**URI模板变量
  public final UriComponents expand(Map<String, ?> uriVariables) {
    return expandInternal(new MapTemplateVariables(uriVariables));
  }
  // 给定的是变量数组,那就按照顺序替换
  public final UriComponents expand(Object... uriVariableValues) {...}
  public final UriComponents expand(UriTemplateVariables uriVariables) { ... }
  // 真正的expand方法,其实还是子类来实现的
  abstract UriComponents expandInternal(UriTemplateVariables uriVariables);
  // 规范化路径移除**序列**,如“path/…”。
  // 请注意,规范化应用于完整路径,而不是单个路径段。
  public abstract UriComponents normalize();
  // 连接所有URI组件以返回完全格式的URI字符串。
  public abstract String toUriString();
  public abstract URI toUri();
  @Override
  public final String toString() {
    return toUriString();
  }
  // 拷贝
  protected abstract void copyToUriComponentsBuilder(UriComponentsBuilder builder);
  ... // 提供静态工具方法expandUriComponent和sanitizeSource
}


它包含有和Http相关的各个部分:如schema、port、path、query等等。此抽象类有两个实现类:OpaqueUriComponents和HierarchicalUriComponents


Hierarchical:分层的 Opaque:不透明的


由于在实际使用中会使用构建器来创建实例,所以都是面向抽象类编程,并不需要关心具体实现,因此实现类部分此处省略~


UriComponentsBuilder

从命名中就可以看出,它使用了Builder模式,用于构建UriComponents。实际应用中我们所有的UriComponents都应是通过此构建器构建出来的~


// @since 3.1
public class UriComponentsBuilder implements UriBuilder, Cloneable {
  ... // 省略所有正则(包括提取查询参数、scheme、port等等等等)
  ... // 它所有的构造函数都是protected的
  // ******************鞋面介绍它的实例化静态方法(7种)******************
  // 创建一个空的bulder,里面schema,port等等啥都木有
  public static UriComponentsBuilder newInstance() {
    return new UriComponentsBuilder();
  }
  // 直接从path路径里面,分析出一个builder。较为常用
  public static UriComponentsBuilder fromPath(String path) {...}
  public static UriComponentsBuilder fromUri(URI uri) {...}
  // 比如这种:/hotels/42?filter={value}
  public static UriComponentsBuilder fromUriString(String uri) {}
  // 形如这种:https://example.com/hotels/42?filter={value}
  // fromUri和fromHttpUrl的使用方式差不多~~~~
  public static UriComponentsBuilder fromHttpUrl(String httpUrl) {}
  // HttpRequest是HttpMessage的子接口。它的原理是:fromUri(request.getURI())(调用上面方法fromUri)
  // 然后再调用本类的adaptFromForwardedHeaders(request.getHeaders())
  // 解释:从头Forwarded、X-Forwarded-Proto等拿到https、port等设置值~~
  // 详情请参见http标准的Forwarded头~
  // @since 4.1.5
  public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {}
  // origin 里面放的是跨域访问的域名地址。比如 www.a.com 访问 www.b.com会形成跨域
  // 这个时候访问 www.b.com 的时候,请求头里会携带 origin:www.a.com(b服务需要通过这个来判断是否允许a服务跨域访问)
  // 方法可以获取到协议,域名和端口。个人觉得此方法没毛卵用~~~
  // 和fromUriString()方法差不多,不过比它精简(因为这里只需要关注scheme、host和port)
  public static UriComponentsBuilder fromOriginHeader(String origin) {}
  // *******************下面都是实例方法*******************
  // @since 5.0.8
  public final UriComponentsBuilder encode() {
    return encode(StandardCharsets.UTF_8);
  }
  public UriComponentsBuilder encode(Charset charset) {}
  // 调用此方法生成一个UriComponents
  public UriComponents build() {
    return build(false);
  }
  public UriComponents build(boolean encoded) {
    // encoded=true,取值就是FULLY_ENCODED 全部编码
    // 否则只编码模版或者不编码
    return buildInternal(encoded ? EncodingHint.FULLY_ENCODED :
        (this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE)
        );
  }
  // buildInternal内部就会自己new子类:OpaqueUriComponents或者HierarchicalUriComponents
  // 以及执行UriComponents.expand方法了(若指定了参数的话),使用者不用关心了
  // 显然这就是个多功能方法了:设置好参数。build后立马Expand
  public UriComponents buildAndExpand(Map<String, ?> uriVariables) {
    return build().expand(uriVariables);
  }
  public UriComponents buildAndExpand(Object... uriVariableValues) {}
  //build成为一个URI。注意这里编码方式是:EncodingHint.ENCODE_TEMPLATE
  @Override
  public URI build(Object... uriVariables) {
    return buildInternal(EncodingHint.ENCODE_TEMPLATE).expand(uriVariables).toUri();
  }
  @Override
  public URI build(Map<String, ?> uriVariables) {
    return buildInternal(EncodingHint.ENCODE_TEMPLATE).expand(uriVariables).toUri();
  }
  // @since 4.1
  public String toUriString() { ... }
  // ====重构/重新设置Builder====
  public UriComponentsBuilder uri(URI uri) {}
  public UriComponentsBuilder uriComponents(UriComponents uriComponents) {}
  @Override
  public UriComponentsBuilder scheme(@Nullable String scheme) {
    this.scheme = scheme;
    return this;
  }
  @Override
  public UriComponentsBuilder userInfo(@Nullable String userInfo) {
    this.userInfo = userInfo;
    resetSchemeSpecificPart();
    return this;
  }
  public UriComponentsBuilder host(@Nullable String host){ ... }
  ... // 省略其它部分
  // 给URL后面拼接查询参数(键值对)
  @Override
  public UriComponentsBuilder query(@Nullable String query) {}
  // 遇上相同的key就替代,而不是直接在后面添加了(上面query是添加)
  @Override
  public UriComponentsBuilder replaceQuery(@Nullable String query) {}
  @Override
  public UriComponentsBuilder queryParam(String name, Object... values) {}
  ... replaceQueryParam
  // 可以先单独设置参数,但不expend哦~
  public UriComponentsBuilder uriVariables(Map<String, Object> uriVariables) {}
  @Override
  public Object clone() {
    return cloneBuilder();
  }
  // @since 4.2.7
  public UriComponentsBuilder cloneBuilder() {
    return new UriComponentsBuilder(this);
  }
  ...
}


API都不难理解,此处我给出一些使用案例供以参考:


public static void main(String[] args) {
    String url;
    UriComponents uriComponents = UriComponentsBuilder.newInstance()
            //.encode(StandardCharsets.UTF_8)
            .scheme("https").host("www.baidu.com").path("/test").path("/{template}") //此处{}就成 不要写成${}
            //.uriVariables(传一个Map).build();
            .build().expand("myhome"); // 此效果同上一句,但推荐这么使用,方便一些
    url = uriComponents.toUriString();
    System.out.println(url); // https://www.baidu.com/test/myhome
    // 从URL字符串中构造(注意:toUriString方法内部是调用了build和expend方法的~)
    System.out.println(UriComponentsBuilder.fromHttpUrl(url).toUriString()); // https://www.baidu.com/test/myhome
    System.out.println(UriComponentsBuilder.fromUriString(url).toUriString()); // https://www.baidu.com/test/myhome
    // 给URL中放添加参数 query和replaceQuery
    uriComponents = UriComponentsBuilder.fromHttpUrl(url).query("name=中国&age=18").query("&name=二次拼接").build();
    url = uriComponents.toUriString();
    // 效果描述:&test前面这个&不写也是木有问题的。并且两个name都出现了哦~~~
    System.out.println(uriComponents.toUriString()); // https://www.baidu.com/test/myhome?name=中国&name=二次拼接&age=18
    uriComponents = UriComponentsBuilder.fromHttpUrl(url).query("name=中国&age=18").replaceQuery("name=二次拼接").build();
    url = uriComponents.toUriString();
    // 这种够狠:后面的直接覆盖前面“所有的”查询串
    System.out.println(uriComponents.toUriString()); // https://www.baidu.com/test/myhome?name=二次拼接
    //queryParam/queryParams/replaceQueryParam/replaceQueryParams
    // queryParam:一次性指定一个key,queryParams一次性可以搞多个key
    url = "https://www.baidu.com/test/myhome"; // 重置一下
    uriComponents = UriComponentsBuilder.fromHttpUrl(url).queryParam("name","中国","美国").queryParam("age",18)
            .queryParam("name","英国").build();
    url = uriComponents.toUriString();
    // 发现是不会有repalace的效果的~~~~~~~~~~~~~
    System.out.println(uriComponents.toUriString()); // https://www.baidu.com/test/myhome?name=中国&name=美国&name=英国&age=18
    // 关于repalceParam相关方法,交给各位自己去试验吧~~~
    // 不需要domain,构建局部路径,它也是把好手
    uriComponents = UriComponentsBuilder.fromPath("").path("/test").build();
    // .fromPath("/").path("/test") --> /test
    // .fromPath("").path("/test") --> /test
    // .fromPath("").path("//test") --> /test
    // .fromPath("").path("test") --> /test
    System.out.println(uriComponents.toUriString()); // /test?name=fsx
}


使用这种方式来构建URL还是非常方便的,它的容错性非常高,写法灵活且不容易出错,完全面向模块化思考,值得推荐。


  1. URI构建的任意部分(包括查询参数、scheme等等)都是可以用{}这种形式的模版参数的
  2. 被替换的模版中还支持这么来写:/myurl/{name:[a-z]}/show,这样用expand也能正常赋值


它还有个子类:ServletUriComponentsBuilder,是对Servlet容器的适配,也非常值得一提

相关文章
|
21天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
11天前
|
设计模式 Java Spring
spring源码设计模式分析(五)-策略模式
spring源码设计模式分析(五)-策略模式
|
11天前
|
消息中间件 设计模式 缓存
spring源码设计模式分析(四)-观察者模式
spring源码设计模式分析(四)-观察者模式
|
11天前
|
设计模式 Java Spring
spring源码设计模式分析(六)-模板方法模式
spring源码设计模式分析(六)-模板方法模式
|
11天前
|
设计模式 Java Spring
spring源码设计模式分析(七)-委派模式
spring源码设计模式分析(七)-委派模式
|
11天前
|
设计模式 Java 数据库
spring源码设计模式分析(八)-访问者模式
spring源码设计模式分析(八)-访问者模式
|
11天前
|
设计模式 搜索推荐 Java
spring源码设计模式分析(三)
spring源码设计模式分析(三)
|
11天前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)
|
11天前
|
设计模式 存储 Java
spring源码设计模式分析(一)
spring源码设计模式分析(一)
|
9天前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
下一篇
无影云桌面