之前有一篇 Java 的参数检查与断言 介绍了在 Java 中如何应用 Guava 的 Preconditions 来进行参数检查与状态断言,原本是可以心无旁骛,专心的用它就行了,可是刚刚因琢磨我们使用 JdbcTemplate 存在的一大 Bug,阅读 Spring 的源代码时发现 spring-util 也提供了一个类似于 Gruva Preconditions 的工具类 -- Assert,它是自 Spring 1.1.2 开始就静静的躺在那儿了。
所以现在要检查参数或状态断言时反而犯上了选择综合证,虽然内心还是偏向于 Guava Preconditions,但总之不那么坚决了,也不知到底是谁在重新发明着轮子。可以说 Spring Assert 与 Guava Preconditions 的功能基本一致,也是针对入口参数或中间运行结果的检查分别抛出
IllegalArgumentException和IllegalStateException. 下面一张图来了解它的所有方法。 Read MoreJava 的可变参数(Varargs) 方法让我们调用起来很方便,不需要总是去构造一个数组来传递不定数量的参数,而且还可以作为方法的一个可选参数,如
void foo(int id, String...name) {
String yourName = name.length == 0 ? "Anonymous" : name[0];
......
}想要告诉名字就调用
foo(1, "Yanbin"), 不想的话就用foo(1).但我们在使用 Java Varargs 时,当变参类型定义为
Object...objects时就要当心了,因为 Object 类型的包容性原因一不小心就可以掉到坑里去了,例如下面的方法void foo(Object...objects) {
Arrays.stream(objects).forEach(System.out::println());
}当引用类型是
Object[]时调用没问题,下面代码调用可以得到预期的结果 Read More- 最近转战到 Amazon 的云服务 AWS 上,考虑到在使用它的 Lambda 服务时 Python 应用有比较可观的启动速度,与之相比而言,Java 总是慢热型,还是一个内存大户。所以有想法 Lambda 函数用 Python 来写,来增强响应速度,而内部的应用仍然采用 Java, 于是就有了 Java 与 Python 的数据交换格式。使用 Kafka 的时候是用的 Apache Avro, 因此继续考察它。
注意,本文的内容会有很大部份与前一篇 Apache Avro 序列化与反序列化 (Java 实现) 雷同,不过再经一次的了应用,了解更深了。
在不同类型语言间进行数据交换,很容易会想到用 JSON 格式,那我们为什么还要用 Apache Avro 呢?通过接下来的内容,我们可以看到以下几点:- Apache Avro 序列化的格式也是 JSON 的,Java 的 Avro 库依赖于 Jackson 库
- 序列化数据库本身带有 Schema 定义的,方便于反序列化,特别是对于 Java 代码; 而 JavaScript 会表示多此一举
- 自动支持序列化数据的压缩,在官方提供的库中,Java 可支持
deflate,snappy,bzip2, 和xz. 其他语言中可能少些,如 Python 只支持deflate, 和snappy, 应该可扩充。序列化数据中 Schema 部分不被压缩 - 天然支持序列化对象列表,这样在序列化数据中只需要一份 Schema,类似于数据库表 Schema 加上多记录行的表示方式。只用 Apache Avro 传输小对象的话,数据量比 JSON 事 JDK 序列化的数据要大。
Apache Avro 官方提供有 C, C++, C#, Java, PHP, Python 和 Ruby 的支持库,可在网上找到其他语种的类库,如 NodeJS, Go 的,等等。 Read More - 实际中可能有这样的应用场景,得到一个记录不需要立即去处理它,而是等累积到一定数量时再批量处理它们。我们可以用一个计数器,来一个加一个,量大时一块处理,然后又重零开始计数。如果记录的来源单一还好办,要是有多个数据源来提供记录就会有多线程环境下数据丢失的问题。
这里我编写了一个最简单的任务批处理的队列,构造了告诉它批处理数量,消费者,然后就只管往队列里添加记录,队列在满足条件时自动进行批处理。因为内部使用的是BlockingQuque来存储记录,所以多线程往里同时添加记录也没关系,最后的未达到batchSize, 的那些记录可主动调用completeAll()函数或在达到 timeout 后来触发批处理,并且结束队列内的循环线程。
注意: 多线程环境下往一个无线程保护的集合或结构中,如 ArrayList, LinkedList, HashMap, StringBuilder 中添加记录非常容易造成数据的丢失,而往有线程保护的目的地写东西就安全了,如 Vector, Hashtable, StringBuffer, BlockingQueue。当然性能上要付出一点代价,不过对于使用了可重入锁(ReentrantLock), 而非同步锁(synchronized) 的数据结构还是可以放心使用的。
下面是 BatchQueue 的简单实现 Read More - 在 Java 中对于下面最简单的泛型类
class A<T> {
设想我们使用时
public void foo() {
//如何在此处获得运行时 T 的具体类型呢?
}
}new A<String>().foo();
是否能在foo()方法中获得当前的类型是 String 呢?答案是否定的,不能。在foo()方法中 this 引用给不出类型信息,this.getClass()就更不可能了,因为 Java 的泛型不等同于 C++ 的模板类,this.getClass()实例例是被所有的不同具体类型的 A 实例(new A<String>(), new A<Integer>() 等) 共享的,所以在字节码中类型会被擦除到上限。
我们可以在 IDE 的调试时看到这个泛型类的签名
Read More
在上一篇中尝试了 使用 Javassist 运行时生成泛型子类,这里要用另一个更方便的字节码增加组件 Byte Buddy 来实现类似的功能, 但代码上要直白一些。就是运用 Byte Buddy 在运行时生成一个类的子类,带泛型的,给类加上一个注解,可生成类文件或 Class 实例,不过这里更进一步,实现的方法是带参数的。
用 Byte Buddy 操作起来更简单,根本不需要接触任何字节码相关的,诸如常量池等概念。与 Javassist 相比,Byte Buddy 更为先进的是能生成的类文件都是可加载运行的,不像 Javassist 生成的类文件反编译出来是看起来是正常的,但一加载执行却不那回事。
本例所使用的 Byte Buddy 的版本是当前最新的 1.6.7,在 Maven 项目中用下面的方式引入依赖<dependency>
下面是几个需要在本例中用到的类定义 Read More
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.6.7</version>
</dependency>越是复杂的项目希望使用者能愉快的编码的话,可能就要使用到字节码增强工具来暗地里做些手脚。 这方面的工具有 JDK 的 Instrumentation, ASM, BCEL, CGLib, i Javassist, 还有 Byte Buddy. Javassist 和 Byte Buddy 更贴近我们编码中的概念,使用起来也简单, 而其他几个工具需要我们更多的了解字节码指令,以及常量池等概念。所以我着重去了解怎么运用 Javassist 和 Byte Buddy 来动态修改来生成类文件。
所以本文是系列中的第一篇,旨在以一个 Javassist 的例子来了解它的基本使用方法。本例中在运行时动态生成一个类的子类,并且是泛型的,实现了一个方法,给类加上了一个注解,最终生成一个类文件。总之尽可能的让这个例子具有代表性,同时又需控制它的复杂性。最后通过加载类文件的方式来验证前面生成的类是否是正确的,也可以直接反编译生成的类文件来查看源代码,不过实际操作中我们可能会被反编译出来的源代码欺骗。
本例所使用的 Javassist 的版本是 3.21.0-GA, 是在一个 Maven 项目中测试的,所以 Maven 的依赖是<dependency>
Read More
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
Java 8 之前如何重复使用注解
在 Java 8 之前我们不能在一个类型重复使用同一个注解,例如 Spring 的注解@PropertySource不能下面那样来引入多个属性文件@PropertySource("classpath:config.properties")
上面的代码无法在 Java 7 下通过编译,错误是: Duplicate annotation
@PropertySource("file:application.properties")
public class MainApp {}
于是我们在 Java 8 之前想到了一个方案来规避 Duplicate Annotation 的错误: 即声明一个新的 Annotation 来包裹@PropertySource, 如@PropertySources1@Retention(RetentionPolicy.RUNTIME) 2public @interface PropertySources { 3 PropertySource[] value(); 4}
然后使用时两个注解齐上阵 Read More
最初接触 Mockito 还思考并尝试过如何用它来 mock 返回值为 void 的方法,然而 Google 查找到的一般都会说用doThrow()的办法doThrow(new RuntimeException()).when(mockObject).methodWithVoidReturn();
因为无法使用常规的when(mockObject.foo()).thenReturn(...)的方法。
当时我就纳闷,为何我想 mock 一个返回值为 void 的方法,却是在模拟抛出一个异常,现在想来如果一个返回值为 void 的方法,为何要去 mock 这个方法呢?
回想一个我们要 mock 一个方法的意图是什么:- 在特定输入参数的情况下期待需要的输出结果(返回值)
- 在方法抛出某种类型异常调用者作出的反应
对于 void 返回值的方法,如果要验证有没有被调用过几次可以在事后用verify()方法去断言。所以基本上对于 void 返回值的方法一般可不用去 mock 它,只需用 verify() 去验证,或者就是像前面一样模拟出现异常时的情况。
所以本文并不像是去直接回答标题所示的问题: Mockito 如何 mock 返回值为 void 的方法,而是如何应对 mock 对象的 void 方法 Read More
Mockito 可以帮助我们创建 Mock 对象,mock 被调用的方法,断言调用次数,在方法参数不易确定的情况下还能帮我们捕获参数。下面是我们第一个问题:为什么要捕获调用参数
在被 mocker 方法调用参数明确的情况下可无需捕获参数,例如,下面的情景:1@Test 2public void dontCaptureArgument() { 3 UserDao userDao = Mockito.mock(UserDao.class); 4 UserService userService = new UserService(userDao); 5 6 User user = new User(1, "Yanbin"); 7 userService.saveUser(user); //假如它的实现是 userDao.save(user) 8 9 verify(userDao, times(1)).save(user); //断言了 userDao.save(user) 操作的还是 user 对象 10}
如果 UserService 的 save(user) 最终操作的不是同一个对象,它的实现稍加变化如下 Read More