在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集合框架的操作。
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的操作,主要是BeanUtils这个类。
BeanUtils将property分成simple(简单类型:String、int)、indexed(索引类型:数组、ArrayLsit)以及Maped(Map类型)三种类型,可以直接get和set Java Bean中的一个属性的值。
这里需要注意的是,此类其实最终是调用的BeanUtilsBean,但是BeanUtilsBean2继承BeanUtilsBean并做了升级,提供了任何类型到字符串的转换。因此,使用的时候建议直接使用BeanUtilsBean2。
Copy 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的功能。
Copy 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之间的转换。
Copy 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。
Base64
Copy Base64.encodeBase64String(byte[] binaryData);
Base64.decodeBase64(String base64String);
MD5
Copy DigestUtils.md5Hex(final byte[] data);
Sha1
Copy DigestUtils.sha1Hex(final byte[] data);
URL
Copy URLCodec.encode(final String str);
URLCodec.decode(final String str);
Collections
Collections为JDK的集合类提供了更为丰富的工具类、接口以及实现。最新的版本4,包名修改为:org.apache.commons.collections4。
CollectionUtils: 提供了一些方面的操作方法,如判断集合非空、对集合的并集、交集、差集的操作。
Copy List<String> list = getList();
List<String> list2 = getList2();
if(CollectionUtils.isNotEmpty(list)){ //判断非空
CollectionUtils.union(list,list2)//并集
CollectionUtils.subtract(list,list2)//差集
CollectionUtils.retainAll(list,list2)//交集
}
提供了一些新的集合类型。
Copy //得到集合里按顺序存放的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的扩展,操作文件非常方便。
IOUtils:对IO stream操作的封装。
Copy InputStream is = new URL( "http://baidu.cim" ).openStream();
try{
IOUtils.toString(is, "utf-8");
IOUtils.readLines(is, "utf-8");
}finally {
IOUtils.closeQuietly(is);
}
FileUtils:对文件操作的封装。
Copy 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"); //字符串写入文件
FileSystemUtils:对文件系统的操作封装。
Copy FileSystemUtils.freeSpaceKb("/data"); //查看相应路径的剩余空间
Lang
一些公共的工具集合,涵盖了字符串操作、字符操作、 JVM交互操作、归类、异常和位域校验等等。现在最新的版本为3, 包名改成了org.apache.commons.lang3。
StringUtils && StringEscapeUtils
StringUtils继承自Object,是null safe的,即遇到null的String对象,会把它处理掉不会抛出异常;StringEscapeUtils是对字符串做转义的工具类,包括HTML、JS、XML等等。
Copy 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方法使用正则,此方法直接使用了完整的字符串来做匹配,且会丢弃空字符串。
ArrayUtils
ArrayUtils是一个对数组进行特殊处理的类。ArrayUtils扩展了JDK中的Arrays,提供了更多的功能。
Copy String[] strs = new String[]{"1", "4", "2"};
ArrayUtils.nullToEmpty(strs); //如果数组为null,则返回长度为0的数组
ArrayUtils.reverse(strs); //反转数组
ArrayUtils.addAll(strs,"3"); //数组添加元素
需要注意:使用addAll添加元素,是需要数组拷贝的,慎用。
RandomUtils && RandomStringUtils
提供了生成随机数、字符串的操作封装。
Copy RandomUtils.nextInt(0,10); //随机一个整数,从0到10,不包括10。
RandomStringUtils.random(3); //随机三个字母的字符串出来。
这里需要注意的是这俩类都使用了Random这个类,但其是伪随机的,在要求严格的环境下,尽量不要使用这俩类,去使用SecureRandom。
NumberUtils
为数字提供了一些操作封装, 是null safe的。
Copy String numberStr = "123";
long n = NumberUtils.toLong(numberStr); //将字符串转化为long, 如果字符串格式不对或者为Null,则返回0,并不会抛出异常
long max = NumberUtils.max(new Long[]{1L,5L,10L}); //计算数组最大值
DateUtils && DateFormatUtils
是对日期时间操作的封装。
DateUtils提供了很多日期计算。
Copy DateUtils.addDays(new Date(),3); //计算三天后的时间
DateUtils.addHours(new Date(),3); //三个小时候的时间
DateUtils.truncate(new Date(), Calendar.HOUR); //截断日期到小时,后面的分、秒都为0
DateFormatUtils提供了Date到字符串表示的操作。
Copy DateFormatUtils.format(new Date(),"yyyyMMdd"); //以yyyyMMdd的格式输出日期
MethodUtils
通过此工具类可以调用类的方法,实现原理基于反射。
Copy MethodUtils.invokeStaticMethod(StringUtils.class,"isNotBlank","test"); //调用静态方法
MethodUtils.invokeMethod(user,"getName"); //调动实例方法
StopWatch
StopWatch是一个秒表类。
Copy StopWatch stopWatch = new StopWatch();
stopWatch.start(); //开始计时
stopWatch.split(); //截断每一次的分段计时
stopWatch.getSplitTime(); //获取分段计时
stopWatch.suspend(); //暂停秒表
stopWatch.resume(); //恢复计时
stopWatch.stop(); //停止秒表
stopWatch.getTime(); //获得总共计时
ImmutablePair && ImmutableTriple
这俩类都是不可变的,经常是用在返回值是两个或者三个的场景下,是对多返回值的通用封装。
Copy 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。
连接池
HttpClient提供了HTTP连接池的支持,连接池依赖HTTP 1.1的keep alive机制,对HTTP 1.0需要做兼容配置。此外,也支持HTTPS请求。需要注意的是使用连接池能够减少频繁创建、销毁连接的消耗提高性能,但是由于连接池是有锁的,为了提升并发性能,最好对于每一个服务都创建一个连接池。
Copy 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());
HttpRequestBase
HttpClient支持HTTP的各种方法, 都是HttpRequestBase的子类。
以上类都有一个参数为URL字符串的构造方法。此外,对post、put这种需要传递数据的方法,HttpClient是使用HttpEntity来实现的。常用的几个HttpEntity如下:
UrlEncodedFormEntity:最常见的表单提交,Content-type为application/x-www-form-urlencoded。
MultipartFormEntity: 提交文件时常用的方式,Content-type为multipart/form-data
StringEntiry:自包含的Entity, 传递JSON数据时可以使用。
Copy StringEntity entity = new StringEntity("{\"name\";\"test\"}", "UTF-8");
entity.setContentType("application/json")
HttpPost method = new HttpPost(url);
method.setEntity(entity);
Cookie
Cookie的支持需要依赖CookieStore。HttpClientUtil内置了BasicCookieStore。
Copy CookieStore cookieStore = new BasicCookieStore()
cookieStore.addCookie(Cookie cookie);
List<Cookie> cookies = cookieStore.getCookies();
HttpClientBuilder
HttpClient的创建依赖于HttpClientBuilder, 能够对HttpClient的Cookie以及Connect Timeout、Socket Timout、Keep Alive的策略进行配置。
Copy 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();
使用
Copy 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类似。不同之处如下:
连接池管理器多了IO线程的配置且连接的Registry也不同。
Copy 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);
使用CloseableHttpAsyncClient。
Copy RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(3000)
.setSocketTimeout(1000).build();
CloseableHttpAsyncClient asyncClient = HttpAsyncClients.custom().setConnectionManager(conMgr)
.setDefaultCookieStore(new BasicCookieStore())
.setDefaultRequestConfig(requestConfig)
.build();
使用时,需要先启动Client,并且提供了回调使用方式。
Copy 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讲过。
Preconditions
对条件做前置判断,经常用在方法的最前面,来对参数进行校验,不符合则抛出异常。
Copy Preconditions.checkArgument(user != null,"user null error"); //user为null则抛出IllegalArgumentException
Preconditions.checkNotNull(user); //user为null则抛出NullPointerException
Optional
使用Optional表示可能为null的T类型引用,能够显著地降低代码抛出NullPointerException的可能。其中其of方法和fromNullable,前者传入的值不能为空,后者则可以传入null值,表示引用缺失。提供了isPresent()方法判断是否引用缺失,在调用get之前务必先调用isPresent()。但Optional不能乱用,建议仅仅用在对外的API和接口的返回值上。
Copy 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";
}
});
Objects && MoreObjects
是对Java中Object的操作的扩展,后者是对前者的升级,都是null safe的。包括非空判断、相等判断、空值处理、hashcode计算等。
Copy 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时的默认值。
ComparisonChain
提供了链式的比较器,执行比较操作直至发现非零的结果, 之后的比较将被忽略。
Copy ComparisonChain.start()
.compare(user.getName(),user1.getName())
.compare(user.getAge(),user1.getAge())
.result();
Strings && Joiner && Splitter && CaseFormat
这几个类都是对字符串的操作,包括分割、连接、格式转换等。
Copy 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。
ImmutableList && Multiset && Multimap
这些是Guava实现的新的集合类型。
ImmutableList是不可变集合(保证线程绝对安全)的一种。除此之外,还有ImmutablesSet、ImmutableMap,用法都类似。
Copy ImmutableList<String> list = ImmutableList.of("1","2","3");
Multiset可以多次添加相等的元素,主要统计给定元素的个数。
Copy 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,前者允许重复值,后者则不允许。
Copy Multimap<String,String> multimap = ArrayListMultimap.create();
multimap.put("test","1");
multimap.put("test","2");
multimap.get("test"); //["1","2"];
Lists && Maps
JDK默认提供了Collections作为集合工具类。Guava针对其没有提供的集合工具操作做了扩展和实现。Lists、Maps是List和Map的工具类。最大的一个特性是提供的静态工厂方法相比先创建ArrayList再添加元素或者设置参数的初始化方式要简单优雅很多。
Copy Lists.newArrayList("1", "2", "3");
Lists.newArrayListWithCapacity(100);
ListenableFuture
ListenableFuture继承了JDK concurrent包下的Future接口,可以大大简化并发逻辑的编写。可以注册回调方法,在运算(多线程执行)完成的时候进行调用, 或者在运算(多线程执行)完成后立即执行。
Copy 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) {
...
}
});
还支持链式操作:
Copy Futures.transform(future, new AsyncFunction() {
@Override
public ListenableFuture apply(Object input) throws Exception {
return ...;
}
}, new Executor() {
@Override
public void execute(Runnable command) {
}
});
EventBus
Guava实现的事件总线机制,为了替代通用型的发布-订阅模型,不适用于进程间通信。
EventBus定义了事件生产者和事件监听者的角色,类似于生产者和消费者。
事件生产者:管理和追踪监听者以及向监听者分发事件。
事件监听者:注册到总线上,按照Event监听。只要以ChangeEvent为唯一参数创建方法,并用Subscribe注解标记即可实现一个监听者。register到EventBus上即可开始监听Event。
Copy 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"));
RateLimiter
使用令牌桶的速率限制器,经常用于限制对一些物理资源或者逻辑资源的访问速率。与JDK中的Semaphore相比,Semaphore 限制了并发访问的数量而不是使用速率。
Copy 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。
初始化时间
Copy DateTime dateTime=new DateTime(2017, 6, 21, 18, 00,0); //2017.06.21 18:00:00
输出格式化字符串
Copy dateTime.toString("yyyy-MM-dd");
解析时间字符串
Copy DateTimeFormatter format = DateTimeFormat .forPattern("yyyy-MM-dd");
DateTime dateTime = DateTime.parse("2017-06-21", format);
时间计算
Copy dateTime.plusDays(1) // 增加天
.plusYears(1)// 增加年
.plusMonths(1)// 增加月
.plusWeeks(1)// 增加星期
.minusMillis(1)// 减分钟
.minusHours(1)// 减小时
.minusSeconds(1);// 减秒数
DateTime.Property month = dateTime.monthOfYear();
month.isLeap(); //判断是否是闰月
与Java Date转换
Copy dateTime = new DateTime(new Date());
dateTime = new DateTime(Calendar.getInstance());
Date date = dateTime.toDate();
7.4.4 FastJson
FastJson是阿里巴巴开源的JSON处理器,官方测试称性能超过MappingJackson。使用起来比较简单方便。
序列化
Copy User user = new User();
user.setName("testUser");
user.setGender("M");
Strign userJson = JSON.toJSONString(user);
反序列化
Copy user = JSON.parseObject(str,User.class);
JSONObject jo = JSON.parseObject("{\"name\":\"test\"}");
构造JSONObject以及取值
Copy JSONObject jo = new JSONObject();
jo.put("name","test");
jo.getString("name");
jo.getString("nickName"); //返回为null, 不会抛异常
属性名称转化
很多时候会遇到JSON字符串中的属性和Java Bean中的属性名称不一致的情况。这时候如果直接调用,则会出错,需要做名称转换,可以使用@JSONField注解配置别名:
Copy @JSONField(name = "nick")
private String nickName;
此外,FastJson默认提供了JSON属性Low Underscore到Java Bean字段Low Camel命名的转化。如果想要实现序列化的时候到Low Camel的转换除了可以使用@JSONField,还可以使用SerializeConfig, 设置其PropertyNamingStrategy。同样的,ParseConfig也能设置此策略。
Copy 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(驼峰)。
JSONPath
FastJson1.2.0之后提供了JSONPath,方便取值,类似于XPath。主要是为了简化取值逻辑,方便嵌套取值、过滤取值、获取集合长度等。
Copy 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的(安卓下不会开启)。可以通过下面的代码关闭:
Copy 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(使用反射,对反射信息做了缓存)。
Copy MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
User user = new User();
user.setName("test");
User user1 = mapper.map(user, User.class);
不同类之间复制,如果属性名不一致,可以通过自定义映射来复制,属性名相同的直接可以复制。
Copy 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堆外(堆外内存、磁盘)的存储实现。很多时候被用作多级缓存。如下:
Copy 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碰撞率高场景下可以提升性能.
Copy HystrixRequestContext context = HystrixRequestContext.initializeContext();
Servlet容器中,可以直接实用Filter机制Hystrix请求上下文
信号量隔离:SEMAPHORE 隔离本地代码或可快速返回远程调用(如memcached,redis)可以直接使用信号量隔离,降低线程隔离开销.
使用hystrix-javanica的Java注解