总结一套groovy脚本语言与spring容器完美结合的动态执行能力
1.groovy脚本
groovy 是JVM脚本语言,可以在JVM环境中编译运行,基于这个特性,我们只需要在项目中封装好了对脚本的编译逻辑-运行接口,那么在JVM中动态运行groovy脚本就顺利成章了,可喜的是,groovy 提供了GroovyShell 和 Script 类专门提供这样的逻辑,所以代码写起来就简单的多了
/**
* 动态执行groovy脚本代码
* @param scriptStr
* @return
*/
public Object runScript (String scriptStr){
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(this.getClass().getClassLoader());
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
compilerConfiguration.setSourceEncoding("utf-8");
//Binding 可以将变量传递进Script当中,类似可以传递任何参数
Binding binding = new Binding();
binding.setVariable("scriptEngine",this);
GroovyShell groovyShell = new GroovyShell(groovyClassLoader,binding,compilerConfiguration);
Script scriptObj = groovyShell.parse(scriptStr);
Object result = scriptObj.run();
return result;
}
如此:我们提交一段groovy代码片段,就可以执行对应逻辑了,但是这样有几个问题:
- Script对象本身是非常大的,使用不当容易导致OOM
- 没有与项目中的spring相融合,脚本中无法方便的使用spring bean 那么这样的脚本实用性就不强
为解决以上两个问题,请往下看
2.groovy动态bean
项目工程一般情况下都会继承spring,此方案就是将groovy 动态脚本方便快捷的与spring容器结合,便于开发,更实用
这里仅仅使用了Groovy的编译,将编译后的class注入的spring容器中,所以并没有使用的Groovy的Script所以内存占用是非常有限的,随后以bean的形式注入spring容器
/**
* 全局scriptBean注册锁,注册bean是串行的
*/
private static final String REGISTER_LOCK = "scriptLock";
/**
* spring 容器
*/
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 动态注册GroovyBean
* 因为是单例使用锁控制串行就行
* @param contextFunction
* @param scriptName
* @return
*/
public BaseScript registerIfAbsent (String scriptName, ScriptContextFunction contextFunction){
synchronized (REGISTER_LOCK){
BaseScript bean = getBean(scriptName);
if (bean == null){
return register(scriptName,contextFunction);
}
return bean;
}
}
/**
* 刷新注册GroovyBean
* @param contextFunction
* @param scriptName
* @return
*/
public void refresh (String scriptName, ScriptContextFunction contextFunction){
synchronized (REGISTER_LOCK){
BaseScript bean = getBean(scriptName);
if (bean == null){
logger.info("刷新ScriptBean失败:脚本{}未注册bean",scriptName);
return;
}
register(scriptName,contextFunction);
logger.info("刷新ScriptBean成功:{}",scriptName);
}
}
/**
* 注册bean
* @param scriptName
* @param contextFunction
* @return
*/
private BaseScript register(String scriptName, ScriptContextFunction contextFunction) {
String finalGroovyBeanName = "groovy" + scriptName + "Bean";
GroovyClassLoader groovyClassLoader = new GroovyClassLoader(this.getClass().getClassLoader());
String scriptContext = contextFunction.getScript(scriptName);
Class scriptCls = groovyClassLoader.parseClass(scriptContext,finalGroovyBeanName);
//获取BeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
//创建bean信息
BeanDefinitionBuilder beanDefinitionBuilder =BeanDefinitionBuilder.genericBeanDefinition(scriptCls);
//动态注册bean
defaultListableBeanFactory.registerBeanDefinition(finalGroovyBeanName,beanDefinitionBuilder.getBeanDefinition());
Object bean = applicationContext.getBean(finalGroovyBeanName);
logger.info("注册ScriptBean:{}",scriptName);
return (BaseScript) bean;
}
/**
* 获取Bean的工具类
* @param beanName
* @return
*/
public BaseScript getBean(String beanName){
String finalGroovyBeanName = "groovy" + beanName + "Bean";
BaseScript script = null;
try {
script = (BaseScript) applicationContext.getBean(finalGroovyBeanName);
} catch (NoSuchBeanDefinitionException e) {
}
return script;
}
工具类
@FunctionalInterface
public interface ScriptContextFunction {
/**
* 获取脚本内容
* @param name
* @return
*/
String getScript(String name);
}
基于以上对于groovy脚本的动态编译和注入,以及刷新能力,业务的使用场景可以自由定制实现,比如如何获取脚本内容,如何使用动态脚本bean等