springboot集成groovy执行代码

springboot集成groovy动态执行代码

springboot版本号:2.2.2.RELEASE

在pom.xml中集成groovy


   org.codehaus.groovy
   groovy-all
   1.8.9
   compile

一.springboot中执行groovy文件

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);
    }
}

二,通过数据库保存groovy内容,动态执行groovy脚本

数据库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

你可能感兴趣的:(java,jdk)