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还是非常方便的,它的容错性非常高,写法灵活且不容易出错,完全面向模块化思考,值得推荐。
- URI构建的任意部分(包括查询参数、scheme等等)都是可以用{}这种形式的模版参数的
- 被替换的模版中还支持这么来写:/myurl/{name:[a-z]}/show,这样用expand也能正常赋值
它还有个子类:ServletUriComponentsBuilder,是对Servlet容器的适配,也非常值得一提