这样指定:
compileGroovy {
options.compilerArgs << "-Xlint:unchecked"
}
在 gradle 中配置下面内容:
// 下面的配置会让gradle先编译groovy,后编译java,以便在 java 中使用 groovy 类
// ref: https://coderwall.com/p/wuqopq/compile-groovy-before-java-in-gradle-build
// 因为 groovy 插件默认会先编译 java 后编译 groovy,导致 java 找不到 groovy 的类。
sourceSets {
main {
groovy {
// this makes the groovy-compiler compile groovy- as well
// as java-files.
// Needed, because java is normally compiled before groovy.
// Since we are using groovy objects from java, we need it
// the other way round.
srcDirs = ['src/main/groovy', 'src/main/java']
}
java {
srcDirs = [] // don't compile Java code twice
}
}
}
警告如下:
public Token deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
^
返回类型需要从Token到T的未经检查的转换
其中, T是类型变量:
T扩展已在方法 deserialze(DefaultJSONParser,Type,Object)中声明的Object
没有找到解决方法彻底解决,但可以用注解告诉 javac 编译器不要显示 unchecked 警告。
@SuppressWarnings("unchecked")
其实这个接口的定义不需要定义一个泛型 T 吧 (fastjson项目的)?下面这段代码
public interface ObjectDeserializer {
/**
* fastjson invokes this call-back method during deserialization when it encounters a field of the
* specified type.
* In the implementation of this call-back method, you should consider invoking
* {@link JSON#parseObject(String, Type, Feature[])} method to create objects
* for any non-trivial field of the returned object.
*
* @param parser context DefaultJSONParser being deserialized
* @param type The type of the Object to deserialize to
* @param fieldName parent object field name
* @return a deserialized object of the specified type which is a subclass of {@code T}
*/
<T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName);
int getFastMatchToken();
}
不清楚上面这样定义 T 有什么作用。
看groovy的 traits(特征) 相关文档,就能知道了。
首先要能区分两个容易混淆的概念 “fields” 和 “property”。
因此,我们从规则上就应该避免这样用,实在需要这样设计,那么属性对应的字段肯定是改不成 protected 了,直接使用 get、set 方法就好了。如果是 groovy 代码,那么使用 属性 和使用 字段 是没有区别的,都是用 obj.name 的方式就可以了。
如果是在 traits 中使用 property 就更需要注意了,因为实现类中不能重载traits属性的定义,设置值倒是可以的,如下面的代码示例:
trait IntCouple {
int x = 1
int y = 2
int sum() { x+y }
}
class Elem implements IntCouple {
int x = 3
int y = 4
int f() { sum() }
}
def elem = new Elem()
assert elem.f() == 3
结果是失败!因为 elem.f() 等于 7!而不是3,因为 traits 有一个独立的对象,这个对象的 field 并不会被实现类(Elem)所改变。
要想支持在实现类中改变traits的属性,只能在traits的定义中使用get、set方法,而不要直接使用字段,像下面这样:
trait IntCouple {
int x = 1
int y = 2
int sum() { getX()+getY() }
}
就可以了,elem.f() 的值现在就是 7 了。或者用设置值的方式也行,如下:
class Elem implements IntCouple {
int f() {
x = 3
y = 4
sum()
}
}
Groovy 的 traits 功能非常强大,值得单独写一篇文章。
在方法或类上添加 @SuppressWarnings(“unused”) 注解即可。
TODO
下面代码不成立
list*.staff*.status = newStatus
只能展开一次,并且只能在等号的右侧,如下
def staffList = list*.staff
如果 class 定义了 field,定义 get 函数,那么 GString 中的 this.name 会解析为 field 而不会作为property访问,去调用 get 函数。例如:
class TenantUser {
private ObjectProperty<TenantUserStatus> status = new SimpleObjectProperty<>()
TenantUserStatus getStatus() {
return status.value
}
def test() {
"${this.status.name}"
}
}
def user = new TenantUser()
// 编译会报错:找不到 类型 ObjectProperty 的 name 方法
说明,GString 中 ${this.status.name} 解析 this.status 为字段而不是属性了。
解决方法是在GString中使用 get 函数。
比如 JsonSlurper, XMLSlurper 等等。
Slurper 是寂静岭3这款游戏中的怪物,头上就一张嘴,跳起来从头上啜食猎物。因此我们可以把 Slurper 翻译为“吃货”,对,就是吃货,如果翻译为“啜食兽”,又让人要去猜什么是“啜食”,不如“吃货”来得形象。
因此,JsonSlurper 就是 Json吃货,XMLSlurper 就是 XML 吃货。
就是将 map 的 key 对应 对象的属性名,value 赋给该属性。目标是实现将 json slurper 生成的 Groovy Object 转换为指定 class 的对象。
目标逻辑示例代码如下:
def jsonObject = jsonSlurper.parseText "{name:'abc'}"
User user = jsonObjectToUser(jsonObject)
assert user instanceof User
思路:
有帮助的一个方法是 InvokeHelper.setProperties( target, properties )
User user = User.findById('1')
User copyUser = new User()
InvokerHelper.setProperties(copyUser, user.properties)
copyUser.language = 'French'
copyUser.save(flush:true)
另外一个思路是用 强制转换 操作符 “as” ,参考 as 的文档。
下面是一个 as 的使用方法
class Identifiable {
String name
}
class User {
Long id
String name
def asType(Class target) {
if (target == Identifiable) {
return new Identifiable(name: name)
}
throw new ClassCastException("User cannot be coerced into $target")
}
}
def u = new User(name: 'Xavier')
def p = u as Identifiable
assert p instanceof Identifiable
assert !(p instanceof User)
因为 JsonSlurper 解析 json 中的 object 为 Map,所以我们需要为 Map 动态地加上一个函数 asType()。
怎么实现呢?用 traits 吧。traits 可以为一个类动态地添加接口。 用traits使用起来比较麻烦,应该使用 Category 来实现。
下面的traits代码不工作,会抛出异常。
trait MapToClass {
/**
* 支持将 Map 转换为 TenantStaff 类
* @param target 要转换的目标类
*/
def asType(Class target){
switch (target) {
case TenantStaff:
TenantStaff targetObject = new TenantStaff()
// 复制key/value到 targetObject 的属性上
this.entrySet().collect {
targetObject.setProperty(it.key as String, it.value) // <1>
}
return targetObject
case StringProperty:
// 取 StringProperty 变成 json串的object后的 value 属性,因为这个对象的格式类似下面这样:
// {value=老杨 (在线), bean=null, bound=true, name=, valueSafe=老杨 (在线)}
return new SimpleStringProperty(this.value)
}
throw new ClassCastException("Unsupport coercion from Map to class $target")
}
}
<1> 在这里会抛出异常,说无法将 Map cast 成 StringProperty。这个原因是groovy在设置 property 值之前,做了参数的 cast 动作,而我们的参数是一个复杂对象即 StringProperty,它被序列化成 json 的 object,当反序列化的时候,就会被 JsonSlurper 读作 Map 对象,从而引发上面的 cast 异常。下面我们用 category 来实现也是一样会抛异常的,因为都会调用 setProperty() 方法。
换用 Category 来实现还是不行,抛异常。
看来要用 ExpandoMetaClass 才能实现了。
抛异常的地方:
//----------------------------------------------------------------------
// executing the getter method
//----------------------------------------------------------------------
if (method != null) {
if (arguments.length == 1) {
newValue = DefaultTypeTransformation.castToType(
newValue,
method.getParameterTypes()[0].getTheClass()); <1>
arguments[0] = newValue;
} else {
newValue = DefaultTypeTransformation.castToType(
newValue,
method.getParameterTypes()[1].getTheClass());
arguments[1] = newValue;
}
method.doMethodInvoke(object, arguments);
return;
}
private static Object continueCastOnSAM(Object object, Class type) {
...
return InvokerHelper.invokeConstructorOf(type, args); <2>
...
}
<1> 这个地方导致的异常了。
<2> 这个地方最终抛出的异常。
所以还要研究如何给一个 Class 动态添加构造函数才行。
主要是因为属性的类型是 javafx 的 StringProperty 类 class,它有已经有 default constructor。
如果把它换成具体的实现类,比如SimpleStringProperty则会出现另外的异常,即无法cast参数值的异常。
最后,用了一个解决方法,就是为每个特殊的属性编写转换逻辑:
/**
* 支持将 Map 转换为 TenantStaff 类
* @param target 要转换的目标类
*/
static def asType(Map self, Class target) {
switch (target) {
case TenantStaff:
TenantStaff targetObject = new TenantStaff()
// 复制key/value到 targetObject 的属性上
self.entrySet().forEach {
switch (it.key) {
case "elementNameProperty":
targetObject.elementNameProperty.value = it.value.value
break
case "statusProperty":
targetObject.statusProperty.value = it.value.value as TenantUserStatus
break
case "children":
targetObject.children.addAll(it.value as List)
break
default:
targetObject.setProperty(it.key as String, it.value)
}
}
return targetObject
}
throw new ClassCastException("Unsupport coercion from Map to class $target")
}
小结:Groovy 的 动态 setProperty 方法要求属性的类型是可以正常实例化的,并且要设置的值对象必须能直接转换为属性类型,而不能通过额外的其他强制转换方式实现。这也是目前Groovy能力的局限吧。
今天在开发时,需要用 Date.parse(“yyyy-MM-dd HH:mm:ss”, “2020-02-28 12:30:00”) 这个方法,却发现找不到了,编译器报告说 parse() 函数已经过时。
原来是缺少 groovy-dateutil JAR 的依赖,添加依赖即可,如下
implementation 'org.codehaus.groovy:groovy-all:2.5.9'
implementation 'org.codehaus.groovy:groovy-dateutil:2.5.9'
另外 LocalDate 的扩展也可以使用,如下:
org.apache.groovy.datetime.extensions.DateTimeStaticExtensions#parse(java.time.LocalDate, java.lang.CharSequence, java.lang.String)
所以我们要这样写,注意 pattern 参数放到最后面了:
LocalDate.parse("2020-02-28 12:30:00", "yyyy-MM-dd HH:mm:ss")
目前我的理解,Vert.x 就是 Java 中的 Node.js,一切调用都是异步的。
和 node.js 一样,都使用 Reactor Pattern 进行编程。而和 node.js 不同的是,Vert.x 使用了多个 event-loop thread,这样可以用一个 Vert.x 实例运行在多个 CPU 核心上,这种模式叫做 Multi-Reactor Pattern.
这种编程模式,对于程序员最重要的要求是“不能阻塞Event Loop Thread” !例如不能在你的 Handler 中 wait、sleep等。
对于会阻塞一小会儿(不超过几秒)的任务,可以用 executeBlocking()方法来执行。但是更长时间的任务,就必须由应用程序自己提供专门的线程来执行,然后用 event-bus 或者 runOnContext 来和 vert.x 交互了。
这个看起来和 JavaFX 的UI线程模式怎么这么相像
vertx.executeBlocking({ promise ->
// Call some blocking API that takes a significant amount of time to return
def result = someAPI.blockingMethod("hello")
promise.complete(result)
}, { res ->
println("The result is: ${res.result()}")
})
到这里,并没有觉得 Vert.x 的优点,是不是使用线程池来开发也是一样的呢。其实还是有一点细微的差别的,这个细微的差别就是 Vert.x 使用的 Reactor-Pattern,就像 Node.js 那样,处处使用、甚至是强迫程序员使用 Reactor-Pattern,可以将CPU因为等待IO而浪费的时间大大减小,这样就做到了提高服务器使用效率,也就是单台服务器能处理更多的并发请求了。
另外一个好处就是下面的各种“异步协调”,这种模式可以方便地组合异步任务的结果。
就是将多个异步执行的“未来”对象进行各种组合,得到一个新的“未来”对象。
因为 Node.js 已经在世界各大电商、流量大户广泛使用,其中一个原因是由于异步开发模式带来的服务器执行效率,一个原因是 javascript 完成了全栈的开发,开发效率高。
因此想到了 Java 程序员们,Groovy 是 Java 中的 Javascript,那么 Vert.x 就是 Java 中的 Node.js 了,如果 Groovy 语言能在 Java 世界中普及、流行,那么 Vert.x 也有望在 Java 世界中普及。但是因为 Vert.x 不像 Node.js 对于 Javascript 世界那么重要,因为 Node.js 直接打通了前后台开发,Vert.x 并不能渗透到前端开发,再加上 Vert.x 是Node.js 的后来者,Vert.x 最多在服务器端,把异步编程带来的服务器性能发挥到极致。
结论就是 Vert.x 的流行程度会和 Groovy 语言的流行程度成正比,能在服务器端更好地完成“高并发、大流量”的计算任务,短时间内的成功取决于世界级的电商大厂、中型互联网公司的采纳和拥抱,但长期来看,Java世界要拥抱 “异步编程模式”(所谓的 Reactive Programming)就必须仰仗 Vert.x、Spring5+,或者Akka了。
参考资料