Dubbo框架大牛 为什么没照搬Joshua Bloch的这个设计模式

Spring框架与阿里Dubbo框架的一次巅峰对决,同样的组件,使用同样的设计模式,Spring框架大牛与Dubbo框架大牛,谁是牛中之牛;你想知道dubbo框架的大牛,为何没使用标准的建造者模式,它们两者有何区别,哪个更合适;Spring框架的这个组件,竟然应用了三种设计模式,估计是设计模式深度患者;JDK中哪些类,应用了建造者模式。

谁家的牛

大家好,欢迎关注极客架构师,极客架构师,专注架构师成长,我是码农老吴。

本期是极客架构师推出的《源码说》的第4期。

在第3期,我给大家分享了,原型模式是如何在JDK的ArrayList,HashMap中实现的,以及在Spring框架及阿里的FastJson类库中原型模式。

在本期,我将给大家分享,建造者模式,在JDK,spring框架,Dubbo框架中的落地方案,看看不同的框架,在建造者模式落地时,都进行了哪些调整优化,还有就是通用的组件,spring框架与dubbo框架,两家的建造者模式,孰强孰弱。


基本思路

建造者模式要点回顾

JDK base模块中的建造者模式

StringBuilder类

Calendar类

Spring框架中的建造者模式

UriComponentsBuilder类

Dubbo框架中的建造者模式

URLBuilder类

ServiceBuilder类


建造者模式要点回顾

有关建造者模式的完整文章和视频,没有看过的朋友,可以到我的文集和视频集中查看,有助于帮你理解后面的内容。

我们只回顾建造者模式的重点内容。

落地方案:Joshua Bloch改进的建造者模式(主流方式)

下面这个实现了建造者模式的类,注意以下6点:

1,它里面的静态内部类MyCacheBuilder才是神来之笔。(主类是产品,静态内部类是工厂,工厂生产产品)

2,它的构造函数是private的(有点像单例模式,防止用户绕开工厂,自己直接建立对象)

3,它的参数,没有提供set方法(主类(产品)是只读的,绝对的线程安全)

4,它里面必须(不能为空)的参数,添加了final关键字

5,静态内部类MyCacheBuilder的set方法,不同于常规的set方法,注意它的返回值

6,静态内部类MyCacheBuilder的build方法,非常重要。

package com.geekarchitect.patterns.builder.demo04;import java.util.Map;/** * @author 极客架构师@吴念 * @createTime 2022/7/6 */public class MyCache {    /**     * 初始化容量,必须     */    private final int initialCapacity;    /**     * 最大数量,必须     */    private final long maximumSize;    /**     * 并行等级。决定segment数量的参数     */    private int concurrencyLevel = -1;    /**     * 最大权重     */    private long maximumWeight = -1L;    /**     * 写操作后失效时间     */    private long expireAfterWriteNanos = -1L;    /**     * 访问操作后失效时间     */    private long expireAfterAccessNanos = -1L;    private MyCache(MyCacheBuilder myCacheBuilder) {        this.initialCapacity = myCacheBuilder.initialCapacity;        this.maximumSize = myCacheBuilder.maximumSize;        this.concurrencyLevel = myCacheBuilder.concurrencyLevel;        this.maximumWeight = myCacheBuilder.maximumWeight;        this.expireAfterWriteNanos = myCacheBuilder.expireAfterWriteNanos;        this.expireAfterAccessNanos = myCacheBuilder.expireAfterAccessNanos;    }    @Override    public String toString() {        return "MyCache{" +                "initialCapacity=" + initialCapacity +                ", maximumSize=" + maximumSize +                ", concurrencyLevel=" + concurrencyLevel +                ", maximumWeight=" + maximumWeight +                ", expireAfterWriteNanos=" + expireAfterWriteNanos +                ", expireAfterAccessNanos=" + expireAfterAccessNanos +                '}';    }    public void put(K key, V value) {    }    public V get(K key) {        return null;    }    public static class MyCacheBuilder {        /**         * 初始化容量,必须         */        private final int initialCapacity;        /**         * 最大数量,必须         */        private final long maximumSize;        private final Map cacheMap = null;        /**         * 并行等级。决定segment数量的参数         */        private int concurrencyLevel = -1;        /**         * 最大权重         */        private long maximumWeight = -1L;        /**         * 写操作后失效时间         */        private long expireAfterWriteNanos = -1L;        /**         * 访问操作后失效时间         */        private long expireAfterAccessNanos = -1L;        public MyCacheBuilder(int initialCapacity, long maximumSize) {            this.initialCapacity = initialCapacity;            this.maximumSize = maximumSize;        }        public MyCacheBuilder setConcurrencyLevel(int concurrencyLevel) {            this.concurrencyLevel = concurrencyLevel;            return this;        }        public MyCacheBuilder setMaximumWeight(long maximumWeight) {            this.maximumWeight = maximumWeight;            return this;        }        public MyCacheBuilder setExpireAfterWriteNanos(long expireAfterWriteNanos) {            this.expireAfterWriteNanos = expireAfterWriteNanos;            return this;        }        public MyCacheBuilder setExpireAfterAccessNanos(long expireAfterAccessNanos) {            this.expireAfterAccessNanos = expireAfterAccessNanos;            return this;        }        public MyCache build() {            return new MyCache(this);        }    }}

测试类

package com.geekarchitect.patterns.builder.demo04;import com.geekarchitect.patterns.builder.demo03.TestCache;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * @author 极客架构师@吴念 * @createTime 2022/7/6 */public class TestMyCache {    private static final Logger LOG = LoggerFactory.getLogger(TestMyCache.class);    public static void main(String[] args) {        TestMyCache testMyCache=new TestMyCache();        testMyCache.demo01();    }    public void demo01() {        MyCache myCache = new MyCache.MyCacheBuilder(10, 100L)                .setConcurrencyLevel(20)                .setMaximumWeight(30L).build();        LOG.info(myCache.toString());    }}


JDK base模块中的建造者模式

查询条件:

repo:^github\.com/openjdk/jdk$ file:^src/java\.base -file:test lang:Java file:builder.java count:all

注意:

建造者模式,一般类名以builder结尾,但是也有特殊情况,我们前面举的案例,就是内部类以builder结尾,按照我们现在的查询方式,应该是查询不出来,但是现有的案例,已经足够我们研究了。

我们仍然是查询JDK基础模块里面的类,而且是JDK的最新版本,关于JDK的查询范围和版本问题,我们在上期,专门说明了一下。以便我们找的案例,都是大家相对比较熟悉的类。

从查询结果看,JDK基础模块里面,符合我们查询条件的,与建造者模式有关键的结果,有14个,足够我们研究了。

我们先看第一个,也是大家非常熟悉的,面试常常被问的类,StringBuilder类。

StringBuilder类

StringBuilder类,顾名思义,是用来创建String对象的,那为什么不直接创建String对象呢,我相信大部分人都知识,是因为有些情况下,我们需要的字符串,是通过多个步骤,拼接的字符串。当出现“多个步骤”创建对象,建造者模式,就有了用武之地。StringBuilder类也就应运而生了,它里面的append()方法,传入的是需要拼接的字符串,返回的都是它自身,方便用户不断的拼接字符串。“return this”,是建造者模式比较明显的一个特征。但并不是说,具有这个特征的,就一定是建造者模式。

下面截图,是StringBuilder类的构造函数,注意这些函数,它们是违反建造者模式的,但是却体现了这个类的灵活之处。

方案点评:

从上面的代码可以看出来,StringBuilder类,既符合建造者模式,又具有一定的灵活性,用户不仅可以使用它里面的append()方法,一步一步拼接所需的字符串;而且也可以直接通过构造函数,建立普通的,不需要拼接的字符串,这是它灵活性的一面。

也就是说,对于StringBuilder类,你可以使用它里面的建造者模式,拼接String对象,也可以通过构造函数传入参数,获取相应的String对象。


Calendar类

查询:

repo:^github\.com/openjdk/jdk$ file:^src/java\.base/share/classes/java/util/Calendar\.java


Java类库中的日历工具类,我们的老朋友Calendar类(我们上期分享原型模式时,已经介绍过它了),今天我们看它里面的建造者模式。这个类的高频使用,是众所周知的,为了广大Java程序员能高效简洁的使用它,在JDK1.8中,这个类的开发者们,引入了建造者模式,而且采用了与Joshua Bloch 推荐的建造者模式,完全一致的实现方式,也就是采用了静态内部类的方式。我们先看一下它在JDK1.8前后的使用案例,体验一下它的改进效果,再了解它的源码。

案例对比

package com.geekarchitect.patterns.builder.demo05;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.text.SimpleDateFormat;import java.util.Calendar;import static java.util.Calendar.*;/** * @author 极客架构师@吴念 * @createTime 2022/7/25 */public class TestCalendar {    private static final Logger LOG = LoggerFactory.getLogger(TestCalendar.class);    public static void main(String[] args) {        TestCalendar testCalendar = new TestCalendar();        testCalendar.demo01();        testCalendar.demo02();    }    public void demo01() {        Calendar calendar =                Calendar.getInstance();        calendar.set(YEAR, 2022);        calendar.set(MONTH, JULY);        calendar.set(DATE, 7);        calendar.set(HOUR, 1);        calendar.set(MINUTE, 5);        calendar.set(SECOND, 10);        calendar.set(AM_PM, PM);        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");        LOG.info("calendar传统用法案例 {}", sdf.format(calendar.getTime()));    }    public void demo02() {        Calendar calendar = new Calendar.Builder()                .set(YEAR, 2022)                .set(MONTH, JULY)                .set(DATE, 7)                .set(HOUR, 1)                .set(MINUTE, 5)                .set(SECOND, 10)                .set(AM_PM, PM).build();        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");        LOG.info("calendar JDK1.8 新用法案例 {}", sdf.format(calendar.getTime()));    }}

静态内部类Builder

在JDK1.8,Calendar类,新增了一个静态内部类,实现了建造者模式。

静态内部类中的set()方法

注意它的返回值。

静态内部类的build()方法

它的返回值是Calendar类的对象

思考一下自己的项目,看看有没有类似的类,可以考虑升级一下,改善一下用户体验。


Spring框架中的建造者模式

查询语句:

repo:^github\.com/spring-projects/spring-framework$ -file:test lang:Java file:builder.java count:all

从查询结果上看,spring框架中,大约有33个结果,与建造者模式相关,使用率还是非常高的。


UriComponentsBuilder类

repo:^github\.com/spring-projects/spring-framework$ file:^spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder\.java

Spring框架的UriComponentsBuilder是一个非常好的综合性案例,里面既有建造者模式,又有前面我们已经分享的原型模式,还有我们后面要分析的工厂模式,简直是一个创建者模式大全,看来这个模块的架构师,是一个设计模式成瘾的深度患者。看看后面是否有机会,我专门就这个模块,写个专题文章,把这几个模式融会贯通一下。而且对于建造者模式,居然还建立了一个接口UriBuilder。我们先看一个案例,体验一下这个建造者模式。


案例

package com.geekarchitect.patterns.builder.demo05;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.util.UriComponents;import org.springframework.web.util.UriComponentsBuilder;/** * @author 极客架构师@吴念 * @createTime 2022/7/26 */public class TestUriComponentsBuilder {    private static final Logger LOG = LoggerFactory.getLogger(TestUriComponentsBuilder.class);    public static void main(String[] args) {        TestUriComponentsBuilder testUriComponentsBuilder=new TestUriComponentsBuilder();        testUriComponentsBuilder.demo01();    }    public void demo01(){        UriComponents uriComponents = UriComponentsBuilder.newInstance()                .scheme("https")                .host("space.bilibili.com")                .path("//1135139396/search/video")                .queryParam("keyword", "设计模式")                .build();        LOG.info("UriComponentsBuilder 案例");        LOG.info("URL = {}",uriComponents.toUri());    }}

运行结果

从上面的代码看,UriComponentsBuilder是用来建立UriComponents类的对象。

UriComponents类

它是一个抽象类,执行了Serializable接口,说明它是一个支持序列化的类。它里面定义了组成Uri的各个部分,如:scheme,Host,path等

@SuppressWarnings("serial")public abstract class UriComponents implements Serializable {        /** Captures URI template variable names. */        private static final Pattern NAMES_PATTERN = Pattern.compile("\{([^/]+?)\}");        @Nullable        private final String scheme;        @Nullable        private final String fragment;        protected UriComponents(@Nullable String scheme, @Nullable String fragment) {                this.scheme = scheme;                this.fragment = fragment;        }        // Component getters        /**         * Return the scheme. Can be {@code null}.         */        @Nullable        public final String getScheme() {                return this.scheme;        }        /**         * Return the fragment. Can be {@code null}.         */        @Nullable        public final String getFragment() {                return this.fragment;        }        /**         * Return the scheme specific part. Can be {@code null}.         */        @Nullable        public abstract String getSchemeSpecificPart();        /**         * Return the user info. Can be {@code null}.         */        @Nullable        public abstract String getUserInfo();        /**         * Return the host. Can be {@code null}.         */        @Nullable        public abstract String getHost();        /**         * Return the port. {@code -1} if no port has been set.         */        public abstract int getPort();        /**         * Return the path. Can be {@code null}.         */        @Nullable        public abstract String getPath();        /**         * Return the list of path segments. Empty if no path has been set.         */        public abstract List getPathSegments();        /**         * Return the query. Can be {@code null}.         */        @Nullable        public abstract String getQuery();        

UriComponentsBuilder类,执行了UriBuilder接口,Builder模式,一般很少有接口。这个接口,不仅引出了建造者模式,里面还有工厂模式。

UriBuilder接口


UriComponentsBuilder


UriComponentsBuilder

下面代码,我截取了这个类的一部分方法,以from开头的方法。在这个类的类注释中,介绍了这个类的常见使用方法。

/** * Builder for {@link UriComponents}. * * 

Typical usage involves: *

    *
  1. Create a {@code UriComponentsBuilder} with one of the static factory methods * (such as {@link #fromPath(String)} or {@link #fromUri(URI)})
  2. *
  3. Set the various URI components through the respective methods ({@link #scheme(String)}, * {@link #userInfo(String)}, {@link #host(String)}, {@link #port(int)}, {@link #path(String)}, * {@link #pathSegment(String...)}, {@link #queryParam(String, Object...)}, and * {@link #fragment(String)}.
  4. *
  5. Build the {@link UriComponents} instance with the {@link #build()} method.
  6. *
* * @author Arjen Poutsma * @author Rossen Stoyanchev * @author Phillip Webb * @author Oliver Gierke * @author Brian Clozel * @author Sebastien Deleuze * @author Sam Brannen * @since 3.1 * @see #newInstance() * @see #fromPath(String) * @see #fromUri(URI) */public class UriComponentsBuilder implements UriBuilder, Cloneable { /** * Create a builder that is initialized with the given path. * @param path the path to initialize with * @return the new {@code UriComponentsBuilder} */ public static UriComponentsBuilder fromPath(String path) { UriComponentsBuilder builder = new UriComponentsBuilder(); builder.path(path); return builder; } /** * Create a builder that is initialized from the given {@code URI}. *

Note: the components in the resulting builder will be * in fully encoded (raw) form and further changes must also supply values * that are fully encoded, for example via methods in {@link UriUtils}. * In addition please use {@link #build(boolean)} with a value of "true" to * build the {@link UriComponents} instance in order to indicate that the * components are encoded. * @param uri the URI to initialize with * @return the new {@code UriComponentsBuilder} */ public static UriComponentsBuilder fromUri(URI uri) { UriComponentsBuilder builder = new UriComponentsBuilder(); builder.uri(uri); return builder; } /** * Create a builder that is initialized with the given URI string. *

Note: The presence of reserved characters can prevent * correct parsing of the URI string. For example if a query parameter * contains {@code '='} or {@code '&'} characters, the query string cannot * be parsed unambiguously. Such values should be substituted for URI * variables to enable correct parsing: *

         * String uriString = "/hotels/42?filter={value}";         * UriComponentsBuilder.fromUriString(uriString).buildAndExpand("hot&cold");         * 
* @param uri the URI string to initialize with * @return the new {@code UriComponentsBuilder} */ public static UriComponentsBuilder fromUriString(String uri) { Assert.notNull(uri, "URI must not be null"); 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); boolean opaque = false; if (StringUtils.hasLength(scheme)) { String rest = uri.substring(scheme.length()); if (!rest.startsWith(":/")) { opaque = true; } } builder.scheme(scheme); if (opaque) { String ssp = uri.substring(scheme.length() + 1); if (StringUtils.hasLength(fragment)) { ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1)); } builder.schemeSpecificPart(ssp); } else { if (StringUtils.hasLength(scheme) && scheme.startsWith("http") && !StringUtils.hasLength(host)) { throw new IllegalArgumentException("[" + uri + "] is not a valid HTTP URL"); } 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; } else { throw new IllegalArgumentException("[" + uri + "] is not a valid URI"); } } /** * Create a URI components builder from the given HTTP URL String. *

Note: The presence of reserved characters can prevent * correct parsing of the URI string. For example if a query parameter * contains {@code '='} or {@code '&'} characters, the query string cannot * be parsed unambiguously. Such values should be substituted for URI * variables to enable correct parsing: *

         * String urlString = "https://example.com/hotels/42?filter={value}";         * UriComponentsBuilder.fromHttpUrl(urlString).buildAndExpand("hot&cold");         * 
* @param httpUrl the source URI * @return the URI components of the URI */ public static UriComponentsBuilder fromHttpUrl(String httpUrl) { Assert.notNull(httpUrl, "HTTP URL must not be null"); Matcher matcher = HTTP_URL_PATTERN.matcher(httpUrl); if (matcher.matches()) { UriComponentsBuilder builder = new UriComponentsBuilder(); String scheme = matcher.group(1); builder.scheme(scheme != null ? scheme.toLowerCase() : null); builder.userInfo(matcher.group(4)); String host = matcher.group(5); if (StringUtils.hasLength(scheme) && !StringUtils.hasLength(host)) { throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL"); } builder.host(host); String port = matcher.group(7); if (StringUtils.hasLength(port)) { builder.port(port); } builder.path(matcher.group(8)); builder.query(matcher.group(10)); String fragment = matcher.group(12); if (StringUtils.hasText(fragment)) { builder.fragment(fragment); } return builder; } else { throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL"); } } /** * Create a new {@code UriComponents} object from the URI associated with * the given HttpRequest while also overlaying with values from the headers * "Forwarded" (RFC 7239), * or "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if * "Forwarded" is not found. * @param request the source request * @return the URI components of the URI * @since 4.1.5 * @see #parseForwardedFor(HttpRequest, InetSocketAddress) */ public static UriComponentsBuilder fromHttpRequest(HttpRequest request) { return fromUri(request.getURI()).adaptFromForwardedHeaders(request.getHeaders()); } }

从UriComponentsBuilder这个类的类注释中,我们可以看到,这个类推荐的使用方法是,先采用from开头的这些方法(这些方法,就是工厂系列里面的临时工,简单工厂模式,也叫静态工厂模式),创建出Builder对象,然后根据情况,调用相应的set方法即可。这个思路,可以大大提高程序员,使用这个类的效率,在后面Dubbo的URLBuilder类中,也出现了类似的解决方案。这个我认为,是对建造者模式的一个重大改进。


和上期一样,咱们不能只看国外的代码, 也看看咱们中国程序员写的代码,对码农加强一下爱国主义教育,提升一下民族自信心。

这次我们看看大家比较熟悉的阿里Dubbo框架,它里面大量使用了建造者模式,代码非常具有参考价值。

Dubbo框架中的建造者模式

Dubbo框架,相信大部分Java程序员,对于它都比较熟悉。毕竟现在基于分布式的微服务架构体系大行其道,而Dubbo作为里面的引领者,独占鳌头,风光无限。我们今天就看看它里面的建造者模式。

查询语句

repo:^github\.com/apache/dubbo$ -file:test file:builder.java count:all


dubbo中,涉及建造者模式的类,有50个左右,比spring框架的还多,看来规模还是相当庞大的。

我们挑几个有代表性的类,来看看建造者模式,咱们中国的程序员中,是如何落地的。

上面,我们看了Spring框架中的UriComponentsBuilder类,现在我们看看Dubbo中和它相似的类,看看能不能通过对比,学到点什么,这是spring框架与Dubbo框架,在设计模式上的一次对决,孰强孰弱,我们拭目以待。

URLBuilder类

查询

repo:^github\.com/apache/dubbo$ file:^dubbo-common/src/main/java/org/apache/dubbo/common/URLBuilder\.java

继承关系

如下图,URLBuilder 继承 ServiceConfigURL,ServiceConfigURL 继承 URL,注意,这个URL并不是JDK里面的java.net.URL(这个类是final的,不能继承),而是org.apache.dubbo.common.URL.看来dubbo框架的开发者很嚣张啊,JDK原有的URL都无法满足他们的需求啦,需要他们自己扩展自己的URL类了。这种基础性的类,在普通的项目里面,一般是不敢轻易自定义的,因为牵一发而动全身,很多地方都得进行扩展。


org.apache.dubbo.common.URL

这个类的注释里面对url各个部分的讲解,特别是第二部分,特殊Url案例,讲解的非常详细,大家这方面知识薄弱的,正好可以补充一下。

ServiceConfigURL

这个类继承了URL,里面定义了四个属性,这个四个属性,模式都一样,都是为了提供效率而增加的,起到缓存的作用,何解?

public class ServiceConfigURL extends URL {    private volatile transient String full;    private volatile transient String string;    private volatile transient String identity;    private volatile transient String parameter;    public ServiceConfigURL() {        super();    }    public ServiceConfigURL(URLAddress urlAddress, URLParam urlParam, Map attributes) {        super(urlAddress, urlParam, attributes);    }    @Override    public String toString() {        if (string != null) {            return string;        }        return string = super.toString();    }    @Override    public String toFullString() {        if (full != null) {            return full;        }        return full = super.toFullString();    }    @Override    public String toIdentityString() {        if (identity != null) {            return identity;        }        return identity = super.toIdentityString();    }    @Override    public String toParameterString() {        if (parameter != null) {            return parameter;        }        return parameter = super.toParameterString();    }}

我以full属性为例给大家讲解,大家就可以触类旁通了。

这个full属性,被用在如下的toFullString()方法里面,这种代码是典型的查询缓存的代码模式。说明super.toFullString()应该是一个重量级操作。需要对它的结果,进行缓存,以便提升效率。

URL的toFullString()

如下所示,URL里面的toFullString()依赖的是buildString()方法,而这个方法里面,一大堆的业务逻辑需要处理,所以是一个重量级的操作。所以,非常有必要对它的结果进行缓存,避免造成资源浪费。

full属性的volatile和transient修饰符

transient:我们在前面刚刚分享过,表示该属性不支持序列化。因为它是缓存值,所以,没有缓存的必要,被反序列化之后为空,可以再重新生成新的值。

volatile:精通多线程的朋友,应该清楚它的含义,表示所修饰的属性,在多线程环境下的可见性。这个说起来话长,我这次就不进行扩展了,后面我会推出《极客基本功之并发编程》,会对这个修饰符,进行系统阐述。

我们言归正传,回到建造者设计模式上面,看看URLBuilder实现的建造者模式,有哪些值得我们学习的。

建造者模式点评

1,大胆使用重叠式构造函数

我在分享建造者模式时,对重叠式构造函数的解决方案,进行了详细讲解,并且强调了它的弊端,但是如果把它和建造者模式,同时使用,可以大大方便用户的使用。如果单纯只使用建造者模式,每个参数,都需要用户调用相应的set方法进行初始化,虽然使用了链式调用,但是代码仍然有些繁琐,如果对于用户经常联合使用的参数,采用构造函数进行初始化,可以大大简化用户的代码。

而URLBuilder类,就是很好的证明。看看下面的代码,它定义了一系列的构造函数。同时也定义了相应的set方法。用户可以根据情况选择合适的方案。

public final class URLBuilder extends ServiceConfigURL {    private String protocol;    private String username;    private String password;    // by default, host to registry    private String host;    // by default, port to registry    private int port;    private String path;    private Map parameters;    private Map attributes;    private Map> methodParameters;    public URLBuilder() {        protocol = null;        username = null;        password = null;        host = null;        port = 0;        path = null;        parameters = new HashMap<>();        attributes = new HashMap<>();        methodParameters = new HashMap<>();    }    public URLBuilder(String protocol, String host, int port) {        this(protocol, null, null, host, port, null, null);    }    public URLBuilder(String protocol, String host, int port, String[] pairs) {        this(protocol, null, null, host, port, null, CollectionUtils.toStringMap(pairs));    }    public URLBuilder(String protocol, String host, int port, Map parameters) {        this(protocol, null, null, host, port, null, parameters);    }    public URLBuilder(String protocol, String host, int port, String path) {        this(protocol, null, null, host, port, path, null);    }    public URLBuilder(String protocol, String host, int port, String path, String... pairs) {        this(protocol, null, null, host, port, path, CollectionUtils.toStringMap(pairs));    }    public URLBuilder(String protocol, String host, int port, String path, Map parameters) {        this(protocol, null, null, host, port, path, parameters);    }    public URLBuilder(String protocol,                      String username,                      String password,                      String host,                      int port,                      String path,                      Map parameters) {        this(protocol, username, password, host, port, path, parameters, null);    }    public URLBuilder(String protocol,                      String username,                      String password,                      String host,                      int port,                      String path,                      Map parameters,                      Map attributes) {        this.protocol = protocol;        this.username = username;        this.password = password;        this.host = host;        this.port = port;        this.path = path;        this.parameters = parameters != null ? parameters : new HashMap<>();        this.attributes = attributes != null ? attributes : new HashMap<>();    }   

2,没有采用静态内部类模式

我认为,对于建造者模式,Joshua Bloch的静态内部类机制,是一种比较好的落地方案,在多线程环境下,会更安全,但是URLBuilder并没有使用这个方案,我分析有一个比较大的原因,就是这个类,并不是给普通用户使用的,而是框架内部自用的,所以这个类,并没有考虑的那么严格,正常使用,一般是不会有线程安全问题的。

3,根据内容反向生产对象(静态工厂方法模式)

URLBuilder类里面,还有一个非常值得我们学习的点,就是根据URL字符串,反向生成URLBuilder对象。我们生成URLBuilder对象,目的是为了对URL各个部分进行解析,以便我们使用URL,而URL本身,又可以表示为常见的URL字符串,如:

“http://www.facebook.com/friends?param1=value1¶m2=value2”

那么根据URL字符串,反向生成对应的对象,也可以大大提高用户创建对象的效率,因为用户对URL地址,还是比较熟悉的。URLBuilder类里面的from方法,实现了静态工厂方法,就起到这个作用。

URLBuilder案例

Dubbo项目里面,关于URLBuilder的单元测试类,对URLBuilder类的使用,有很好的案例,特别是对from()方法的使用,可以说是淋漓尽致。

repo:^github\.com/apache/dubbo$ file:^dubbo-common/src/test/java/org/apache/dubbo/common/URLBuilderTest\.java


URLBuilder类里面的建造者模式,我们就讲到这里,它与Spring框架的UriComponentsBuilder类,孰强孰弱,大家自己评判,我就不剥夺大家思考的空间了。


熟悉dubbo框架的朋友,对下面这张图里面的组件,应该不会陌生吧,Dubbo作为微服务框架,框架使用者最常用的两类组件,应该是Service(服务提供者)和Reference(服务消费者)这两个组件吧,我们就以Service为例进行说明。

dubbo中,对组件的配置,可以通过各种途径,xml方式,API方式,注解方式等等,无论哪种方式,对于Service组件,用户的配置信息,都要通过ServiceConfig对象来封装,而ServiceConfig则可以通过ServiceBuilder类来生成,这个类就是我们研究的对象,因为它里面使用了建造者模式。

ServiceConfig

对于一个建造者模式,我们在研究它之前,好好的看看它所生成的产品,或者叫对象。对我们理解建造者模式,有很大的帮助。

上面是ServiceConfig类,这个可能不直观,看下面的类图,更直观一些。

可以看出来,ServiceConfig上面继承了多层,每个父类,都定义了一些属性,比较重要的我已经标出来了。但是,不论再复杂,它也是封装数据用的,相当于实体类,业务逻辑不会太复杂。

看来,dubbo框架的类层次结构,还是比较深的。

下面我们看看,用于建立ServiceConfig对象的ServiceBuilder类,它里面实现了建造者模式。

ServiceBuilder类

下图是ServiceBuilder的类层次结构,上面也是一堆父类,确实有些复杂。不过,我们是研究建造者模式的,目标明确,就不会迷路,就容易看懂它的代码及结构。

ServiceBuilder

它里面的建造者模式,和上面的URLBuilder有相似之处,都没有用静态内部类的模式,而是对泛型的使用,到了登峰造极的地步,看来后面我有必要,对泛型,进行一个系列分享,否则看懂这些代码,还是有一定难度的,是否需要,大家可以评论区留言。

public class ServiceBuilder extends AbstractServiceBuilder, ServiceBuilder> {    /**     * The interface name of the exported service     */    private String interfaceName;    /**     * The interface class of the exported service     */    private Class<?> interfaceClass;    /**     * The reference of the interface implementation     */    private U ref;    /**     * The service name     */    private String path;    /**     * The method configuration     */    private List methods;    /**     * The provider configuration     */    private ProviderConfig provider;    /**     * The providerIds     */    private String providerIds;    /**     * whether it is a GenericService     */    private String generic;    public static  ServiceBuilder newBuilder() {        return new ServiceBuilder<>();    }    public ServiceBuilder id(String id) {        return super.id(id);    }    public ServiceBuilder interfaceName(String interfaceName) {        this.interfaceName = interfaceName;        return getThis();    }    public ServiceBuilder interfaceClass(Class<?> interfaceClass) {        this.interfaceClass = interfaceClass;        return getThis();    }    public ServiceBuilder ref(U ref) {        this.ref = ref;        return getThis();    }    public ServiceBuilder path(String path) {        this.path = path;        return getThis();    }    public ServiceBuilder addMethod(MethodConfig method) {        if (this.methods == null) {            this.methods = new ArrayList<>();        }        this.methods.add(method);        return getThis();    }    public ServiceBuilder addMethods(List<? extends MethodConfig> methods) {        if (this.methods == null) {            this.methods = new ArrayList<>();        }        this.methods.addAll(methods);        return getThis();    }    public ServiceBuilder provider(ProviderConfig provider) {        this.provider = provider;        return getThis();    }    public ServiceBuilder providerIds(String providerIds) {        this.providerIds = providerIds;        return getThis();    }    public ServiceBuilder generic(String generic) {        this.generic = generic;        return getThis();    }    public ServiceConfig build() {        ServiceConfig serviceConfig = new ServiceConfig<>();        super.build(serviceConfig);        serviceConfig.setInterface(interfaceName);        serviceConfig.setInterface(interfaceClass);        serviceConfig.setRef(ref);        serviceConfig.setPath(path);        serviceConfig.setMethods(methods);        serviceConfig.setProvider(provider);        serviceConfig.setProviderIds(providerIds);        serviceConfig.setGeneric(generic);        return serviceConfig;    }    @Override    protected ServiceBuilder getThis() {        return this;    }}

案例:

小结一下,对于Dubbo框架中的建造者模式,仅从URLBuilder类和ServiceBuilder类来看,它们确实有不少相似之处,都没有采用标准的,Joshua Bloch 推荐的建造者模式。也就是没有采用静态内部类的方式。创建的对象,也不是只读的,因为只读对象,具有很高的线程安全性,我估计是因为这些类,都是框架内部自用的,所以只考虑了用户正常使用的情况,而不会出现线程安全问题。另外,对泛型技术使用的比较熟练,看来进阿里大厂,不精通泛型是不行的。


后续

至此,关于建造者模式,在开源软件JDK,Spring,Dubbo中的落地,我们就分享到这里。

后面,《源码说》系列,将分享单例模式,在开源软件中的使用情况。

大家对我的分享方式,或者分享内容,有什么意见或者建议,欢迎评论区留言或者私信我。我会不断进行改进,大家共同进步。

极客架构师,专注架构师成长,我们下期见。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章