動態執行代碼邏輯

動態執行邏輯的方法據我所知有一下兩種方式java

  • QLExpress
  • Groovy

QLExpress

QLExpress是阿里開源的動態腳本執行的項目。 由阿里的電商業務規則、表達式(布爾組合)、特殊數學公式計算(高精度)、語法分析、腳本二次定製等強需求而設計的一門動態腳本引擎解析工具。 在阿里集團有很強的影響力,同時爲了自身不斷優化、發揚開源貢獻精神,於2012年開源。 git

https://github.com/alibaba/QL...github

這種方案在配置上感受不太方便,緣由是沒有IDE支持、某些JAVA語法不支持。。。

Groovy

來着百度百科

Groovy 是 用於Java虛擬機的一種敏捷的動態語言,它是一種成熟的面向對象編程語言,既能夠用於面向對象編程,又能夠用做純粹的腳本語言。使用該種語言沒必要編寫過多的代碼,同時又具備閉包和動態語言中的其餘特性。編程

Groovy是JVM的一個替代語言(替代是指能夠用 Groovy 在Java平臺上進行 Java 編程),使用方式基本與使用 Java代碼的方式相同,該語言特別適合與Spring的動態語言支持一塊兒使用,設計時充分考慮了Java集成,這使 Groovy 與 Java 代碼的互操做很容易。(注意:不是指Groovy替代java,而是指Groovy和java很好的結合編程。api

原理

經過Groovy提供的GroovyClassLoader把源代碼動態加載編譯成Class,Class再實例化成對象緩存

動手實現

依賴閉包

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>3.0.0-rc-1</version>
</dependency>
<!--hutool 工具包,不是核心-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.0.3</version>
</dependency>
  1. 建立動態腳本工廠,inject方法用於擴展。
package cn.dhbin.dynamic;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import groovy.lang.GroovyClassLoader;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 動態腳本工廠
 * 做用:
 * 經過字符串源碼生成Class
 * Class -> 實例
 *
 * @author donghaibin
 * @date 2019/11/19
 */
public class DynamicFactory {

    /**
     * 單例
     */
    private static DynamicFactory dynamicFactory = new DynamicFactory();

    /**
     * groovy類加載器
     */
    private GroovyClassLoader groovyClassLoader = new GroovyClassLoader();

    /**
     * 緩存Class
     */
    private ConcurrentHashMap<String, Class<?>> classCache = new ConcurrentHashMap<>();

    /**
     * 獲取單例
     *
     * @return 實例
     */
    public static DynamicFactory getInstance() {
        return dynamicFactory;
    }


    /**
     * 加載建立實例,prototype
     *
     * @param codeSource 源代碼
     * @return 實例
     * @throws Exception 異常
     */
    public IScript loadNewInstance(String codeSource) throws Exception {
        if (StrUtil.isNotBlank(codeSource)) {
            Class<?> aClass = getCodeSourceClass(codeSource);
            if (aClass != null) {
                Object instance = aClass.newInstance();
                if (instance != null) {
                    if (instance instanceof IScript) {
                        this.inject((IScript) instance);
                        return (IScript) instance;
                    } else {
                        throw new IllegalArgumentException(StrUtil.format("建立實例失敗,[{}]不是IScript的子類", instance.getClass()));
                    }
                }
            }
        }
        throw new IllegalArgumentException("建立實例失敗,instance is null");
    }

    /**
     * code text -> class
     * 經過類加載器生成class
     *
     * @param codeSource 源代碼
     * @return class
     */
    private Class<?> getCodeSourceClass(String codeSource) {
        String md5 = SecureUtil.md5(codeSource);
        Class<?> aClass = classCache.get(md5);
        if (aClass == null) {
            aClass = groovyClassLoader.parseClass(codeSource);
            classCache.putIfAbsent(md5, aClass);
        }
        return aClass;
    }


    /**
     * 對script對象處理
     *
     * @param script {@link IScript}
     */
    public void inject(IScript script) {
        // to do something
    }
}
  1. 定義腳本模板
package cn.dhbin.dynamic;

/**
 * 腳本接口,全部腳本實現該接口的{@link IScript#run(String)}方法
 *
 * @author donghaibin
 * @date 2019/11/19
 */
public interface IScript {

    /**
     * 具體邏輯
     *
     * @param param 參數
     * @return 執行結果
     */
    String run(String param);

}
  1. 腳本執行器
package cn.dhbin.dynamic;

import java.util.concurrent.ConcurrentHashMap;

/**
 * @author donghaibin
 * @date 2019/11/19
 */
public class ScriptExecutor {

    /**
     * 緩存實例
     */
    private ConcurrentHashMap<String, IScript> objCache = new ConcurrentHashMap<>();

    /**
     * 執行腳本
     *
     * @param id 實例Id
     * @return 運行結果
     */
    public String run(String id, String param) {
        IScript script = objCache.get(id);
        if (script == null) {
            throw new IllegalArgumentException("未找到實例, id = [" + id + "]");
        } else {
            return script.run(param);
        }
    }

    /**
     * 註冊實例
     *
     * @param id 實例id
     * @param script 實例
     * @return 返回前一個實例,若是爲null,則是新插入
     */
    public IScript register(String id, IScript script) {
        return objCache.put(id, script);
    }

    /**
     * 移除實例
     *
     * @param id 實例id
     * @return 移除的實例
     */
    public IScript remove(String id) {
        return objCache.remove(id);
    }


}

到這裏,就基本實現了腳本的加載-實例化-執行。下面測試編程語言

編寫腳本

package cn.dhbin.dynamic;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author donghaibin
 * @date 2019/11/19
 */
public class SimpleScript implements IScript{

    private static final Logger log = LoggerFactory.getLogger(SimpleScript.class);

    @Override
    public String run(String param) {
        log.info("輸入的參數是:[{}]", param);
        log.info("你好世界");
        return "hello world";
    }

}

測試用例

package com.pig4cloud.pig.sms.dynamic;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

/**
 * @author donghaibin
 * @date 2019/11/19
 */
@Slf4j
class DynamicFactoryTest {

    @Test
    void runWithExecutor() throws Exception {
        DynamicFactory dynamicFactory = DynamicFactory.getInstance();
        ScriptExecutor executor = new ScriptExecutor();
        String codeSource = "package cn.dhbin.dynamic;\n" +
            "\n" +
            "import org.slf4j.Logger;\n" +
            "import org.slf4j.LoggerFactory;\n" +
            "\n" +
            "/**\n" +
            " * @author donghaibin\n" +
            " * @date 2019/11/19\n" +
            " */\n" +
            "public class SimpleScript implements IScript{\n" +
            "\n" +
            "\tprivate static final Logger log = LoggerFactory.getLogger(SimpleScript.class);\n" +
            "\n" +
            "\t@Override\n" +
            "\tpublic String run(String param) {\n" +
            "\t\tlog.info(\"輸入的參數是:[{}]\", param);\n" +
            "\t\tlog.info(\"你好世界\");\n" +
            "\t\treturn \"hello world\";\n" +
            "\t}\n" +
            "\n" +
            "}\n";
        IScript script = dynamicFactory.loadNewInstance(codeSource);
        String id = "1";
        executor.register(id, script);

        for (int i = 0; i < 10; i++) {
            String result = executor.run(id, "abc");
            log.info("結果:[{}]", result);
        }

    }

    @Test
    void runWithoutExecutor() throws Exception{
        DynamicFactory dynamicFactory = DynamicFactory.getInstance();
        String codeSource = "package cn.dhbin.dynamic;\n" +
            "\n" +
            "import org.slf4j.Logger;\n" +
            "import org.slf4j.LoggerFactory;\n" +
            "\n" +
            "/**\n" +
            " * @author donghaibin\n" +
            " * @date 2019/11/19\n" +
            " */\n" +
            "public class SimpleScript implements IScript{\n" +
            "\n" +
            "\tprivate static final Logger log = LoggerFactory.getLogger(SimpleScript.class);\n" +
            "\n" +
            "\t@Override\n" +
            "\tpublic String run(String param) {\n" +
            "\t\tlog.info(\"輸入的參數是:[{}]\", param);\n" +
            "\t\tlog.info(\"你好世界\");\n" +
            "\t\treturn \"hello world\";\n" +
            "\t}\n" +
            "\n" +
            "}\n";

        for (int i = 0; i < 10; i++) {
            IScript script = dynamicFactory.loadNewInstance(codeSource);
            String result = script.run("abc");
            log.info("結果:[{}]", result);
        }
    }


}

執行結果

11:19:32.243 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.255 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.255 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.256 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.256 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.256 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 輸入的參數是:[abc]
11:19:32.256 [main] INFO cn.dhbin.dynamic.SimpleScript - 你好世界
11:19:32.256 [main] INFO cn.dhbin.dynamic.DynamicFactoryTest - 結果:[hello world]

兩個用例執行的結果都同樣,區別就是一個使用了執行器。這樣作的目的是提升運行效率,執行器緩存了實例對象,不用每次執行都實例化。ide

總結

Groovy這種方案實際上是從xxl-job這個定時任務項目中提取出來的。它還擴展了Spring的幾個註解,能從Spring的容器中加載Bean並使用。項目連接: https://gitee.com/xuxueli0323...工具

SpringGlueFactory

思考

經過groovy動態加載Class,再結合Spring的生命週期,是否能夠實現動態添加Bean?是否能夠實現動態添加Controller?

image

相關文章
相關標籤/搜索