背景
在游戏开发中,lua脚本被大量运用。其思路是,将脚本作为黏合组件的胶水,利用基础组件提供的功能,灵活地组合出各种不同的功能,就好像创造出一种新的DSL(domain specific language)一样。在后端开发中,显然也可以借鉴这里面的宝贵经验。在java生态下,使用groovy就显得自然而然。
why groovy
作为厚重java组件的胶水语言,他具有以下优势:
- 完全兼容java语法
到了groovy3,new String[]{"a"}
、(a,b) -> a+b
等java语法也得到了支持,def m = [:] as Map<String, List<String>>
在groovy2中因为末尾的>导致编译器解析报错的bug也得到了解决——这门语言还是在蓬勃发展呀! - 集成方式简单
使用GroovyShell类即可。网上有很多集成的方法,这里就略去不表了。 - 老生常谈
“java程序员更容易接受groovy,学习曲线平滑”。这在实际中真没发现=.=。即使《clean code》早在上个世纪就已经完成了,今天大多数coder还是会使用cv大法。人们显得功利和浮躁,往往不会去了解技术的本质。
有本书提过的“救火”类程序员竟然是真实存在的。根据我多年的java经验,这个虚拟机很温柔,说java是现存最简单的语言也不为过,因为市面上大把的是培训三个月出来混的,其创造力十分惊人,写出下面的代码就不足为奇了。
1
2
3
4
5Map map = new HashMap() {{ put("a", 1); put("") }};
第一眼看去,很多人注意到这代码没好好写泛型。然而,应该还有更多人不知道上面代码里所谓的“动态初始化块”、以及其创造出一个匿名内部类的事实(有个5年java工作经验的同事竟也不知道)。
题外话扯多了,干脆再说一句吧。写出好的代码和语言无关,详见redis源码,因为好的代码用所有语言写出来都是一个样的,不好的代码才是千奇百怪。尤其对jvm而言,当你写的代码足够好的时候,性能自然而然就好了。根本没所谓的调优一说。至少在我的实践里这是真的。
回归主题
springboot如何从groovy脚本环境中受益呢?比如你开发了个接口,为了稳健,一般即使没有绩效上的要求,你也会加个查询接口,暴露一些内部的东西,或者去跟踪程序的运行。这样做,每次会产生很多接口,让人头晕@@。
理论来讲,没用且重复的代码还是少点为好,不然太多的垃圾信息会掩埋真正需要关心内容。
我们可以用脚本来轻松达到目的。空说无益,用几个例子来展示下脚本的价值吧!假设我们已经初始化好GroovyShell:
1
2
3
4def binding = new Binding() ... def groovyShell = new GroovyShell(binding, this.class.classLoader)
我们可以这样给它准备一个redis template。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Service class ShellRedisOptions { @Autowired StringRedisTemplate stringRedisTemplate // 封装标准的redis方法 def get(str) { return stringRedisTemplate.opsForValue().get(str) } def hget(key, subKey) { return stringRedisTemplate.opsForHash().get(key, subKey) } // ... other stuff }
然后,把这个基础设施暴露给shell环境:
1
2
3
4
5@Autowired ShellRedisOptions shellRedisOptions binding.set('redis', shellRedisOptions)
更传统地,我们可以使用groovy sql,暴露数据库基础设施给shell:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@Service class ShellDbOptions { @Autowired DataSource dataSource Sql sql @PostConstruct void init() { sql = new Sql(dataSource) } // 再封装groovy sql里最牛逼的两个方法firstRow, rows } // 暴露给shell binding.set('sql', shellDbOptions)
接着,我们在统一暴露一个shell接口:
1
2
3
4
5
6
7
8
9
10
11@Controller class GroovyShellController { @Autowired GroovyShellService groovyShellService @GetMapping('/shell/exec') String exec(@RequestParam String script) { return groovyShellService.exec(script) } }
最后,我们可以用python的tkinter,或者javafx做个简单的界面,这个界面类似于groovyconsole^^,你可以给它一个ctrl + R的快捷键,用于调用上面的接口。
我们可以在springboot环境中愉快地享用了:
1
2
3
4
5
6
7def keys = redis.keys('some_key:*') println keys println redis.type(keys.first()) def rows = sql.rows('select name from persons') rows.each { println it.name }
是不是很酷。当然,上面省略了一些细节。比如,拦截并重定向shell的输出流(不然println会打印到日志里去。),这个可以参考GroovyConsole源码里的OutputIntercepter。
几个应用
调试
除去上面的招式外,假如你把spring context的Object getBean(String beanName)
、Object getBean(Class<?> clazz)
适配到groovy的下标访问契约:getAt,并暴露给shell,存入到名为spring的变量里,你的武器库就更强大了!
1
2
3
4// 使用你开发的服务 def s = spring['youServiceBean'] s.doWork()
用这招去测试环境调试也是可以的,当然,如果暴露太多的东西,把测试同事的数据环境给整挺好了,那就不妙了==。
压测
能用程序解决的事情,还是尽量用程序解决。比如我们需要压测一个接口,用jmeter是很nice的,但是过于笨重,还不够灵活。测试程序的并发性能,用脚本我们可以这么干:
1
2
3
4
5
6
7
8
9
10
11
12
13def s = spring['yourServiceBean'] (1..10).collect { Thread.start { 100.times { def start = System.currentTimeMillis() s.doWork() println """${Thread.currentThread().name} cost: ${System.currentTimeMillis() - start}ms""" } } }.each { it.join() }
随写随用,用完就丢。
ScriptHub
其实脚本大多还是有保留价值的。譬如,我用javafx开发了上述后端接口的前端界面,并用redis做持久化,企图在同事间共享脚本,为了方便,我还实现了所有主流的idea快捷键。无奈这个项目写到内网去了。所以这里是看不到了。
这个思路是极好的,开发写脚本,给做数据、测试的同事用,快速响应,挺敏捷的,毕竟在门外汉眼里,做工程的现在不吃香了,搞点这种东西装下13,挺直下腰杆子,还是可以的。
然而正如前面提到的jemeter,一般人还是更习惯GUI,眼睛看不到的东西让他们很慌,脚本什么的,就更是让他们崩溃了。咋推广还真是个问题,难道要把脚本封装到可拖拽的pipeline里去么?_?。不得不说这是个方向。
写在最后
最好使用@Conditional注解,不要让上面的东西泄露到生产环境中去,这是为了你的安全着想。
前置知识:groovy、groovy、groovy。
其实变通一下,集成jython也是可以(亲测可行)的和必要的,很多人还是习惯看似简单无脑的python,殊不知这个语言比java要复杂地多,至少培训机构三个月量产不出来。
最后
以上就是无语心情最近收集整理的关于springboot集成groovy脚本环境的全部内容,更多相关springboot集成groovy脚本环境内容请搜索靠谱客的其他文章。
发表评论 取消回复