# 7.4 Java开发利器

在Java开发中，有很多成熟的开源工具库供大家选用，覆盖了很多常见的但JDK中没有提供的功能和使用场景。学习并熟练使用这些类库，能让你在编码过程中少走很多弯路，并且也能学习到很多漂亮的编码方式和风格。比较常用的有以下几个：

* Apache Commons：Apache开源的Java相关工具库，囊括了编码、文本、网络等等一系列工具类。
* Guava：Google贡献的一个服务于Java6、7的类库，囊括了集合、字符串、缓存等一系列工具类。
* Joda Time: 为了弥补JDK自带日期类使用上的不方便而创造的一个日期时间工具库，已经得到了广泛运用。
* FastJson: 阿里开源的JSON序列化、反序列化类库，使用比较方便，性能也比较好。
* Orika: 简单快速高效的Java Bean映射、复制框架。
* MapDB: 专为Java设计的高性能的数据库，可以被用作多级缓存。
* FastUtils：扩充了Java的集合类，提供了很多快速、压缩、支持基本数据类型的集合类以及大规模集合。
* JCTools: 提供很多并发集合类，适用于高并发的业务场景。
* Relections：org.relections提供了一系列对于运行时元数据的查询接口，大大简化了Java自带反射API的使用。
* Lombok：一个简化开发工作的开发工具，能够节省很多重复、繁琐代码的编写。使用@Data可以省去编写getter、setter、hashCode、equals、toString等，使用@Slf4j自动生成Slf4j log对象。其原理是JDK中注解的编译时解析机制，javac会在执行的时候去调用实现了相关API（Pluggable Annotation Processing API）的程序，从而可以对编译器做一些增强。

此外，还有其他大厂出品的工具类库如：Twitter的commons、Linkedin的linkedin-utils等，基本上也都是对一些常用工具的封装。

还需要说明的是，前文提到过的Spring框架中也提供了很多可用的工具，如StringUtils、StopWatch、ReflectionUtils、ResourceUtils等，在Spring应用中完全可以拿来用。

本章主要讲述其中最为常用的Apache Commons、Google Guava、Joda Time、FastJson、Orika以及MapDB。

## 7.4.1 Apache Commons

Apache Commons是Apache开源的一个可复用Java组件库。包含了多达50个子项目。其中开发中常用的有以下几个：

* BeanUtils: 提供了对于Java Bean进行各种操作，克隆对象、属性。
* Codec: 处理常用的编码解码方法的工具类包等。
* Collections: 扩展Java集合框架的操作。
* IO: 输入输出工具的封装。
* Lang: Java基本对象（java.lang）方法的工具类包。
* HttpClient: 低层次对HTTP协议操作的封装, 提供HTTP客户端与服务器的各种通讯操作。

### BeanUtils

BeanUtils基于JDK的java.beans，提供了一系列对Java Bean的操作：读取（get）和设置（set）Bean属性值、动态定义和访问bean属性等。

先讲述一下Java Bean的定义，符合Bean规范的Java类需要符合以下要求：

* 类必须是public访问权限，且需要有一个public的无参构造方法，方便利用Java的反射动态创建对象实例。
* Bean的属性都是私有字段。
* Bean的属性值只能通过setter方法设置。
* 读取Bean的属性需要通过getter方法。

对于Bean的操作，主要是BeanUtils这个类。

BeanUtils将property分成simple（简单类型:String、int）、indexed（索引类型：数组、ArrayLsit）以及Maped（Map类型）三种类型，可以直接get和set Java Bean中的一个属性的值。

这里需要注意的是，此类其实最终是调用的BeanUtilsBean，但是BeanUtilsBean2继承BeanUtilsBean并做了升级，提供了任何类型到字符串的转换。因此，使用的时候建议直接使用BeanUtilsBean2。

```
BeanUtilsBean beanUtilsBean = new BeanUtilsBean2();
beanUtilsBean.getConvertUtils().register(false, false, 0);//错误不抛出异常、不使用Null做默认值，数组的默认大小为0
    
User user = new User();
beanUtilsBean.setProperty(user, "name", "testName");//设置属性的值
beanUtilsBean.getProperty(user,"name");//获取属性的值
```

这里需要注意的是，如果类型是indexed，那么属性名\[index]可以直接获取某个元素的值，而对于Maped类型，属性名（key值）可以获取某一个key对应的value。

此外，还提供了复制以及克隆Bean的功能。

```
User user = new User();
user.setName("test");
User user2 = new User();
    
beanUtilsBean.copyProperties(user2,user);
User user3= (User)beanUtilsBean.cloneBean(user);
```

但这里的复制是浅复制，2个Bean的同一个属性可能拥有同一个对象的引用。

还有Map和Bean之间的转换。

```
Map<String,Object> map = beanUtilsBean.describe(user);//bean->map
beanUtilsBean.populate(user, map) //map->bean

```

此外，还有一个PropertyUtils和BeanUtils功能几乎一致。不同的是BeanUtils在对Bean赋值是会进行自动类型转化，只要属性名相同，类型会尝试转换，而PropertyUtils则会报错。

### Codec

常用的解码、编码方法封装，包括Base64、MD5、Sha1、URL。

1. Base64

   ```
   Base64.encodeBase64String(byte[] binaryData);
   Base64.decodeBase64(String base64String); 
   ```
2. MD5

   ```
   DigestUtils.md5Hex(final byte[] data);
   ```
3. Sha1

   ```
   DigestUtils.sha1Hex(final byte[] data);
   ```
4. URL

   ```
   URLCodec.encode(final String str);
   URLCodec.decode(final String str);
   ```

### Collections

Collections为JDK的集合类提供了更为丰富的工具类、接口以及实现。最新的版本4，包名修改为：org.apache.commons.collections4。

1. CollectionUtils: 提供了一些方面的操作方法，如判断集合非空、对集合的并集、交集、差集的操作。

   ```
   List<String> list = getList();
   List<String> list2 = getList2();

   if(CollectionUtils.isNotEmpty(list)){ //判断非空
       CollectionUtils.union(list,list2)//并集
       CollectionUtils.subtract(list,list2)//差集
       CollectionUtils.retainAll(list,list2)//交集
   }
   ```
2. 提供了一些新的集合类型。

   ```
   //得到集合里按顺序存放的key之后的某一Key
   OrderedMap map = new LinkedMap();  
   map.put("1", "1");  
   map.put("2", "1");  
   map.put("3", "1");  
   map.firstKey(); // returns "1"
   map.nextKey("1"); // returns "2" 

   //双向map
   BidiMap bidi = new TreeBidiMap();
   bidi.put("6", "6");  
   bidi.get("6");  // returns "6"  
   bidi.getKey("6");  // returns "6"
   ```

### IO

提供了一些IO工具类, 是对java.io的扩展，操作文件非常方便。

1. IOUtils：对IO stream操作的封装。

   ```
   InputStream is = new URL( "http://baidu.cim" ).openStream(); 
   try{
       IOUtils.toString(is, "utf-8");
       IOUtils.readLines(is, "utf-8");
   }finally {  
       IOUtils.closeQuietly(is);  
   }  
   ```
2. FileUtils：对文件操作的封装。

   ```
   File file = new File("/data/data.txt");  
   List lines = FileUtils.readLines(file, "UTF-8"); //读取成字符串集合
   byte[] fileBytes = FileUtils.readFileToByteArray(file); //读取成字节数组
   FileUtils.writeByteArrayToFile(file,fileBytes); //字节写入文件
   FileUtils.writeStringToFile(file, "test"); //字符串写入文件
   ```
3. FileSystemUtils：对文件系统的操作封装。

   ```
   FileSystemUtils.freeSpaceKb("/data"); //查看相应路径的剩余空间
   ```

### Lang

一些公共的工具集合，涵盖了字符串操作、字符操作、 JVM交互操作、归类、异常和位域校验等等。现在最新的版本为3, 包名改成了org.apache.commons.lang3。

1. StringUtils && StringEscapeUtils

   StringUtils继承自Object，是null safe的，即遇到null的String对象，会把它处理掉不会抛出异常；StringEscapeUtils是对字符串做转义的工具类，包括HTML、JS、XML等等。

   ```
   String str = ...;

   StringUtils.isEmpty(str); //判断字符串为空，多个连续空格不为空
   StringUtils.isBlank(str); //判断字符串为空，多个连续空格为空

   StringUtils.trim(str);//以strip开头的方法都是trim方法的扩展，不过可以自定义stripChars，不局限于空白符。

   StringUtils.equals(str,"test");//支持null

   StringUtils.contains("str","test"); //子字符串匹配

   StringUtils.split(str,";"); //根据字符/字符串分隔字符串
   StringUtils.join(new String[]{"1","2"},"-"); //根据字符连接字符串

   StringEscapeUtils.escapeHtml4(str); //对字符串中的html标签做转义
   ```

   这里需要注意的是其split方法，相比字符串自带的split方法使用正则，此方法直接使用了完整的字符串来做匹配，且会丢弃空字符串。
2. ArrayUtils

   ArrayUtils是一个对数组进行特殊处理的类。ArrayUtils扩展了JDK中的Arrays，提供了更多的功能。

   ```
   String[] strs = new String[]{"1", "4", "2"};

   ArrayUtils.nullToEmpty(strs); //如果数组为null，则返回长度为0的数组
   ArrayUtils.reverse(strs); //反转数组

   ArrayUtils.addAll(strs,"3"); //数组添加元素
   ```

   需要注意：使用addAll添加元素，是需要数组拷贝的，慎用。
3. RandomUtils && RandomStringUtils

   提供了生成随机数、字符串的操作封装。

   ```
   RandomUtils.nextInt(0,10); //随机一个整数，从0到10，不包括10。
   RandomStringUtils.random(3); //随机三个字母的字符串出来。
   ```

   这里需要注意的是这俩类都使用了Random这个类，但其是伪随机的，在要求严格的环境下，尽量不要使用这俩类，去使用SecureRandom。
4. NumberUtils

   为数字提供了一些操作封装, 是null safe的。

   ```
   String numberStr = "123";
   long n = NumberUtils.toLong(numberStr); //将字符串转化为long, 如果字符串格式不对或者为Null，则返回0，并不会抛出异常
   long max = NumberUtils.max(new Long[]{1L,5L,10L}); //计算数组最大值
   ```
5. DateUtils && DateFormatUtils

   是对日期时间操作的封装。

   DateUtils提供了很多日期计算。

   ```
   DateUtils.addDays(new Date(),3); //计算三天后的时间
   DateUtils.addHours(new Date(),3); //三个小时候的时间

   DateUtils.truncate(new Date(), Calendar.HOUR); //截断日期到小时，后面的分、秒都为0
   ```

   DateFormatUtils提供了Date到字符串表示的操作。

   ```
   DateFormatUtils.format(new Date(),"yyyyMMdd"); //以yyyyMMdd的格式输出日期
   ```
6. MethodUtils

   通过此工具类可以调用类的方法，实现原理基于反射。

   ```
   MethodUtils.invokeStaticMethod(StringUtils.class,"isNotBlank","test"); //调用静态方法
   MethodUtils.invokeMethod(user,"getName"); //调动实例方法
   ```
7. StopWatch

   StopWatch是一个秒表类。

   ```
   StopWatch stopWatch = new StopWatch();
   stopWatch.start(); //开始计时
   stopWatch.split(); //截断每一次的分段计时
   stopWatch.getSplitTime(); //获取分段计时
   stopWatch.suspend(); //暂停秒表
   stopWatch.resume(); //恢复计时
   stopWatch.stop(); //停止秒表
   stopWatch.getTime(); //获得总共计时
   ```
8. ImmutablePair && ImmutableTriple

   这俩类都是不可变的，经常是用在返回值是两个或者三个的场景下，是对多返回值的通用封装。

   ```
   ImmutablePair pair = ImmutablePair.of(user,user1);
   pair.getLeft();
   pair.getRight();
   ImmutableTriple triple = ImmutableTriple.of(user,user1,user2);
   triple.getLeft();
   triple.getMiddle();
   triple.getRight();
   ```

### HttpClient

提供HTTP客户端与服务器的各种通讯操作, 包括支持各种HTTP Method、SSL连接、Cookie、Session保持等。此工具类现在已经从Apache Commons移到Apache HttpComponents中。包名改为：org.apache.http。

1. 连接池

   HttpClient提供了HTTP连接池的支持，连接池依赖HTTP 1.1的keep alive机制，对HTTP 1.0需要做兼容配置。此外，也支持HTTPS请求。需要注意的是使用连接池能够减少频繁创建、销毁连接的消耗提高性能，但是由于连接池是有锁的，为了提升并发性能，最好对于每一个服务都创建一个连接池。

   ```
   RegistryBuilder<ConnectionSocketFactory> schemeRegistry = RegistryBuilder.create();
   schemeRegistry.register("http", PlainConnectionSocketFactory.getSocketFactory());

   //对https的支持
   SSLContext sslcontext = SSLContext.getInstance("TLS");
   sslcontext.init(new KeyManager[0], new TrustManager[]{new SimpleTrustManager()}, null);
   SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslcontext);
   schemeRegistry.register("https", sf);

   //连接池配置
   PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager(schemeRegistry.build());
   pool.setMaxTotal(1000); //最大连接数支持
   pool.setDefaultMaxPerRoute(100); //每一个路由的最大连接数
   pool.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(5000).build());
   ```
2. HttpRequestBase

   HttpClient支持HTTP的各种方法, 都是HttpRequestBase的子类。

   * HttpGet
   * HttpPost
   * HttpPut
   * HttpDelet

   以上类都有一个参数为URL字符串的构造方法。此外，对post、put这种需要传递数据的方法，HttpClient是使用HttpEntity来实现的。常用的几个HttpEntity如下：

   * UrlEncodedFormEntity：最常见的表单提交，Content-type为application/x-www-form-urlencoded。
   * MultipartFormEntity: 提交文件时常用的方式，Content-type为multipart/form-data
   * StringEntiry：自包含的Entity, 传递JSON数据时可以使用。

   ```
   StringEntity entity = new StringEntity("{\"name\";\"test\"}", "UTF-8");
   entity.setContentType("application/json")    

   HttpPost method = new HttpPost(url);
   method.setEntity(entity);    
   ```
3. Cookie

   Cookie的支持需要依赖CookieStore。HttpClientUtil内置了BasicCookieStore。

   ```
   CookieStore cookieStore = new BasicCookieStore()
   cookieStore.addCookie(Cookie cookie);
   List<Cookie> cookies = cookieStore.getCookies();
   ```
4. HttpClientBuilder

   HttpClient的创建依赖于HttpClientBuilder, 能够对HttpClient的Cookie以及Connect Timeout、Socket Timout、Keep Alive的策略进行配置。

   ```
   HttpClientBuilder httpClientBuilder = HttpClients.custom().setDefaultCookieStore(cookieStore);//cookie支持
   httpClientBuilder.setConnectionManager(pool); //设置连接池
   httpClientBuilder.setDefaultSocketConfig(pool.getDefaultSocketConfig());
   httpClientBuilder.setDefaultRequestConfig(
           RequestConfig.custom()
                   .setConnectTimeout(3000)
                   .setSocketTimeout(5000)
                   .build());
                   
   //对keep alive的策略配置
   httpClientBuilder.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
                       public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                           HeaderElementIterator it = new BasicHeaderElementIterator(response
                                   .headerIterator(HTTP.CONN_KEEP_ALIVE));
                           while (it.hasNext()) {
                               HeaderElement he = it.nextElement();
                               String param = he.getName();
                               String value = he.getValue();
                               if (value != null && param.equalsIgnoreCase("timeout")) {
                                   try {
                                       return Long.parseLong(value) * 1000;
                                   } catch (NumberFormatException ignore) {
                                   }
                               }
                           }
                           // 否则保持活动5秒
                           return 5 * 1000;
                       }
                   });

   HttpClient httpClient = httpClientBuilder.build()；
   ```
5. 使用

   ```
   method.setProtocolVersion(HttpVersion.HTTP_1_1); //设置使用http 1.1
   request.addHeader("User-Agent", agentHeader); //设置ua
   request.addHeader("Connection", "keep-alive"); //为了keepalive支持http 1.0

   HttpResponse res = httpClient.execute(request);

   byte[] byteResult = EntityUtils.toByteArray(res.getEntity());
   ```

   最终可以通过返回的HttResponse拿到接口返回的body、header等信息。

此外，Apache HttpComponents还提供了AsyncHttpClient用于异步通讯场景, 使用流程和HttpClient类似。不同之处如下：

1. 连接池管理器多了IO线程的配置且连接的Registry也不同。

   ```
   Registry<SchemeIOSessionStrategy> sessionStrategyRegistry = RegistryBuilder
         .<SchemeIOSessionStrategy>create()
         .register("http", NoopIOSessionStrategy.INSTANCE)
         .register("https", new SSLIOSessionStrategy(SSLContexts.createDefault()))
         .build();

   // 配置io线程
   IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
         .setIoThreadCount(Runtime.getRuntime().availableProcessors())
         .build();

   // 设置连接池
   ConnectingIOReactor ioReactor;
   ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
   PoolingNHttpClientConnectionManager conMgr = new PoolingNHttpClientConnectionManager(
         ioReactor, null, sessionStrategyRegistry, null);
   conMgr.setMaxTotal(100);
   conMgr.setDefaultMaxPerRoute(100);

   // 连接配置：忽略传输错误，默认编码使用utf-8
   ConnectionConfig connectionConfig = ConnectionConfig.custom()
         .setMalformedInputAction(CodingErrorAction.IGNORE)
         .setUnmappableInputAction(CodingErrorAction.IGNORE)
         .setCharset(Consts.UTF_8).build();
   conMgr.setDefaultConnectionConfig(connectionConfig);
   ```
2. 使用CloseableHttpAsyncClient。

   ```
   RequestConfig requestConfig = RequestConfig.custom()
               .setConnectTimeout(3000)
               .setSocketTimeout(1000).build();

   CloseableHttpAsyncClient asyncClient = HttpAsyncClients.custom().setConnectionManager(conMgr)
       .setDefaultCookieStore(new BasicCookieStore())
       .setDefaultRequestConfig(requestConfig)
       .build();
   ```
3. 使用时，需要先启动Client，并且提供了回调使用方式。

   ```
   asyncClient.start(); //需要先启动Client

   // 通过future获取结果
   HttpGet httpGet = new HttpGet("http://www.baidu.com");
   Future<HttpResponse> responseFuture = asyncClient.execute(httpGet, null);
   try {
       HttpResponse httpResponse = responseFuture.get();
       HttpEntity httpEntity = httpResponse.getEntity();
       ...
   } catch (InterruptedException | ExecutionException e) {
       e.printStackTrace();
   }

   // 回调方式获取结果   
   final HttpGet httpGet2 = new HttpGet("http://www.baidu.com"); 
   asyncClient.execute(httpGet2, new FutureCallback<HttpResponse>() {
       @Override
       public void completed(HttpResponse httpResponse) {
           HttpEntity httpEntity = httpResponse.getEntity();
           ...
       }

       @Override
       public void failed(Exception e) {
       
       }

       @Override
       public void cancelled() {
       
       }
   });

   ...

   asyncClient.close();
   ```

   需要注意的是，无论是HttpClient还是AsyncHttpClient, 连接池都是有锁的。虽然支持对每一个route设置最大连接数，但如果是高并发场景，最好对于每一个服务都创建一个单独的HttpClient实例，使用不同的连接池。

   还需要提到的是，如果想要使用Http缓存提高请求的性能，可以使用Apache HttpComponents提供的httpclient-cache中的CachingHttpClient。

## 7.4.2 Guava

Guava是Google开源的一个涵盖了字符串处理、缓存、并发库、事件总线、IO等常用操作的Java核心库，也是Google自己很多Java项目依赖的工具库。其中的Guava Cache缓存已经在4.3.1讲过。

1. Preconditions

   对条件做前置判断，经常用在方法的最前面，来对参数进行校验，不符合则抛出异常。

   ```
   Preconditions.checkArgument(user != null,"user null error"); //user为null则抛出IllegalArgumentException
   Preconditions.checkNotNull(user); //user为null则抛出NullPointerException
   ```
2. Optional

   使用Optional表示可能为null的T类型引用，能够显著地降低代码抛出NullPointerException的可能。其中其of方法和fromNullable，前者传入的值不能为空，后者则可以传入null值，表示引用缺失。提供了isPresent()方法判断是否引用缺失，在调用get之前务必先调用isPresent()。但Optional不能乱用，建议仅仅用在对外的API和接口的返回值上。

   ```
   String str = ...;
   Optional<String> optional = Optional.fromNullable(str);
   if(optional.isPresent()){
       String tmp = optional.get();
   }

   str = optional.or("default string");
   str = optional.or(new Supplier<String>() {
       @Override
       public String get() {
           return "default string";
       }
   });
   str = optional.orNull();

   // 对optional中的value做转换操作
   optional.transform(new Function<String, Object>() {
       @Override
       public Object apply(String input) {
           return "transformed string";
       }
   });
   ```
3. Objects && MoreObjects

   是对Java中Object的操作的扩展，后者是对前者的升级,都是null safe的。包括非空判断、相等判断、空值处理、hashcode计算等。

   ```
   User user = new User();
   User user1 = new User();  
   ...  

   Objects.equal(user,user1); //equal判断
   Objects.hashCode(user); //获取对象实例的哈希值
   MoreObjects.toStringHelper(user).add("name","testName").toString(); //辅助编写类的toString方法
   MoreObjects.firstNonNull(user,new User()); //取第一个非空的实例，可以用来在设置第一个元素为null时的默认值。
   ```
4. ComparisonChain

   提供了链式的比较器，执行比较操作直至发现非零的结果, 之后的比较将被忽略。

   ```
   ComparisonChain.start()
               .compare(user.getName(),user1.getName())
               .compare(user.getAge(),user1.getAge())
               .result();
   ```
5. Strings && Joiner && Splitter && CaseFormat

   这几个类都是对字符串的操作，包括分割、连接、格式转换等。

   ```
   Strings.nullToEmpty(str); //如果字符串为null，则转换为空字符串
   Strings.repeat(str,3); //重复字符串成新的字符串

   Joiner joiner = Joiner.on(";").skipNulls(); 
   joiner.join("1", null, "2", "3","4"); //使用;拼接字符串

   Splitter.on(';')
           .trimResults()
           .omitEmptyStrings()
           .split("1;2;3;4"); //分隔字符串

   CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "user_name"); // 将字符串从low underscore命名格式转换为low camel命名。
   ```

   其中的命名格式转换还支持LOWER\_HYPHEN、UPPER\_CAMEL以及UPPER\_UNDERSCORE。
6. ImmutableList && Multiset && Multimap

   这些是Guava实现的新的集合类型。

   ImmutableList是不可变集合（保证线程绝对安全）的一种。除此之外，还有ImmutablesSet、ImmutableMap，用法都类似。

   ```
   ImmutableList<String> list = ImmutableList.of("1","2","3");
   ```

   Multiset可以多次添加相等的元素,主要统计给定元素的个数。

   ```
   Multiset<String> set = HashMultiset.create();
   set.add("1");
   set.add("1");
   System.out.println(set.count("1"));
   ```

   Multimap是一个key映射多值的Map，类似于`Map<K, List<V>>、Map<K, Set<V>>`。主要是使用其两个子类：ListMultimap和SetMultimap，前者允许重复值，后者则不允许。

   ```
   Multimap<String,String> multimap = ArrayListMultimap.create();
   multimap.put("test","1");
   multimap.put("test","2");
   multimap.get("test"); //["1","2"];
   ```
7. Lists && Maps

   JDK默认提供了Collections作为集合工具类。Guava针对其没有提供的集合工具操作做了扩展和实现。Lists、Maps是List和Map的工具类。最大的一个特性是提供的静态工厂方法相比先创建ArrayList再添加元素或者设置参数的初始化方式要简单优雅很多。

   ```
   Lists.newArrayList("1", "2", "3");
   Lists.newArrayListWithCapacity(100);
   ```
8. ListenableFuture

   ListenableFuture继承了JDK concurrent包下的Future接口，可以大大简化并发逻辑的编写。可以注册回调方法，在运算（多线程执行）完成的时候进行调用, 或者在运算（多线程执行）完成后立即执行。

   ```
   ListeningExecutorService service  = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
   ListenableFuture future = service.submit(new Callable<String>() {
         @Override
         public String call() throws Exception {
             return "result";
         }
   });
   Futures.addCallback(future, new FutureCallback() {
         @Override
         public void onSuccess(Object result) {
             ...
         }
       
         @Override
         public void onFailure(Throwable t) {
           ...
         }
   });
   ```

   还支持链式操作：

   ```
   Futures.transform(future, new AsyncFunction() {
        @Override
        public ListenableFuture apply(Object input) throws Exception {
            return ...;
        }
   }, new Executor() {
        @Override
        public void execute(Runnable command) {
            
        }
   });
   ```
9. EventBus

   Guava实现的事件总线机制，为了替代通用型的发布-订阅模型，不适用于进程间通信。

   EventBus定义了事件生产者和事件监听者的角色，类似于生产者和消费者。

   * 事件生产者：管理和追踪监听者以及向监听者分发事件。
   * 事件监听者：注册到总线上，按照Event监听。只要以ChangeEvent为唯一参数创建方法，并用Subscribe注解标记即可实现一个监听者。register到EventBus上即可开始监听Event。

   ```
   class EventBusListener {
       @Subscribe
       public void recordCustomerChange(ChangeEvent e) {
           System.out.println(e.getSource());
       }
   }

   EventBus eventBus = new EventBus(); //还可以使用AsyncEventBus异步发送event
   eventBus.register(new EventBusListener());

   eventBus.post(new ChangeEvent("test event"));
   ```
10. RateLimiter

    使用令牌桶的速率限制器，经常用于限制对一些物理资源或者逻辑资源的访问速率。与JDK中的Semaphore相比，Semaphore 限制了并发访问的数量而不是使用速率。

    ```
    final RateLimiter rateLimiter = RateLimiter.create(2.0); //桶容量为2且每秒新增2个令牌

    Executor executo = Executors.newFixedThreadPool(10);

    for (Runnable task : tasks) {
            rateLimiter.acquire(); // 也许需要等待直到有令牌可用
            executor.execute(task);
        }
    }

    ```

    需要注意的是RateLimiter并不提供公平性的保证，没有先来先得的概念。此外，其请求的许可数不会影响到请求本身的限制，但会影响下一次请求的限制（高开销的任务下一个请求会经历额外的限制，从而来偿付高开销任务）。如：acquire(1) 和acquire(1000)的限制效果一样，但后者的下一次请求会受到额外限制。

此外，Guava还提供了散列、函数式风格、IO、区间、数学运算、Service框架、BloomFilter 等等许多非常有用的工具类，都大大提高了开发效率和代码质量。其中，Guava的Optional、Objects这些由于用的非常广泛，Java8都吸收并做了类似的实现。

## 7.4.3 Joda Time

JDK自带的Date、Calendar类使用起来非常麻烦，并且日期与字符串之间的转换很慢且非线程安全。Joda Time就是为了解决这些问题而创造的日期时间库，使用起来非常简单方便。和之前Apache Commons提供的DateUtils相比，如果想继续使用Java日期，可以选择DateUtils；如果想彻底改变的话就可以使用Joda Time。

1. 初始化时间

   ```
   DateTime dateTime=new DateTime(2017, 6, 21, 18, 00,0); //2017.06.21 18:00:00
   ```
2. 输出格式化字符串

   ```
   dateTime.toString("yyyy-MM-dd");
   ```
3. 解析时间字符串

   ```
   DateTimeFormatter format = DateTimeFormat .forPattern("yyyy-MM-dd");         
   DateTime dateTime = DateTime.parse("2017-06-21", format);
   ```
4. 时间计算

   ```
   dateTime.plusDays(1) // 增加天      
              .plusYears(1)// 增加年      
              .plusMonths(1)// 增加月      
              .plusWeeks(1)// 增加星期      
              .minusMillis(1)// 减分钟      
              .minusHours(1)// 减小时      
              .minusSeconds(1);// 减秒数 

   DateTime.Property month = dateTime.monthOfYear();      
   month.isLeap(); //判断是否是闰月            
              
   ```
5. 与Java Date转换

   ```
   dateTime = new DateTime(new Date());
   dateTime = new DateTime(Calendar.getInstance());
   Date date = dateTime.toDate();
   ```

## 7.4.4 FastJson

FastJson是阿里巴巴开源的JSON处理器，官方测试称性能超过MappingJackson。使用起来比较简单方便。

1. 序列化

   ```
   User user = new User();
   user.setName("testUser");
   user.setGender("M");
   Strign userJson = JSON.toJSONString(user);
   ```
2. 反序列化

   ```
   user = JSON.parseObject(str,User.class);
   JSONObject jo = JSON.parseObject("{\"name\":\"test\"}");
   ```
3. 构造JSONObject以及取值

   ```
   JSONObject jo = new JSONObject();
   jo.put("name","test");
   jo.getString("name");
   jo.getString("nickName"); //返回为null, 不会抛异常
   ```
4. 属性名称转化

   很多时候会遇到JSON字符串中的属性和Java Bean中的属性名称不一致的情况。这时候如果直接调用，则会出错，需要做名称转换，可以使用@JSONField注解配置别名：

   ```
   @JSONField(name = "nick")
   private String nickName;
   ```

   此外，FastJson默认提供了JSON属性Low Underscore到Java Bean字段Low Camel命名的转化。如果想要实现序列化的时候到Low Camel的转换除了可以使用@JSONField，还可以使用SerializeConfig, 设置其PropertyNamingStrategy。同样的，ParseConfig也能设置此策略。

   ```
   SerializeConfig config = new SerializeConfig();
   config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
   String str = JSON.toJSONString(user, config);
   System.out.println(str); // {\"nick_name\":\"testNick\"}
   ```

   PropertyNamingStrategy还支持KebabCase（短横线连接单词）、PascalCase（大写字母开头）以及CamelCase（驼峰）。
5. JSONPath

   FastJson1.2.0之后提供了JSONPath，方便取值，类似于XPath。主要是为了简化取值逻辑，方便嵌套取值、过滤取值、获取集合长度等。

   ```
   String jsonStr = "{\"name\":\"testName\",\"interests\":[\"music\",\"basketball\"]," +
               "\"notes\":[{\"title\":\"note1\",\"contentLength\":200},{\"title\":\"note2\",\"contentLength\":100}]}";
   JSONObject jsonObject1 = JSON.parseObject(jsonStr);
   System.out.println(JSONPath.eval(jsonObject1, "$.interests.size()")); //集合长度
   System.out.println(JSONPath.eval(jsonObject1, "$.interests[0]")); //集合取值
   System.out.println(JSONPath.eval(jsonObject1, "$.notes[contentLength > 100].title")); //集合过滤取值
   System.out.println(JSONPath.eval(jsonObject1, "$.notes['title']")); //只取某一个属性的值
   ```

使用FastJson时需要注意FastJson在序列化和反序列化默认是开启ASM的（安卓下不会开启）。可以通过下面的代码关闭：

```
SerializeConfig.getGlobalInstance().setAsmEnable(false); // 序列化的时候关闭ASM  
ParserConfig.getGlobalInstance().setAsmEnable(false); // 反序列化的时候关闭ASM 
```

## 7.4.5 Orika

Orika是一个快速、高效的Java Bean映射框架，主要用于在VO、PO等各种Bean之间复制属性，并且是深复制。相比起7.4.1中提到的BeanUtils使用反射，Orika是使用代码生成进行复制的。因此其性能好于BeanUtils和Dozer（使用反射，对反射信息做了缓存）。

```
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();

User user = new User();
user.setName("test");
User user1 = mapper.map(user, User.class);
```

不同类之间复制，如果属性名不一致，可以通过自定义映射来复制，属性名相同的直接可以复制。

```
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(User.class, TestUser.class)
      .field("name", "testName")
      .byDefault()
      .register();

MapperFacade mapper = mapperFactory.getMapperFacade();

User user = new User();
user.setName("test");
TestUser testUser = mapper.map(user, TestUser.class);
```

### 7.4.6 MapDB

MapDB将Java中常用的Maps, Sets, Lists, Queues等其他集合做了JVM堆外（堆外内存、磁盘）的存储实现。很多时候被用作多级缓存。如下：

```
DB db = DBMaker.memoryDB().make();

HTreeMap diskCache = db.hashMap("testCache")
      .expireStoreSize(10 * 1024)
      .expireMaxSize(1000)
      .expireAfterCreate(10, TimeUnit.SECONDS)
      .createOrOpen();

HTreeMap cache = db.hashMap("testCache")
      .expireMaxSize(100)
      .expireOverflow(diskCache)
      .createOrOpen();
```

需要注意的是，最新的MapDB使用的是Kotlin语言实现其主要逻辑。

### 7.4.7 使用Hystrix做熔断

除了HystrixBadRequestException异常之外，所有从run()方法抛出的异常都算作失败，并触发降级getFallback()和断路器逻辑。 HystrixBadRequestException用在非法参数或非系统故障异常等不应触发回退逻辑的场景。

请求缓存可以让(CommandKey/CommandGroup)相同的情况下,直接共享结果，降低依赖调用次数，在高并发和CacheKey碰撞率高场景下可以提升性能.

```
HystrixRequestContext context = HystrixRequestContext.initializeContext();  
```

Servlet容器中，可以直接实用Filter机制Hystrix请求上下文

信号量隔离:SEMAPHORE 隔离本地代码或可快速返回远程调用(如memcached,redis)可以直接使用信号量隔离,降低线程隔离开销.

使用hystrix-javanica的Java注解
