springboot集成groovy动态执行代码
springboot版本号:2.2.2.RELEASE
在pom.xml中集成groovy
org.codehaus.groovy
groovy-all
1.8.9
compile
main文件直接调用groovy文件,有三种方式
方式一生成Test.groovy:
package com.example.demo.groovy
public class Test {
public static String test(String id) {
return "younger--->" + id;
}
}
方式二生成test1.groovy
package com.example.demo.groovy.groovyscript
def test(id){
return "test2 id:"+ id;
}
方法三生成test2.groovy
package com.example.demo.groovy.groovyscript
output = "test3 id: ${id}, name: ${name}"
通过Java的main方法调用groovy文件,得到执行结束
package com.example.demo;
import com.example.demo.controller.CalculateController;
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.Script;
import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;
import groovy.util.ScriptException;
import java.io.File;
import java.io.IOException;
/**
* @program: demo
* @description: 本地main方法执行groovy文件
* @author: younger
* @create: 2021-05-07 15:42
**/
public class GroovyTest {
public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException, ResourceException, ScriptException {
//方式一调用groovy文件
ClassLoader parent = CalculateController.class.getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = loader.parseClass(new File("src/main/java/com/example/demo/groovy/Test.groovy"));
//得到groovy对象
GroovyObject groovyObject= (GroovyObject)groovyClass.newInstance();
//执行对象的test方法,并传参数-"id---1"
String result = (String) groovyObject.invokeMethod("test", "id---1");
System.out.println("result--------->" + result);
//方式二调用groovy文件,找到groovy脚本所在文件夹
GroovyScriptEngine engine = new GroovyScriptEngine("src/main/java/com/example/demo/groovy/groovyscript");
//得到Script对象
Script script = engine.createScript("test1.groovy", new Binding());
//执行Script对象的test方法,并传参数-"id---1"
result = (String) script.invokeMethod("test", "id----2");
System.out.println("result--------->" + result);
//方式三调用groovy文件
// GroovyScriptEngine engine = new GroovyScriptEngine("src/main/java/com/example/demo/groovy/groovyscript");
Binding binding = new Binding();
//封装参数
binding.setVariable("id","id---3");
binding.setVariable("name", "younger");
//执行test2.groovy脚本
engine.run("test2.groovy", binding);
//返回output
result = binding.getVariable("output").toString();
System.out.println("result--------->" + result);
}
}
数据库mysql,创建groovy规则表:calculate_rule
CREATE TABLE `calculate_rule` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`interface_id` varchar(128) NOT NULL COMMENT '接口id',
`bean_name` varchar(64) NOT NULL COMMENT 'bean_name',
`calculate_rule` text NOT NULL COMMENT 'groovy脚本内容',
`calculate_type` varchar(64) NOT NULL COMMENT '状态',
`status` varchar(16) NOT NULL DEFAULT 'ENABLE' COMMENT 'ENABLE-启用/DISENABLE-停用',
`extend_info` varchar(4096) DEFAULT NULL,
`created_time` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`modified_time` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='calculate rule';
insert into `calculate_rule` (`id`, `interface_id`, `bean_name`, `calculate_rule`, `calculate_type`, `status`, `extend_info`, `created_time`, `modified_time`) values('1','B.integration.A.calculate.reward','rewardCalculateParser','package com.example.demo.groovy.calculate.impl;\nimport com.example.demo.entity.request.CalculateRequest;\nimport com.example.demo.entity.response.CalculateResponse;\nimport com.example.demo.groovy.calculate.CalculateParser\nimport com.example.demo.service.CalculateInterestRuleService;\nimport org.apache.commons.lang3.StringUtils\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.stereotype.Service;\nimport java.math.RoundingMode;\n/**\n * 计算推广奖金\n */\npublic class RewardCalculateParser implements CalculateParser {\n @Autowired\n private CalculateInterestRuleService calculateInterestRuleService;\n @Override\n public CalculateResponse parse(CalculateRequest request) {\n String result = calculateInterestRuleService.test(\"younger\");\n System.out.println(\"calculateInterestRuleService.test-------->\" + result);\n Map extendInfo = request.getExtendInfo();\n String interfaceId = request.getInterfaceId();\n BigDecimal totalAmount = BigDecimal.ZERO;\n if (StringUtils.isNotBlank((String) extendInfo.get(\"totalAmount\"))) {\n totalAmount = new BigDecimal((String) extendInfo.get(\"totalAmount\"));\n }\n int refererNumber = 0;\n if (StringUtils.isNotBlank((String) extendInfo.get(\"refererNumber\"))) {\n refererNumber = Integer.parseInt((String) extendInfo.get(\"refererNumber\"));\n }\n System.out.println(\"进入奖金计算逻辑,总金额为:\" + totalAmount + \",邀请人数为:\" + refererNumber);\n \n BigDecimal reward = totalAmount.multiply(new BigDecimal(String.valueOf(refererNumber)))\n .divide(new BigDecimal(\"100\")).divide(new BigDecimal(\"365\"),4, RoundingMode.HALF_DOWN);\n CalculateResponse response = new CalculateResponse();\n response.setInterfaceId(interfaceId);\n Map map = new HashMap<>();\n map.put(\"reward\", reward);\n response.setExtendInfo(map);\n System.out.println(\"退出奖金计算逻辑,总奖金为:\" + reward);\n return response;\n }\n}\n','reward','ENABLE',NULL,'2020-07-06 09:27:58.279144','2021-05-07 17:17:19.771010');
insert into `calculate_rule` (`id`, `interface_id`, `bean_name`, `calculate_rule`, `calculate_type`, `status`, `extend_info`, `created_time`, `modified_time`) values('2','B.integration.A.calculate.sum','sumCalculateParser','package com.example.demo.groovy.calculate.impl;\n\nimport com.example.demo.entity.request.CalculateRequest;\nimport com.example.demo.entity.response.CalculateResponse;\nimport com.example.demo.groovy.calculate.CalculateParser;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 计算推广奖金\n */\npublic class SumCalculateParser implements CalculateParser {\n\n @Override\n public CalculateResponse parse(CalculateRequest request) {\n\n Map extendInfo = request.getExtendInfo();\n\n String interfaceId = request.getInterfaceId();\n\n BigDecimal totalAmount = BigDecimal.ZERO;\n if (StringUtils.isNotBlank((String) extendInfo.get(\"totalAmount\"))) {\n totalAmount = new BigDecimal((String) extendInfo.get(\"totalAmount\"));\n }\n\n int refererNumber = 0;\n if (StringUtils.isNotBlank((String) extendInfo.get(\"refererNumber\"))) {\n refererNumber = Integer.parseInt((String) extendInfo.get(\"refererNumber\"));\n }\n\n\n System.out.println(\"进入奖金计算逻辑,总金额为:\" + totalAmount + \",邀请人数为:\" + refererNumber);\n \n BigDecimal sum = totalAmount.multiply(new BigDecimal(refererNumber));\n CalculateResponse response = new CalculateResponse();\n\n response.setInterfaceId(interfaceId);\n Map map = new HashMap<>();\n map.put(\"sum\", sum);\n\n response.setExtendInfo(map);\n\n System.out.println(\"退出奖金计算逻辑,总奖金为:\" + sum);\n return response;\n }\n}','reward','ENABLE',NULL,'2020-07-06 09:27:58.279144','2020-07-06 13:23:51.311882');
原理:把groovy脚本放在表calculate_rule中的calculate_rule,spring-boot启动加载calculate_rule中启用的数据,动态成生spring.xml文件把groovy脚本加载至spring容器中。
1.创建GroovyDynamicConfiguration类,在spring-boot启动时加载groovy脚本至spring容器中:
注:请看重点方法
package com.example.demo.groovy.core;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.entity.CalculateRuleDO;
import com.example.demo.groovy.cache.BeanName;
import com.example.demo.groovy.cache.BeanNameCache;
import com.example.demo.groovy.cache.GroovyInfo;
import com.example.demo.groovy.cache.GroovyInnerCache;
import com.example.demo.service.CalculateInterestRuleService;
import groovy.lang.GroovyClassLoader;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.ResourceEntityResolver;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* @program: demo
* @description: 动态加载groovy脚本至spring容器中
* @author: younger
* @create: 2021-05-07 15:42
**/
@Configuration
public class GroovyDynamicConfiguration implements ApplicationContextAware, InitializingBean {
private ConfigurableApplicationContext applicationContext;
private static final GroovyClassLoader groovyClassLoader = new GroovyClassLoader(GroovyDynamicConfiguration.class.getClassLoader());
@Resource
private CalculateInterestRuleService calculateInterestRuleService;
@Override
public void afterPropertiesSet() throws Exception {
long start = System.currentTimeMillis();
System.out.println("开始从数据库解析groovy脚本...");
init();
long cost = System.currentTimeMillis() - start;
System.out.println("结束从数据库解析groovy脚本...,耗时:" + cost);
}
/**
* 启动spring-boot就加载数据库中groovy脚本至spring容器管理
*/
private void init() {
//从mysql中获取groovy脚本规则
List calculateRuleDOS = calculateInterestRuleService.list(new QueryWrapper().eq("status", "ENABLE"));
List beanNameList = new ArrayList<>();
List groovyInfos = convert(calculateRuleDOS, beanNameList);
init(groovyInfos, beanNameList);
}
/**
* 重点方法
*/
private void init(List groovyInfos, List beanNameList) {
if (CollectionUtils.isEmpty(groovyInfos)) {
return;
}
ConfigurationXMLWriter config = new ConfigurationXMLWriter();
//生成配置文件内容
addConfiguration(config, groovyInfos);
//把groovy规则加载至内存
put2map(groovyInfos, beanNameList);
//加载至spring容器中
loadBeanDefinitions(config);
}
/**
* 重新从mysql加载groovy脚本
*/
public void refresh() {
List calculateRuleDOS = calculateInterestRuleService.list(new QueryWrapper().eq("status", "ENABLE"));
List beanNameList = new ArrayList<>();
List groovyInfos = convert(calculateRuleDOS, beanNameList);
if (CollectionUtils.isEmpty(groovyInfos)) {
return;
}
// loadBeanDefinitions 之后才会生效
destroyBeanDefinition(groovyInfos);
destroyScriptBeanFactory();
ConfigurationXMLWriter config = new ConfigurationXMLWriter();
addConfiguration(config, groovyInfos);
put2map(groovyInfos, beanNameList);
loadBeanDefinitions(config);
}
private List convert(List calculateRuleDOS, List beanNameList) {
List groovyInfos = new LinkedList<>();
if (CollectionUtils.isEmpty(calculateRuleDOS)) {
return groovyInfos;
}
for (CalculateRuleDO calculateRuleDO : calculateRuleDOS) {
GroovyInfo groovyInfo = new GroovyInfo();
groovyInfo.setClassName(calculateRuleDO.getBeanName());
groovyInfo.setGroovyContent(calculateRuleDO.getCalculateRule());
groovyInfos.add(groovyInfo);
BeanName beanName = new BeanName();
beanName.setInterfaceId(calculateRuleDO.getInterfaceId());
beanName.setBeanName(calculateRuleDO.getBeanName());
beanNameList.add(beanName);
}
return groovyInfos;
}
private void addConfiguration(ConfigurationXMLWriter config, List groovyInfos) {
for (GroovyInfo groovyInfo : groovyInfos) {
writeBean(config, groovyInfo);
}
}
private void loadBeanDefinitions(ConfigurationXMLWriter config) {
/**
* contextString=
*
*
*
*
*
* 生成加载至spring容器的xml,为了把groovy对应的对象交给spring容器来管理。
*/
String contextString = config.getContent();
if(StringUtils.isBlank(contextString)) {
return ;
}
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) this.applicationContext.getBeanFactory());
beanDefinitionReader.setResourceLoader(this.applicationContext);
beanDefinitionReader.setBeanClassLoader(applicationContext.getClassLoader());
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this.applicationContext));
beanDefinitionReader.loadBeanDefinitions(new InMemoryResource(contextString));
String[] postProcessorNames = applicationContext.getBeanFactory().getBeanNamesForType(CustomScriptFactoryPostProcessor.class, true, false);
for (String postProcessorName : postProcessorNames) {
applicationContext.getBeanFactory().addBeanPostProcessor((BeanPostProcessor) applicationContext.getBean(postProcessorName));
}
}
private void destroyBeanDefinition(List groovyInfos) {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
for (GroovyInfo groovyInfo : groovyInfos) {
try {
beanFactory.removeBeanDefinition(groovyInfo.getClassName());
} catch (Exception e) {
System.out.println("【Groovy】delete groovy bean definition exception. skip:" + groovyInfo.getClassName());
}
}
}
private void destroyScriptBeanFactory() {
String[] postProcessorNames = applicationContext.getBeanFactory().getBeanNamesForType(CustomScriptFactoryPostProcessor.class, true, false);
for (String postProcessorName : postProcessorNames) {
CustomScriptFactoryPostProcessor processor = (CustomScriptFactoryPostProcessor) applicationContext.getBean(postProcessorName);
processor.destroy();
}
}
private void writeBean(ConfigurationXMLWriter config, GroovyInfo groovyInfo) {
if (checkSyntax(groovyInfo)) {
DynamicBean bean = composeDynamicBean(groovyInfo);
config.write(GroovyConstant.SPRING_TAG, bean);
}
}
private boolean checkSyntax(GroovyInfo groovyInfo) {
try {
groovyClassLoader.parseClass(groovyInfo.getGroovyContent());
} catch (Exception e) {
return false;
}
return true;
}
private DynamicBean composeDynamicBean(GroovyInfo groovyInfo) {
DynamicBean bean = new DynamicBean();
String scriptName = groovyInfo.getClassName();
Assert.notNull(scriptName, "parser className cannot be empty!");
//设置bean的属性,这里只有id和script-source。
bean.put("id", scriptName);
bean.put("script-source", GroovyConstant.SCRIPT_SOURCE_PREFIX + scriptName);
return bean;
}
private void put2map(List groovyInfos, List beanNameList) {
GroovyInnerCache.put2map(groovyInfos);
BeanNameCache.put2map(beanNameList);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
}
2.调用Controller测试groovy动态脚本
package com.example.demo.controller;
import java.util.Map;
import java.util.HashMap;
import javax.annotation.Resource;
import com.example.demo.entity.request.CalculateRequest;
import com.example.demo.entity.response.CalculateResponse;
import com.example.demo.groovy.calculate.CalculateParser;
import com.example.demo.groovy.calculate.GroovyParserEngine;
import com.example.demo.groovy.core.GroovyDynamicConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CalculateController {
@Resource
private GroovyParserEngine groovyParserEngine;
@Resource
private GroovyDynamicConfiguration groovyDynamicLoader;
@Resource
private CalculateParser rewardCalculateParserGroovy;
@RequestMapping("/calculate")
public Map calculate() {
String interfaceId = "B.integration.A.calculate.reward";
Map map = new HashMap<>();
map.put("totalAmount", "10");
map.put("refererNumber", "5");
CalculateRequest request = new CalculateRequest();
request.setInterfaceId(interfaceId);
request.setExtendInfo(map);
CalculateResponse response = groovyParserEngine.parse(request);
return response.getExtendInfo();
}
@RequestMapping("/refresh")
public void refresh() {
groovyDynamicLoader.refresh();
}
}
package com.example.demo.groovy.calculate;
import com.example.demo.entity.request.CalculateRequest;
import com.example.demo.entity.response.CalculateResponse;
public interface GroovyParserEngine {
CalculateResponse parse(CalculateRequest request);
}
package com.example.demo.groovy.calculate.impl;
import com.example.demo.groovy.cache.BeanNameCache;
import com.example.demo.groovy.calculate.CalculateParser;
import com.example.demo.groovy.calculate.GroovyParserEngine;
import com.example.demo.entity.request.CalculateRequest;
import com.example.demo.entity.response.CalculateResponse;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
@Service
public class GroovyParserEngineImpl implements GroovyParserEngine, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public CalculateResponse parse(CalculateRequest request) {
String beanName = BeanNameCache.getByInterfaceId(request.getInterfaceId());
CalculateParser parser = (CalculateParser) applicationContext.getBean(beanName);
return parser.parse(request);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
启动项目,调用http://localhost:8888/calculate,测试结果。
代码下载地址:https://download.csdn.net/download/yangxiang_Younger/18445827