浅谈springMVC中的设计模式(4)——建造者模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ljw761123096/article/details/79778251

建造者模式,对于后端开发人员来说应该是很熟悉的,我们比较常用的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(2);
            String userInfo = matcher.group(5);
            String host = matcher.group(6);
            String port = matcher.group(8);
            String path = matcher.group(9);
            String query = matcher.group(11);
            String fragment = matcher.group(13);
            // uri是否透明的标志位
            boolean opaque = false;
            // uri存在scheme且后面不跟:/则为不透明uri 
            例如mailto:java-net@java.sun.com 
            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(1);
                if (StringUtils.hasLength(fragment)) {
                    ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1));
                }
                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类或其子类中,它的代码将变得非常庞大和冗余。而建造者模式可以帮助我们很好的解决这一问题。
所以,如果我们在写代码时,某个复杂的类有多种初始化形式或者初始化过程及其繁琐,并且还对应多个复杂的子类(总之就是构造起来很麻烦),我们就可以用建造者模式,将该类和该类的构造过程解耦哦!

阅读更多
换一批

没有更多推荐了,返回首页