boot中groovy替代java_springboot应用动态运行groovy脚本-附源码

本文介绍了如何在SpringBoot项目中利用Groovy动态脚本能力,实现动态组合或运行bean功能,降低测试成本。通过GroovyShell预设对象,可以直接在Groovy脚本中调用Spring容器中的bean,简化接口测试。文章提供了详细步骤,包括引入依赖、设置Binding、创建Controller,并强调了动态脚本执行的安全性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、简介

在springboot项目中,开发人员可以很方便的完成各种功能的开发和封装,提供流行的restful api接口。然而对项目功能的测试,大部分情况会通过预先编写测试用例进行,甚至开发人员会开发测试专用的restful api接口来完成基本功能的测试,这无疑增加了开发成本,且会产生很多"用过即废"的代码。

那么能否实现动态组合或运行spring容器中注册的各个bean的功能、动态运行bean的方法呢?在项目中集成groovy动态脚本的能力即可。

集成groovy的好处:

groovy跟java都是基于jvm的语言,可以在java项目中集成groovy并充分利用groovy的动态功能;

groovy兼容几乎所有的java语法,开发者完全可以将groovy当做java来开发,甚至可以不使用groovy的特有语法,仅仅通过引入groovy并使用它的动态能力;

groovy可以直接调用项目中现有的java类(通过import导入),通过构造函数构造对象并直接调用其方法并返回结果;

最后也是与本文最相关的,groovy支持通过GroovyShell预设对象,在groovy动态脚本中直接调用预设对象的方法。因此我们可以通过将spring的bean预设到GroovyShell运行环境中,在groovy动态脚本中直接调用spring容器中bean来调用其方法,这点对于spring项目非常方便!

二、groovy动态脚本的使用

2.1 直接调用java类

在上一节中集成groovy的好处中提到,groovy可以通过import的方式直接调用java类,直接上代码:

package pers.doublebin.example.groovy.script.service;

import groovy.lang.Binding;

import groovy.lang.GroovyShell;

import groovy.lang.Script;

public class TestService {

public String testQuery(long id){

return "Test query success, id is " + id;

}

public static void main(String[] args) {

Binding groovyBinding = new Binding();

GroovyShell groovyShell = new GroovyShell(groovyBinding);

String scriptContent = "import pers.doublebin.example.groovy.script.service.TestService\n" +

"def query = new TestService().testQuery(1L);\n" +

"query";

Script script = groovyShell.parse(scriptContent);

System.out.println(script.run());

}

}

返回结果:

Test query success, id is 1

这种方式在groovy动态脚本中将类import后直接new了一个新对象,并调用对象的方法。

2.2 通过GroovyShell预设对象

在上一节中提到,groovy支持通过GroovyShell预设对象,在groovy动态脚本中直接调用预设对象的方法。直接上代码:

package pers.doublebin.example.groovy.script.service;

import groovy.lang.Binding;

import groovy.lang.GroovyShell;

import groovy.lang.Script;

public class TestService {

public String testQuery(long id){

return "Test query success, id is " + id;

}

public static void main(String[] args) {

Binding groovyBinding = new Binding();

groovyBinding.setVariable("testService", new TestService());

GroovyShell groovyShell = new GroovyShell(groovyBinding);

String scriptContent = "def query = testService.testQuery(2L);\n" +

"query";

Script script = groovyShell.parse(scriptContent);

System.out.println(script.run());

}

}

返回结果:

Test query success, id is 1

这种方式通过Binding对象的setVariable方法设置了预设对象testService,在动态脚本中便可以直接调用testService的方法。简单看下Binding类setVariable方法的源码:

public void setVariable(String name, Object value) {

if (this.variables == null) {

this.variables = new LinkedHashMap();

}

this.variables.put(name, value);

}

实际上,Binding对象维护了一个Map类型的属性variables,通过setVariable方法将预设对象和预设对象名称存储到了variables属性中,动态运行时会尝试道variables中获取对应名称的对象,如果存在再尝试调用其方法。

2.3 groovy脚本中调用springbean的方法

到这里已经很清晰了,我们只要能获取spring容器中所有的bean,通过Binding的setVariable将spring所有的bean预设进GroovyShell运行环境,在动态脚本中便可以直接调用bean的方法。这种我们对spring项目中的service层、controller层、DAO层等注册的bean均可以通过这种方式实现动态调用。

三、springboot接口动态运行groovy脚本

下面以一个springboot接口动态运行groovy脚本的示例工程为例,讲述如何在springboot接口中动态运行groovy脚本。

3.1 引入groovy-all依赖

org.codehaus.groovy

groovy-all

2.4.7

3.2 Service层示例类

package pers.doublebin.example.groovy.script.service;

import groovy.lang.Binding;

import groovy.lang.GroovyShell;

import groovy.lang.Script;

import org.springframework.stereotype.Service;

@Service

public class TestService {

public String testQuery(long id){

return "Test query success, id is " + id;

}

TestService 类实现了一个简单的testQuery方法,springboot通过扫描到@Service注解会将生成TestService的bean并注册到应用上下文中,beanName为"testService".

3.3 springboot的Configuration类中设置Binding

首先配置类可以实现org.springframework.context.ApplicationContextAware接口用来获取应用上下文,然后再配置类中通过应用上下文获取所有的bean并注册到groovy的Binding中,看源码:

package pers.doublebin.example.groovy.script.config;

import groovy.lang.Binding;

import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import java.util.Map;

@Configuration

public class GroovyBindingConfig implements ApplicationContextAware {

private ApplicationContext applicationContext;

@Bean("groovyBinding")

public Binding groovyBinding() {

Binding groovyBinding = new Binding();

Map beanMap = applicationContext.getBeansOfType(Object.class);

//遍历设置所有bean,可以根据需求在循环中对bean做过滤

for (String beanName : beanMap.keySet()) {

groovyBinding.setVariable(beanName, beanMap.get(beanName));

}

return groovyBinding;

}

/*@Bean("groovyBinding1")

public Binding groovyBinding1() {

Map beanMap = applicationContext.getBeansOfType(Object.class);

return new Binding(beanMap); //如果不需要对bean做过滤,直接用beanMap构造Binding对象即可

}*/

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

this.applicationContext = applicationContext;

}

}

如果不需要对bean做过滤,可以通过注释掉的方法直接从应用上下文中获取beanMap并直接构造Binding的variables中;当然上面示例applicationContext.getBeansOfType方法也可以指定获取bean的类型。

需要注意的是:上面这种方法注册的到binding中beanMap是不包含groovyBinding这个对象本身的(先后顺序的原因),如果需要将binding对象本身(也是一个bean)注册,也很简单,只需要将Binding的bean生成放在GroovyBindingConfig之前,并且在实现ApplicationContextAware接口的setApplicationContext方法中进行variables的设置即可,但建议不这样做,因为这样就可以通过脚本对Binding对象本身造成破坏,不太优雅~

3.4 实现用于groovy动态脚本运行的controller

直接看源码:

package pers.doublebin.example.groovy.script.controller;

import groovy.lang.Binding;

import groovy.lang.GroovyClassLoader;

import groovy.lang.GroovyShell;

import groovy.lang.Script;

import org.codehaus.groovy.control.CompilerConfiguration;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

import pers.doublebin.example.groovy.script.component.TestScript;

import javax.annotation.PostConstruct;

@RestController

@RequestMapping("/groovy/script")

public class GroovyScriptController {

@Autowired

private Binding groovyBinding;

private GroovyShell groovyShell;

@PostConstruct

public void init(){

GroovyClassLoader groovyClassLoader = new GroovyClassLoader(this.getClass().getClassLoader());

CompilerConfiguration compilerConfiguration = new CompilerConfiguration();

compilerConfiguration.setSourceEncoding("utf-8");

compilerConfiguration.setScriptBaseClass(TestScript.class.getName());

groovyShell = new GroovyShell(groovyClassLoader, groovyBinding, compilerConfiguration);

}

@RequestMapping(value = "/execute", method = RequestMethod.POST)

public String execute(@RequestBody String scriptContent) {

Script script = groovyShell.parse(scriptContent);

return String.valueOf(script.run());

}

}

将binding对象注入后,在初始化方法init()中用binding对象构造GroovyShell对象,在提供的execute接口实现中用GroovyShell对象生成Script脚本对象,并调用Script的run()方法运行动态脚本并返回结果。

上述示例中只是一个简单实现,在接口方法execute中,每次脚本运行前都会通过groovyShell来parse出一个Script 对象,这其实是有成本的,实际应用中可根据脚本特征(如md5值等)将script存储, 下次运行时可根据脚本特征直接获取Script对象,避免parse的成本。

3.5 实现用于groovy动态脚本运行的controller

使用示例:

上述接口定义了一个post方法,path:/groovy/script/execute,运行后直接用postman调用测试testService的方法,结果如下:

c7803626c09d

groovy-script-example.JPG

显然,通过接口直接用groovy脚本调用了testService这个bean的方法,非常简单。

四、源码地址

五、注意事项

在实际项目中,特别是生产环境,虽然可以方便的调用应用中的bean或者类的方法,但随意调用也可能引发不可避免的灾难,所以对运行groovy动态脚本的接口要注意做好严格的权限控制!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值