备受争议的Lombok中@Builder是怎么实现的?
前言 备受争议的Lombok
,有的人喜欢它让代码更整洁,有的人不喜欢它,巴拉巴拉一堆原因。在我看来Lombok
唯一的缺点可能就是需要安装插件了,但是对于业务开发的项目来说,它的优点远远超过缺点。
我们可以看一下,有多少项目使用了Lombok(数量还在疯涨中…)
尽管如此,我们今天也只是单纯的来看一下@Builder()这个东西
@Builder的使用 使用@Builder修饰类 1 2 3 4 5 6 7 8 @Data @Builder public class UserDO { private Long id; private String name; }
使用建造者模式创建类 1 2 3 4 5 6 7 8 @Test public void test () { UserDO userDO = UserDO.builder() .id(1L ) .name("iisheng" ) .build(); System.out.println(userDO); }
编译后源码 执行javac -cp ~/lombok.jar UserDO.java -verbose
将.java
编译成.class
文件。
通过IDE查看该.class
源码
下面展示的是被我处理后的源码,感兴趣的同学,可以自己执行上面命令,查看完整源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class UserDO { private Long id; private String name; public String toString () { return "UserDO(id=" + this .getId() + ", name=" + this .getName() + ")" ; } UserDO(Long var1, String var2) { this .id = var1; this .name = var2; } public static UserDO.UserDOBuilder builder () { return new UserDO.UserDOBuilder(); } private UserDO () { } public static class UserDOBuilder { private Long id; private String name; UserDOBuilder() { } public UserDO.UserDOBuilder id (Long var1) { this .id = var1; return this ; } public UserDO.UserDOBuilder name (String var1) { this .name = var1; return this ; } public UserDO build () { return new UserDO(this .id, this .name); } } }
由此,我们可以看出来Builder的实现步骤:
在UserDO
中创建静态UserDOBuilder
编写设置属性方法,返回UserDOBuilder
对象
编写build()
方法,返回UserDO
对象
是不是很简单?我曾经看过不知道哪个大佬说的一句话,整洁的代码不是说,行数更少,字数更少,而是阅读起来逻辑更清晰。所以,我觉得,哪怕我们不用@Builder,也应该多用这种建造者模式。
是时候看看什么是建造者模式了!
建造者模式 UML类图
这是大部分书籍网络中的建造者模式类图
产品类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Product { private String name; private Integer val; Product(String name, Integer val) { this .name = name; this .val = val; } @Override public String toString () { return "Product is " + name + " value is " + val; } }
抽象建造者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public abstract class Builder { protected Integer val; protected String name; public abstract void setVal (Integer val) ; public void setName (String name) { this .name = name; } public abstract Product buildProduct () ; }
具体建造者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ConcreteBuilder extends Builder { @Override public void setVal (Integer val) { this .val = val + 100 ; } @Override public Product buildProduct () { return new Product(name, val); } }
导演类 1 2 3 4 5 6 7 8 9 10 11 public class Director { private Builder builder = new ConcreteBuilder(); public Product getAProduct () { builder.setName("ProductA" ); builder.setVal(2 ); return builder.buildProduct(); } }
我更喜欢这样的建造者模式类图
Product
的创建,也依赖于Builder
。代码只需要将上面的Product
和ConcreteBuilder
调整一下即可。
调整后的产品类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Product { private String name; private Integer val; Product(Builder builder) { this .name = builder.name; this .val = builder.val; } @Override public String toString () { return "Product is " + name + " value is " + val; } }
这代码只是将构造方法改了,使用Builder
来创建Product
对象。
调整后的具体建造者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ConcreteBuilder extends Builder { @Override public void setVal (Integer val) { this .val = val + 100 ; } @Override public Product buildProduct () { return new Product(this ); } }
相应的使用带Builder
的Product
的构造方法。
JDK中的建造者模式
StringBuilder (截取部分源码)
抽象建造者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 abstract class AbstractStringBuilder implements Appendable , CharSequence { char [] value; int count; public AbstractStringBuilder append (String str) { if (str == null ) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0 , len, value, count); count += len; return this ; } @Override public AbstractStringBuilder append (CharSequence s) { if (s == null ) return appendNull(); if (s instanceof String) return this .append((String)s); if (s instanceof AbstractStringBuilder) return this .append((AbstractStringBuilder)s); return this .append(s, 0 , s.length()); } public AbstractStringBuilder delete (int start, int end) { if (start < 0 ) throw new StringIndexOutOfBoundsException(start); if (end > count) end = count; if (start > end) throw new StringIndexOutOfBoundsException(); int len = end - start; if (len > 0 ) { System.arraycopy(value, start+len, value, start, count-end); count -= len; } return this ; } }
具体建造者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public final class StringBuilder extends AbstractStringBuilder implements java .io .Serializable , CharSequence { @Override public StringBuilder append (String str) { super .append(str); return this ; } @Override public StringBuilder append (CharSequence s) { super .append(s); return this ; } @Override public StringBuilder delete (int start, int end) { super .delete(start, end); return this ; } }
StringBuilder
中的建造者模式比较简单,但是我的确没找到StringBuilder
非要用建造者模式的原因,或许就是想让我们写下面这样的代码?
1 2 3 4 5 6 7 8 public static void main (String[] args) { StringBuilder sb = new StringBuilder(); sb.append("Love " ) .append("iisheng !" ) .insert(0 , "I " ); System.out.println(sb); }
但是我希望你能通过StringBuilder
,感受一下建造者模式的气息
Guava Cache中的建造者模式 如何使用 Guava Cache? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public static void main (String[] args) { LoadingCache<String, Integer> cache = CacheBuilder.newBuilder() .maximumSize(10 ) .expireAfterWrite(10 , TimeUnit.SECONDS) .build(new CacheLoader<String, Integer>() { @Override public Integer load (String key) throws Exception { return -1 ; } }); System.out.println(cache.getIfPresent("key1" )); cache.put("key1" , 1 ); System.out.println(cache.getIfPresent("key1" )); try { System.out.println(cache.get("key2" )); } catch (ExecutionException e) { e.printStackTrace(); } }
下面是截取建造者模式相关的部分代码
产品接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @DoNotMock ("Use CacheBuilder.newBuilder().build()" )@GwtCompatible public interface Cache <K , V > { @Nullable V getIfPresent (@CompatibleWith("K" ) Object key) ; V get (K key, Callable<? extends V> loader) throws ExecutionException ; void put (K key, V value) ; long size () ; ConcurrentMap<K, V> asMap () ; void cleanUp () ; }
另一个产品接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @GwtCompatible public interface LoadingCache <K , V > extends Cache <K , V >, Function <K , V > { V get (K key) throws ExecutionException ; V getUnchecked (K key) ; void refresh (K key) ; @Deprecated @Override V apply (K key) ; @Override ConcurrentMap<K, V> asMap () ; }
产品实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 static class LocalManualCache <K , V > implements Cache <K , V >, Serializable { final LocalCache<K, V> localCache; LocalManualCache(CacheBuilder<? super K, ? super V> builder) { this (new LocalCache<K, V>(builder, null )); } private LocalManualCache (LocalCache<K, V> localCache) { this .localCache = localCache; } @Override public @Nullable V getIfPresent (Object key) { return localCache.getIfPresent(key); } @Override public V get (K key, final Callable<? extends V> valueLoader) throws ExecutionException { checkNotNull(valueLoader); return localCache.get( key, new CacheLoader<Object, V>() { @Override public V load (Object key) throws Exception { return valueLoader.call(); } }); } @Override public void put (K key, V value) { localCache.put(key, value); } @Override public long size () { return localCache.longSize(); } @Override public ConcurrentMap<K, V> asMap () { return localCache; } @Override public void cleanUp () { localCache.cleanUp(); } private static final long serialVersionUID = 1 ; Object writeReplace () { return new ManualSerializationProxy<>(localCache); } }
另一个产品实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static class LocalLoadingCache <K , V > extends LocalManualCache <K , V > implements LoadingCache <K , V > { LocalLoadingCache( CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) { super (new LocalCache<K, V>(builder, checkNotNull(loader))); } @Override public V get (K key) throws ExecutionException { return localCache.getOrLoad(key); } @Override public V getUnchecked (K key) { try { return get(key); } catch (ExecutionException e) { throw new UncheckedExecutionException(e.getCause()); } } @Override public void refresh (K key) { localCache.refresh(key); } @Override public final V apply (K key) { return getUnchecked(key); } private static final long serialVersionUID = 1 ; @Override Object writeReplace () { return new LoadingSerializationProxy<>(localCache); } }
实际产品实现类LocalCache 上面两个产品类实际上,内部使用的是LocalCache
来存储数据。我们再看下LocalCache
的实现。
LocalCache
继承AbstractCache
,我们先看AbstractCache
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @GwtCompatible public abstract class AbstractCache <K , V > implements Cache <K , V > { protected AbstractCache () {} @Override public V get (K key, Callable<? extends V> valueLoader) throws ExecutionException { throw new UnsupportedOperationException(); } @Override public void put (K key, V value) { throw new UnsupportedOperationException(); } @Override public void cleanUp () {} @Override public long size () { throw new UnsupportedOperationException(); } @Override public ConcurrentMap<K, V> asMap () { throw new UnsupportedOperationException(); } }
再来看,LocalCache
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @GwtCompatible (emulated = true )class LocalCache <K , V > extends AbstractMap <K , V > implements ConcurrentMap <K , V > { final long expireAfterWriteNanos; final @Nullable CacheLoader<? super K, V> defaultLoader; LocalCache( CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) { concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); maxWeight = builder.getMaximumWeight(); weigher = builder.getWeigher(); expireAfterAccessNanos = builder.getExpireAfterAccessNanos(); expireAfterWriteNanos = builder.getExpireAfterWriteNanos(); refreshNanos = builder.getRefreshNanos(); defaultLoader = loader; int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); if (evictsBySize() && !customWeigher()) { initialCapacity = (int ) Math.min(initialCapacity, maxWeight); } int segmentShift = 0 ; int segmentCount = 1 ; while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) { ++segmentShift; segmentCount <<= 1 ; } this .segmentShift = 32 - segmentShift; segmentMask = segmentCount - 1 ; this .segments = newSegmentArray(segmentCount); } }
建造者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @GwtCompatible (emulated = true )public final class CacheBuilder <K , V > { long maximumSize = UNSET_INT; long expireAfterWriteNanos = UNSET_INT; Supplier<? extends StatsCounter> statsCounterSupplier = NULL_STATS_COUNTER; public CacheBuilder<K, V> maximumSize (long maximumSize) { checkState( this .maximumSize == UNSET_INT, "maximum size was already set to %s" , this .maximumSize); checkState( this .maximumWeight == UNSET_INT, "maximum weight was already set to %s" , this .maximumWeight); checkState(this .weigher == null , "maximum size can not be combined with weigher" ); checkArgument(maximumSize >= 0 , "maximum size must not be negative" ); this .maximumSize = maximumSize; return this ; } public CacheBuilder<K, V> expireAfterWrite (long duration, TimeUnit unit) { checkState( expireAfterWriteNanos == UNSET_INT, "expireAfterWrite was already set to %s ns" , expireAfterWriteNanos); checkArgument(duration >= 0 , "duration cannot be negative: %s %s" , duration, unit); this .expireAfterWriteNanos = unit.toNanos(duration); return this ; } public CacheBuilder<K, V> recordStats () { statsCounterSupplier = CACHE_STATS_COUNTER; return this ; } public <K1 extends K, V1 extends V> Cache<K1, V1> build () { checkWeightWithWeigher(); checkNonLoadingCache(); return new LocalCache.LocalManualCache<>(this ); } public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build ( CacheLoader<? super K1, V1> loader) { checkWeightWithWeigher(); return new LocalCache.LocalLoadingCache<>(this , loader); } }
Guava Cache的代码还是蛮复杂的,来一张UML图,便于理解
LoadingCache
接口继承了Cache
接口,两个接口都定义了缓存的基本方法
CacheLoader
是LocalCache
的成员变量
LocalCache
继承AbstractMap
,是真正意义上的产品类
LocalManualCache
是CacheBuilder
的build()
方法产生的对象的类,LocalManualCache
因为有LocalCache
作为成员变量,使得它成为了产品类,LocalManualCache
实现了Cache
接口
LocalLoadingCache
继承了LocalManualCache
,是CacheBuilder
的build(CacheLoader<? super K1, V1> loader)
方法产生的对象的类,LocalLoadingCache
实现了LoadingCache
接口
总结 什么时候适合使用建造者模式?
创建对象参数过多的时候
创建一个有很多属性的对象,如果参数在构造方法中写,看起来很乱,一长串不说,还很容易写错。
对象的部分属性是可选择的时候
创建的对象有很多属性是可选择的那种,常见的比如配置类等,不同使用者有不同的配置。
对象创建完成后,就不能修改内部属性的时候
不提供set()方法,使用建造者模式一次性把对象创建完成。
建造者模式和工厂模式的区别是什么?
建造者模式,通过设置不同的可选参数,“定制化”的创建不同的对象
工厂模式,是直接创建不同但是相关类型的对象(继承同一父类或者接口的一组子类)
最后想说的 由@Builder
想到的建造者模式,然后看了StringBuilder
以及Guava Cache
的源码,其中还是有很多值得我们学习的地方。
建造者模式,可能不同的人有不同的理解,不同的实现有不同的方法,但是我们只有深刻的理解了其中的设计思想,才不至于在项目中生搬硬套,才能灵活运用。
参考文献: [1]:《设计模式之禅》 [2]:《Effective Java中文版》 [3]:《设计模式之美 建造者模式》