建造者模式,對于後端開發人員來說應該是很熟悉的,我們比較常用的HttpClient架構在建構Client時就用到了建造者模式。
定義
慣例先來看看建造者模式的定義:将一個複雜對象的建構與它的表示分離,使得同樣的建構過程可以建立不同的表示。
UriComponents
可以說建造者模式了解起來是比較的容易的。它就是将複雜類的建構與其本身解耦合,并在其構造類中完成對它不同形式的建立。
在springMVC中,我們就可以看到建造者模式的身影。springMVC在建構UriComponents的内容時,就用到了建造者模式,我們先來看看UriComponents這個類是提供了哪些Components:
public abstract class UriComponents implements Serializable {
private static final String DEFAULT_ENCODING = "UTF-8";
// 用于分割uri的正規表達式,下面會說到
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
private final String scheme;
private final String fragment;
protected UriComponents(String scheme, String fragment) {
this.scheme = scheme;
this.fragment = fragment;
}
// 多個Components對應的getter方法
/**
* 傳回URL的scheme.
*/
public final String getScheme() {
return this.scheme;
}
/**
* 傳回URL的fragment.
*/
public final String getFragment() {
return this.fragment;
}
/**
* 傳回URL的schemeSpecificPar
*/
public abstract String getSchemeSpecificPart();
/**
* 傳回userInfo
*/
public abstract String getUserInfo();
/**
* 傳回URL的host
*/
public abstract String getHost();
/**
* 傳回URL的port
*/
public abstract int getPort();
/**
* 傳回URL的path
*/
public abstract String getPath();
/**
* 傳回URL的path部分的集合
*/
public abstract List<String> getPathSegments();
/**
* 傳回URL的query部分
*/
public abstract String getQuery();
/**
* 傳回URL的query參數map
*/
public abstract MultiValueMap<String, String> getQueryParams();
/**
* 将URL的components用特定的編碼規則編碼并傳回,預設為utf-8
*/
public final UriComponents encode() {
try {
return encode(DEFAULT_ENCODING);
}
catch (UnsupportedEncodingException ex) {
// should not occur
throw new IllegalStateException(ex);
}
}
/**
* 編碼的抽象方法,傳入相應的編碼規則
*/
public abstract UriComponents encode(String encoding) throws UnsupportedEncodingException;
/**
* 将URL中的模闆參數換成對應的值
*/
public final UriComponents expand(Map<String, ?> uriVariables) {
Assert.notNull(uriVariables, "'uriVariables' must not be null");
return expandInternal(new MapTemplateVariables(uriVariables));
}
/**
* 将URL中的模闆參數換成對應的值,輸入為數組
*/
public final UriComponents expand(Object... uriVariableValues) {
Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
return expandInternal(new VarArgsTemplateVariables(uriVariableValues));
}
/**
* 将URL中的模闆參數換成對應的值,輸入為UriTemplateVariables
*/
public final UriComponents expand(UriTemplateVariables uriVariables) {
Assert.notNull(uriVariables, "'uriVariables' must not be null");
return expandInternal(uriVariables);
}
/**
* 将URL中的模闆參數換成對應的值的最終的實作方法
*/
abstract UriComponents expandInternal(UriTemplateVariables uriVariables);
/**
* 處理URL
*/
public abstract UriComponents normalize();
/**
* 傳回URL的string
*/
public abstract String toUriString();
/**
* 傳回URI格式的方法
*/
public abstract URI toUri();
@Override
public final String toString() {
return toUriString();
}
/**
* 将這些Components的值賦給其builder類
*/
protected abstract void copyToUriComponentsBuilder(UriComponentsBuilder builder);
上面的代碼不包括UriComponents類下其餘的靜态輔助方法,單單從此類的包含多種components中,就可以看出UriComponents的複雜程度。這些components大都對應了url的某個部分,能幫助springMVC對請求的url内容進行識别。springMVC就是通過将uri建構成這個類,再對uri進行處理的。
UriComponentsBuilder
那麼springMVC究竟是如何讓請求的uri生成相應的UriComponents類呢?就要看看UriComponentsBuilder這個類了。
首先看看它的兩個構造函數:
/**
* 預設構造方法,其中path的構造類為CompositePathComponentBuilder,它為UriComponentsBuilder的内部靜态類,主要實作對url的path部分進行構造。
*/
protected UriComponentsBuilder() {
this.pathBuilder = new CompositePathComponentBuilder();
}
/**
* 建立一個傳入UriComponentsBuilder類的深拷貝對象
*/
protected UriComponentsBuilder(UriComponentsBuilder other) {
this.scheme = other.scheme;
this.ssp = other.ssp;
this.userInfo = other.userInfo;
this.host = other.host;
this.port = other.port;
this.pathBuilder = other.pathBuilder.cloneBuilder();
this.queryParams.putAll(other.queryParams);
this.fragment = other.fragment;
}
由于url的path部分是比較複雜的,這邊springMVC用了内部類的方式,為path單獨加了兩個builder類,分别是CompositePathComponentBuilder、FullPathComponentBuilder,這裡就不擴充來說了。看完了UriComponentsBuilder的構造方法,我們來看它是如何将給定的uri生成為相應的UriComponents的。這裡就從比較容易了解的fromUriString方法入手吧:
// 靜态方法,從uri的字元串中獲得uri的各種要素
public static UriComponentsBuilder fromUriString(String uri) {
Assert.notNull(uri, "URI must not be null");
// 利用正規表達式,獲得uri的各個組成部分
Matcher matcher = URI_PATTERN.matcher(uri);
if (matcher.matches()) {
UriComponentsBuilder builder = new UriComponentsBuilder();
// 獲得對應要素的字元串
String scheme = matcher.group();
String userInfo = matcher.group();
String host = matcher.group();
String port = matcher.group();
String path = matcher.group();
String query = matcher.group();
String fragment = matcher.group();
// uri是否透明的标志位
boolean opaque = false;
// uri存在scheme且後面不跟:/則為不透明uri
例如mailto:[email protected]
if (StringUtils.hasLength(scheme)) {
String rest = uri.substring(scheme.length());
if (!rest.startsWith(":/")) {
opaque = true;
}
}
builder.scheme(scheme);
// 如果為不透明uri,則具備ssp,需要設定ssp
if (opaque) {
String ssp = uri.substring(scheme.length()).substring();
if (StringUtils.hasLength(fragment)) {
ssp = ssp.substring(, ssp.length() - (fragment.length() + ));
}
builder.schemeSpecificPart(ssp);
}
// 如果為絕對uri(通常意義上的uri),則設定各個component
else {
builder.userInfo(userInfo);
builder.host(host);
if (StringUtils.hasLength(port)) {
builder.port(port);
}
builder.path(path);
builder.query(query);
}
if (StringUtils.hasText(fragment)) {
builder.fragment(fragment);
}
return builder;
}
// 傳入uri格式不對,抛出異常
else {
throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
}
}
從上面的方法中,我們可以看到,UriComponentsBuilder從一個uri的字元串中,通過正則比對的方式,擷取到不同Components的資訊并指派。UriComponentsBuilder除了fromUriString這一種建構方法外,還提供fromUri,fromHttpUrl,fromHttpRequest,fromOriginHeader等好幾種建構的方法,感興趣的小夥伴可以自己去看。
那麼在通過各種建構後,擷取到了對應的Components資訊,最後的一步,也是最重要的一步,build,将會傳回我們需要的UriComponents類。UriComponentsBuilder提供了兩類build方法,我們主要看預設的build方法:
// build methods
/**
* 預設的build方法
*/
public UriComponents build() {
return build(false);
}
/**
* 具體的build實作方法,它通過ssp是否為空,判斷構造的uri屬于相對uri還是絕對uri,生成OpaqueUriComponents類(相對)或HierarchicalUriComponents類(絕對),它們都為UriComponents的子類
*/
public UriComponents build(boolean encoded) {
if (this.ssp != null) {
return new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
}
else {
// 調用pathBuilder的build方法,構造對應的path
return new HierarchicalUriComponents(this.scheme, this.userInfo, this.host, this.port,
this.pathBuilder.build(), this.queryParams, this.fragment, encoded, true);
}
}
可以看到,UriComponentsBuilder的build方法很簡單,就是傳回相應的UriComponents類。其中,在構造HierarchicalUriComponents時,還調用了pathBuilder的build方法生成uri對應的path,這裡不繼續展開了。
總結
從springMVC通過UriComponentsBuilder建構UriComponents類的整個源碼與流程中,我們可以窺見建造者模式在其中發揮的巨大作用。
它通過builder類,提供了多種UriComponents的初始化方式,并能根據不同情況,傳回不同的UriComponents子類。充分的将UriComponents類本身與它的構造過程解耦合。
試想一下,如果不使用建造者模式,而是将大量的初始化方法直接塞到UriComponents類或其子類中,它的代碼将變得非常龐大和備援。而建造者模式可以幫助我們很好的解決這一問題。
是以,如果我們在寫代碼時,某個複雜的類有多種初始化形式或者初始化過程及其繁瑣,并且還對應多個複雜的子類(總之就是構造起來很麻煩),我們就可以用建造者模式,将該類和該類的構造過程解耦哦!