groovy实现带参接口脚本可以直接写接口吗

Groovy编程入门攻略
投稿:goldensun
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了Groovy编程入门攻略,Groovy是一种同样使用Java虚拟机的动态语言,需要的朋友可以参考下
当一个Java开发人员加入到Groovy的开发之旅的时候,他/她经常带着Java思想去思考,并逐步地学习Groovy,每次学习一个特性,这会让他慢慢变得更有创造性和写出更符合语言习惯的Groovy代码。这篇文章的目的是引导这些开发人员去学习基本的Groovy编程风格,学习新的操作技巧,新的语言特性,例如闭包等等。这篇文章并不会详细铺开描述,而是给读者一个入门的指引,并让读者在以后的深入学习打好基础。如果你喜欢这篇文章,可以贡献你的一份力量去丰富它。
C / C++ / C# / Java开发者,经常到处使用分号。尽管Groovy支持99%的java语法,有时你只需简单的把java代码粘贴到Groovy程序里,但是却带着一大堆分号。在Groovy里,分号是可选的,你可以省略他们,更常用的用法是删除它们。
返回关键字 (Return) 变得可选
在Groovy的世界里面,方法的程序块结尾可以不写'return'关键字而照样返回值。尤其是对于语句不多的方法和闭包。这样的写法更优美更简洁:
String toString() {return"a server"}
String toString() {"a server"}
但有些情况却不那么优美,例如你使用了变量,并在两行里面出现两次:
def props() {
def m1 = [a:1, b:2]
m2 = m1.findAll { k, v -& v %2==0}
在这个例子里面,或者是在最后一个表达式后面加上一个空行,抑或是显式地加上'return'关键字会令代码更有可读性。
我自己个人习惯,有时候喜欢用return关键字,有时候又不喜欢,这跟个人口味有关系吧。但是,更多时候,例如在闭包里面,我更喜欢不写return关键字。所以即使return关键字是可选的,也不会强制你不能使用它,如果你认为它会打破代码的可读性。
谨慎为上,然而当你使用def关键字定义,而并非用代替具体某一个类型去定义的方法,有时候你会惊奇的发现最后一条表达式会作为返回结果而返回。所以更多时候更推荐使用具体的类型(例如void或类型)作为返回类型。在我们上面的例子中,如果我们忘记了把m2放在最后一行并作为返回值,那么最后的一个表达式将是m2.c = 3,这样会导致数值3作为返回值返回,而不是我们期待的结果。
形如if/else语句,try/cath语句同样可以返回值,因为它们里面都有"最后一个表达式"会被返回。
def foo(n) {
if(n ==1) {
assertfoo(1) =="Roshan"
assertfoo(2) =="Dawrani"
Def 和 类型
当我们讨论def和类型,我经常会发现一些开发人员既用'def'又用类型。但是'def'在这里是多余的。所以,大家要做一个选择,要不用'def', 要不就用类型。
所以不要写出如下的代码:
def String name = "Guillaume"
String name ="Guillaume"
当我们在Groovy里面使用def,真正的类型是Object(所以你可以向用def定义的变量,赋值任何的对象,并且,当一个方法是用def作为返回值的时候,可以以任何对象类型作而返回)。
当一个方法未声明变量类型,你可以使用def,但是这不是必须的,所以可以省略他,所以可以代替下面的语句:
void doSomething(def param1, def param2) { }
void doSomething(param1, param2) { }
但是,就如我们在文章末尾提到的,我们更推荐为方法的参数指定类型。这样可以帮助提高代码的可读性,也可以帮助IDE工具使代码完整,或者利用Groovy的静态类型检查或静态编译功能。
另外一个def多余的地方是定义构造函数,应避免使用:
class MyClass {
def MyClass() {}
应去掉 构造函数前的'def':
classMyClass {
MyClass() {}
默认的Public
默认情况,Groovy会认为类和方法是定义为'public'的。所以你不需要显式的声明public。当需要声明为非public的时候,你需要显式的指定修饰符。
所以避免如下写法:
public class Server {
public String toString() {return "a server"}
推荐使用如下简洁的写法
class Server {
String toString() {"a server"}
你可能会关心包范围内的可访问性问题。事实上,Groovy允许省略public, 是因为这个包范围里面默认情况下是不支持public,但是事实上有另外一个Groovy注释语法去实现可访问性:
class Server {
@Package ScopeCluster cluster
省略圆括号
Groovy允许你在顶级表达式中省略圆括号,例如println语句:
println"Hello"
method a, b
println("Hello")
method(a, b)
当方法的最后一个参数是闭包的时候,例如使用Groovy的'each'迭代机制,你可以把闭包放在括号之外,甚至省略括号:
list.each( { println it } )
list.each(){ println it }
list.each { println it }
通常我们推荐使用上面第三种写法,它显得更自然,因为没有圆括号是多么的多余!
但是Groovy在某些情况并不允许你去掉圆括号。就像我之前说的,顶级表达式可以省略,但是嵌套的方法或者赋值表达式的右边,你却不能省略:
def foo(n) { n }
println foo1// 错误写法
def m = foo1
类,一级公民
在Groovy里面,.class后缀是不需要的,有点像Java的instanceof。
connection.doPost(BASE_URI +"/modify.hqu", params, ResourcesResponse.class)
下面我们使用GString,并使用第一类公民:
connection.doPost("${BASE_URI}/modify.hqu", params, ResourcesResponse)
Getters和Setters
在Groovy的世界里,getters和setters就是我们常说的"属性",Groovy提供一个注释语法捷径给我们去访问或给属性赋值。摒弃java方式的getters/setters,你可以使用注释语法:
resourceGroup.getResourcePrototype().getName() == SERVER_TYPE_NAME
resourceGroup.resourcePrototype.name == SERVER_TYPE_NAME
resourcePrototype.setName("something")
resourcePrototype.name = "something"
当你用Groovy写beans的时候,我们通常叫做POGOs(普通Groovy对象),你不需要自己创建属性,和getters/setters方法,而是留给Groovy编译器来完成。
所以,不再需要这样去写:
class Person {
private String name
String getName() {returnname }
void setName(String name) {this.name = name }
class Person {
String name
就如你看到的,一个简洁的"属性"并不带任何的访问修饰符,会让Groovy编译器为你自动生成一个私有的属性和getter和setter方法。
当你在Java中使用这样一个POGOs对象,getter和setter方法其实是存在的,当然跟正常的写法无异。
虽然编译器可以创建getter/setter方法,但是当你需要在这方法里面增加一些特殊的逻辑或者跟默认的getter/setter方法不一样,你可以自己去定义它,编译器自动会选择你的逻辑代替默认逻辑。
用命名参数和默认构造器初始化beans
有这样一个bean:
class Server {
String name
Cluster cluster
为避免像下面这样的麻烦:
def server =newServer()
server.name ="Obelix"
server.cluster = aCluster
你可以使用命名参数和默认构造器(首先会调用构造器,然后依次调用setter方法):
def server =newServer(name:"Obelix", cluster: aCluster)
在同一个bean里面使用with()语法处理重复操作
默认构造器中,命名参数在创建新的实例时是一件十分有趣的事情。但是当你需要更新一个实例时,你是否需要重复地在变量前重复敲打'server'这个前缀呢?答案是否定的,因为多亏了with()语句,Groovy会自动为你填上:
server.name = application.name
server.status = status
server.sessionCount =3
server.start()
server.stop()
server.with {
name = application.name
status = status
sessionCount =3
equals 和 ==
Java世界里,==就相当于Groovy里面的is()方法,另外Groovy的==就是聪明的equal()方法!
当你需要比较对象的引用的时候,你应该使用Groovy的==,因为他会帮你避开NullPointerException,而你就不需要关心操作符的左侧或右侧是否为null。
不应该这样写:
status !=null&& status.equals(ControlConstants.STATUS_COMPLETED)
status == ControlConstants.STATUS_COMPLETED
GStrings ()
我们经常再JAVA里面使用string和变量连接,并使用大量的双引号,加号,还有\n字符去创建新的一行。而使用插值字符串(在Groovy里面叫做GStrings),可以简化我们的代码写法:
throw new Exception("Unable to convert resource: "+ resource)
throw new Exception("Unable to convert resource: ${resource}")
在花括号里面,你可以放任何的表达式,而不单单是变量。对于简单的变量,或者变量属性,你甚至可以丢掉花括号:
throw new Exception("Unable to convert resource: $resource")
你甚至可以通过闭包注释(${-& resource})对表达式进行延迟计算。当GString变量强制转换为String变量的时候,它会自动计算闭包的值,并通过调用toString()方法作为返回值。例如:
def s1 ="i's value is: ${i}"
def s2 ="i's value is: ${-& i}"
asserts1 =="i's value is: 3"// 预先计算,在创建的时候赋值
asserts2 =="i's value is: 4"// 延迟计算,使用最新的变量值
在Java里面,字符串连接是十分的累赘:
throw new PluginException("Failed to execute command list-applications:"+
" The group with name "+
parameterMap.groupname[0] +
" is not compatible group of type "+
SERVER_TYPE_NAME)
你可以使用 \ 连续字符 (这不等同于多行文本)
throw new PluginException("Failed to execute command list-applications: \
The group with name ${parameterMap.groupname[0]} \
is not compatible group of type ${SERVER_TYPE_NAME}")
或者使用三个双引号实现多行文本:
throw new PluginException("""Failed to execute command list-applications:
The group with name ${parameterMap.groupname[0]}
is not compatible group of type ${SERVER_TYPE_NAME)}""")
另外,你也可以使用.stripIndent()函数实现删除多行文本里面的左侧多余空格。
同时请注意Groovy里面单引号和双引号的区别:单引号通常用来创建Java字符串,不带任何的插值变量,而双引号可以创建Java字符串,也可以用来创建带插值变量的GStrings。
对于多行字符串,你可以使用三个引号:例如在GStrings变量使用三个双引号,在简单的String变量中使用三个单引号。
如果你需要写正则表达式,你必须使用斜杠字符标记:
assert"foooo/baaaaar"==~ /fo+\/ba+r/
斜杠标记的好处是,你不需要写两个反斜杠去做转义,让正则表达式更简洁。
最后但并非不重要,请使用单引号去定义字符常量,使用双引号定义需要使用插值函数的字符串。
原生的数据结构语法
Groovy对数据结构提供原生语法,例如列表,映射,正则表达式或者一个范围内的数值。请在你的Groovy程序中好好使用它们。
以下是一些例子是使用那些原生数据结构的:
def list = [1,4,6,9]
// 默认情况下,键是字符的,所以没必要用引号括着它们
// 你可以使用with()包含着键,例如使用[(状态变量):状态名],利用变量或对象作为键
def map = [CA:'California', MI:'Michigan']
def range =10..20
def pattern = ~/fo*/
// 等同于 add()
// 调用 contains()
assert4in list
assert5in list
assert15in range
// 下标符号
assertlist[1] ==4
// 增加键值对
map && [WA:'Washington']
// 下标符号
assertmap['CA'] =='California'
assertmap.WA =='Washington'
// 使用正则表达式匹配字符串
assert'foo'=~ pattern
Grovvy 开发工具
我们来继续说说数据结构,当你需要迭代集合,Groovy提供非常丰富的方法,装饰java的核心数据结构,例如each{}, find{}, findAll{}, every{}, collect{}, inject{}.这些方法给编程语言增加了些乐趣,同时帮助我们更轻松的设计复杂的算法。通过装饰器,大量的新方法已应用于java的各种类型,这得得益于语言得的的动态特性。你可以查找更多的使用在字符串,文件,流,集合或者其他的方法:
http://groovy.codehaus.org/groovy-jdk/
switch的力量
Groovy的switch比C语言家族的更强大,因为它们通常只接收基本数据类型和。而Groovy的switch可以接受丰富的数据类型:&
def x =1.23
def result =""
switch(x) {
case"foo": result ="found foo"
// lets fall through
case"bar": result +="bar"
case[4,5,6,'in List']:
result ="list"
case 12..30:
result ="range"
case Integer:
result ="integer"
case Number:
result ="number"
default: result ="default"
assert result =="number"
通常情况,使用isCase()方法可以判断一个数值是否为大小写。
别名导入 Import aliasing
Java,中当要使用来自两个不同包的同名class时,如
java.util.List 和 java.awt.List, 你能导入其中的一个类,而另一个你就不得不给其有效的重命名了.
还有时候我们在代码中经常使用一个名字非常长的类,使得整个代码变得臃肿不堪.
为了改善这些状况, Groovy 提供了别名导入的特性:
importjava.util.List as juList
importjava.awt.List as aList
importjava.awt.WindowConstants as WC&& //import的时候 用 as 设置一个别名
当然也可以静态的导入某个方法:
import static pkg.SomeClass.foo
Groovy 的真值体系
在groovy中所有对象都可以被强制转为为一个boolean值,即: null, void 或空值 都 会视为 false, 其他的都视为 true.
所以不要再写 这种代码了:if (name != null && name.length & 0) {} 改为: if (name) {}
对于集合和其他数据类型同样适用.
因此,你可以在 诸如while(), if(), 三目运算符,Elvis 运算符(下面会讲)等等的判断条件中使用这种简单的方式.
更强悍的是可以给你的 类 添加 asBoolean() 方法,从而定制你这个类的真值的。
安全的操作对象图(嵌套的对象) Safe graph navigation
Groovy支持使用 . 操作符来安全的操作一个对象图.
Java中如果你对一个对象图中的某个节点感兴趣,想检查其是否为null的时候,经常会写出复杂的if嵌代码,如下:
if(order !=null) { //1
if(order.getCustomer() !=null) { //2
if(order.getCustomer().getAddress() !=null) { //3
System.out.println(order.getCustomer().getAddress());
而使用Groovy的安全操作符 ?. 可以讲上述代码简化为:
println order?.customer?.address
太精妙了。
操作连上 每个?.操作符前面的元素都会做Null检查,如果为null则抛出 NullPointerException 并且返回 一个 null值
断言 Assert
要检查参数、返回值等,你可以使用 assert 语句。
和 Java 的 assert 对比,Groovy 的 assert 无需单独激活。
def check(String name) {
// name non-null and non-empty according to Groovy Truth
assertname
// safe navigation + Groovy Truth to check
assertname?.size() &3
你将注意到 Groovy 的 Power Assert 语句提供更好的输出,包括每个子表达式断言时不同值的图形化视图。
Elvis (埃尔维斯)& ?:& 操作符给变量赋默认值
?: 是一个非常方便的给变量 赋 默认值的操作符,他是三目运算符(xx? a:b)的简写.
我们经常写如下三目运算代码:
def result = name !=null? name :"Unknown"//如果name为null时给'Unknown'的默认值,否则用原name的值
感谢Groovy的真值体系,null的检查可简单直接的通过 ‘name'来判断,null 会被转成 false.
再深入一点,既然总得返回'name',那在三目操作运算符中重复输入name两次就显得累赘了,可以将问号和冒号之间输入的重复变量移除,于是就成了Elvis操作符,如下:
def result = name ?:"Unknown"
//如果判断name为 false 则取 "Unknown",否则 取name之值&span&&/span&
捕捉任何异常
如果你真的不在乎在一个try代码块中抛出的异常,groovy中可以简单的catch所有这些异常,并忽略其异常类型. 所以以后像这种捕捉异常的代码:
}catch(Throwable t) {
// something bad happens
可以写成捕捉任意异常 ('any' or 'all', 或者其他你认为是"任意"的单词):
}catch(any) {
// something bad happens
可选类型 建议
最后我将讨论一下何时和怎样使用 '可选类型' 这个特性.
Groovy让你自己来决定是 显示使用强类型 还是 使用def 关键字 来申明你要的东西,那么怎么决定呢?
我有个相当简单的经验法则:
当你写的代码将会被作为公共API给别人使用时,你应当总是使用强类型的申明。
这将使得: 接口约定更加明确,避免可能的错误类型参数的传递,有利于提供更易读的文档,在编辑如:仅你能使用的私有方法时,强制类型 会有利于IDE的代码自动完成功能,或使IDE更容易推断对象的类型,然后你就能更加自如的决定是否使用明确的类型了
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具[Groovy]Groovy脚本的5种运行方式
groovyConsole 图形交互控制台
groovysh shell命令交互
通过IDE运行Groovy脚本
用命令行执行Groovy脚本
用Groovy创建Unix脚本 Groovy脚本是一些定义在文本文件中的语句和类。
groovyConsole 图形交互控制台
groovysh shell命令交互
通过IDE运行Groovy脚本
用命令行执行Groovy脚本
用Groovy创建Unix脚本 Groovy脚本是一些定义在文本文件中的语句和类。它和其他脚本语言的使用类似,它有多种方式可以运行。 [一]、groovyConsole 图形交互控制台 在终端下输入:groovyConsole
启动交互式控制台模式,可以编写代码执行,如下图: 附件1 [二]、groovysh shell命令交互 在终端下输入:groovysh
启动一个shell命令行,来执行groovy代码的交互: 附件2 [三]、通过IDE运行Groovy脚本 有一个叫GroovyShell的类含有main(String[])方法可以运行任何Groovy脚本.你可以用下面的语句执行任何Groovy脚本: java groovy.lang.GroovyShell foo/MyScript.groovy [arguments]
你可以在IDE中使用上面的Groovy main()执行或调试任何Groovy脚本. 比如 编写一个Hello.groovy的脚本: println ", welcome to Groovy!"
在终端中可以模拟IDE中执行如下: $ java -cp .:groovy-all-2.1.3.jar groovy.lang.GroovyShell Hello.groovy
, welcome to Groovy!
[四]、用命令行执行Groovy脚本 在GROOVY_HOME\bin里有个叫’groovy’ 或 ‘groovy.bat’ 的脚本文件(根据你的平台不同而不同).这些脚本文件是Groovy运行时的一部分.一旦安装了Groovy运行坏境,你就可以这样运行Groovy脚本: groovy foo/MyScript.groovy [arguments] 比如存在一个脚本文件:Hi.groovy println "Hi, ${args[0]} welcome to Groovy!"
执行命令结果如下: $ groovy Hi.
welcome to Groovy!
[五]、用Groovy创建Unix脚本 你可以用Groovy编写Unix脚本并且像Unix脚本一样直接从命令行运行它.倘若你安装的是二进制分发包并且设置好环境变量,那么下面的代码将会很好的工作。 编写一个类似如下的脚本文件,保存为:HelloGroovy
#!/usr/bin/env groovy
println("this is groovy script")
println("Hi,"+args[0]+" welcome to Groovy") 然后在命令行下执行: $ chmod +x HelloGroovy
this is groovy script
welcome to Groovy
本文为云栖社区原创内容,未经允许不得转载,如需转载请发送邮件至yqeditor@list.;如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:yqgroup@ 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
用云栖社区APP,舒服~
【云栖快讯】红轴机械键盘、无线鼠标等753个大奖,先到先得,云栖社区首届博主招募大赛9月21日-11月20日限时开启,为你再添一个高端技术交流场所&&
为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效率,降低 IT 成本...
RDS是一种稳定可靠、可弹性伸缩的在线数据库服务。支持MySQL、SQL Server、PostgreSQL、高...
MaxCompute75折抢购
Loading...博客分类:
Groovy: 把Map作为接口的实现来使用在Groovy中要想实现一个接口,我们可以直接使用map,通过as关键字把它传递给一个方法。下面是一个简单的例子,我们通过map定义了一个java.io.FileFilter接口的实现。我们吧它传递给java.io.File.listFiles()方法。然后把所有扩展名是.css和.png的文件打印出来。
// 实现FileFilter.accept(File)方法.
accept: { file -& file.path ==~ /.*\.(css|png)$/ }
] as FileFilter
new File('c:/temp').listFiles(map).each {
println it.path
如果接口里有一个以上的方法要实现,我们可以用上面的map的方式。如果只有一个方法要实现的话(就像上面的例子)我们也可以使用闭包。上面的例子可以写成这样(只要.jpg文件):
filter = { it.path ==~ /.*\.jpg$/ }
new File('c:/temp').listFiles(filter as FileFilter).each { file -&
println file.path
/2009/08/groovy-goodness-use-map-as-interface.html
浏览: 150484 次
来自: 上海
看了你的文章,我用的是mongodb3.2.7,mongo-j ...
windows是CTRL+Shift+F9非常感谢,终于可以不 ...
forcer521 写道openJPA: QueryImpl& ...
openJPA: QueryImpl&Map&St ...
cai83225 写道我在官方上也看到这样的例子,可是我想问下 ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'// 首先创建一个Groovy脚本管理器
package com.
import static com.utils.lang.StrEx.isE
import groovy.lang.GroovyS
import javax.script.B
import piledS
import javax.script.ScriptE
import javax.script.ScriptEngineM
public class GroovyShellManager {
&&& public
static final GroovyShellManager inst = new
GroovyShellManager();
ClassLoader cl = GroovyShell.class.getClassLoader();
ScriptEngineManager factory = new ScriptEngineManager(cl);
ScriptEngine engine = factory.getEngineByName("groovy");
Compilable compilable = (Compilable)
获取到一个Bindings对象,在执行脚本之前,需要将所有用到的java方法注册到bindings中
&&& public final
Bindings createBindings() {
Bindings binding = engine.createBindings();
&&& return
此方法的作用是将传入的脚本字符串编译成脚本对象
&&& public final
CompiledScript compile(String script) throws Exception {
if(isEmpty(script))
&&& return
&&& pile(script);
&&& } catch
(Exception e) {
// 调用方式说明:
// 调用前的准备工作
final GroovyShellManager groovy = GroovyShellManager.
Bindings b = groovy.createBindings();
b.put("key", value); key:脚本中该类的别名,value:任何Object对象都可放入
&&& b.put("x9",
&&& b.put("dna",
&&& b.put("ctx",
// 动态传入需要注册的方法
&&& b.put(k1,
&&& b.put(k2,
if (script == null || bindings == null)
&&& return
& Object obj = script.eval_r(bindings);// obj
即为返回的执行结果
动态脚本的编写,和java代码类似
def firstLevel =
voucherDetails.get(0).getFirstLevelSubjectRecid();
def firstSubjectName = x9.onGetFieldValue(ctx, "SUBJECT",
firstLevel, "stdname");
def firstSubjectCode = x9.onGetFieldValue(ctx, "SUBJECT",
firstLevel, "stdcode");
return "[$firstSubjectCode]$firstSubjectName";
// 注:def是动态类型的意思,和js中的var类似
对于简单的返回值,不需要return:
x9.sumShowText(voucherDetails, "borrow");
本文已收录于以下专栏:
相关文章推荐
前言及资源前言写在Groovy/Grails栏目开通的话资源2G资源IDEGroovy与IDEGroovy的Eclipse插件的安装
Groovy Tip 33 方法的参数 二  Map参数是我们在Groovy语言的编码中比较常用的一种参数类型,我们常常会很自然的写出来如下的代码:       t.testMap(a:1)    &...
说下为什么要用grovvy,我们现在想写一个通用接口项目,改接口想实现系统A发送各种形式请求到我们,我们接受,根据配置将发送数组处理组装以其他形式发送到到系统B的一个东西。
支持数据类型 json ...
请确定您已经了解了 nGrinder 的 Groovy 脚本结构:nGrinder 的 Groovy 脚本使用指南(Groovy 脚本结构)
在 nGrinder 上创建 Groovy 脚本时,会自...
Groovy咱使用不再什么大项目上,可以用来实现规则引擎,定义一个groovy文件通过spring动态载入,在groovy里面调用java的方法,处理业务对象。我感觉这个比规则引擎更灵活~~
Groovy脚本执行
* @Title: GroovyUtil.java
* @Package: com.boco.gaia.service.mockalarm.util
* @Descriptio...
该操作用给定的字符串连接list中元素的toString的值.例如,它在list的所有字符串元素中间插入了一个’^’分隔符.
['one', 'two', 'three'].join(‘...
当你创建脚本时,如果你选择使用 Groovy 脚本,除了 JUnit 方式测试外,它将类似于 Jython 脚本那样执行。如果你不仅想使用 Groovy 而且还要在 IDE 中执行 JUnit,同时又...
最近设计一个数据统计系统,系统中上百种数据统计维度,而且这些数据统计的指标可能随时会调整.如果基于java编码的方式逐个实现数据统计的API设计,工作量大而且维护起来成本较高;最终确定为将&数据统计&...
他的最新文章
讲师:刘文志
讲师:陈伟
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)}

我要回帖

更多关于 groovy 接口 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信