# 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注解


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://rowkey-books.gitbook.io/pragmatic-java-engineer/chapter7-java/weapons.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
