Java低代码、配置化、DSL总结
技术能力对比
一、表达式引擎
应用场景总结:针对于需要进行脚本求值场景,性能上aviator比SpringEL要好一些,可能是1-2倍or10倍or更多,但是本身计算就是ns级别,所以在非高性能场景可以忽略不计。
标准接口定义:
import java.util.Map;
public interface ExpressionParser {
    <T> T getValue(String expressionString, Map<String, Object> rootObject);
    <T> T getValue(String expressionString);
}
① aviator
官方文档:https://github.com/killme2008/aviatorscript
引入:
        <dependency>
            <groupId>com.googlecode.aviator</groupId>
            <artifactId>aviator</artifactId>
            <version>5.2.1</version>
        </dependency>示例代码:
import com.boommapro.gaia.expression.ExpressionParser;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.Expression;
import java.util.Map;
public class AviatorExpressionParser implements ExpressionParser {
    private static AviatorEvaluatorInstance aviatorEvaluator = AviatorEvaluator.getInstance();
    public <T> T getValue(String expression, Map<String, Object> context) {
        //开启缓存
        Expression expr = aviatorEvaluator.compile(expression, expression, true);
        return (T) expr.execute(context);
    }
    @Override
    public <T> T getValue(String expressionString) {
        return getValue(expressionString, null);
    }
}
② SpringEL
代码块参考:https://boommanpro.github.io/post/springelutil-code/
官方文档:https://docs.spring.io/spring-framework/reference/core/expressions.html
import com.boommapro.gaia.expression.ExpressionParser;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.expression.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeLocator;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SpringExpressionParser implements ExpressionParser {
    private static final long serialVersionUID = 1L;
    private StandardEvaluationContext context;
    private org.springframework.expression.ExpressionParser expressionParser;
    private final ConcurrentHashMap<String, Expression> EXPRESSION_MAP = new ConcurrentHashMap<>();
    @Override
    public <T> T getValue(String expressionString) {
        return getValue(expressionString, null);
    }
    private static final SpringExpressionParser PARSER = new SpringExpressionParser();
    public static SpringExpressionParser getInstance() {
        return PARSER;
    }
    private SpringExpressionParser() {
        setApplicationContext(new GenericApplicationContext());
    }
    @Override
    @SuppressWarnings("all")
    public <T> T getValue(String expressionString, Map<String, Object> rootObject) {
        Expression expression = getExpression(expressionString);
        return ((T) expression.getValue(context, rootObject));
    }
    private Expression getExpression(String expressionString) {
        return EXPRESSION_MAP.computeIfAbsent(expressionString, s -> expressionParser.parseExpression(s));
    }
    @SuppressWarnings("all")
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (applicationContext instanceof ConfigurableApplicationContext) {
            ConfigurableApplicationContext ac = (ConfigurableApplicationContext) applicationContext;
            context = new StandardEvaluationContext();
            context.addPropertyAccessor(new BeanExpressionContextAccessor());
            context.addPropertyAccessor(new BeanFactoryAccessor());
            context.addPropertyAccessor(new MapAccessor());
            context.addPropertyAccessor(new EnvironmentAccessor());
            context.setBeanResolver(new BeanFactoryResolver(ac.getBeanFactory()));
            context.setTypeLocator(new StandardTypeLocator(ac.getBeanFactory().getBeanClassLoader()));
            //初始化上下文
            expressionParser = new SpelExpressionParser();
            //注册默认工具方法
//            registerFunc(context,xxx.class);
        } else {
            throw new ApplicationContextException("can't cast ConfigurableApplicationContext");
        }
    }
    public void registerFunc(StandardEvaluationContext context, Class clazz) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getModifiers() == 9) {
                String name = method.getName();
                context.registerFunction(name, method);
            }
        }
    }
    public void registerFunc(StandardEvaluationContext context, String className) throws ClassNotFoundException {
        Class<?> clazz = Class.forName(className);
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getModifiers() == 9) {
                String name = method.getName();
                context.registerFunction(name, method);
            }
        }
    }
}
③ Groovy简化求值
本身代码管理和优化比较长,暂不提供。
二、动态脚本
应用场景总结:groovy适合轻量级引入,但是存在不兼容java lambda语法和部分语法不兼容等问题,在特殊情况下可以使用 Java Dynamic Class Loader
① Groovy
引入:
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.17</version>
        </dependency>示例代码:
public class GroovyShellTest {
    public static void main(String[] args) {
        GroovyShell shell = new GroovyShell();
        Binding binding = new Binding();
        binding.setVariable("param", "param");
        String script = "";
        try {
            shell.evaluate(script);
        } catch (Exception e) {
            throw new GroovyRuntimeException("groovy脚本执行异常", e);
        } finally {
            shell.resetLoadedClasses();
        }
    }
}② Java Dynamic Class Loader
核心逻辑:通过javax.tools.JavaCompiler 编辑.java代码【注意类需要random后缀】,然后ClassLoader加载class后实例化。
代码较长,暂不提供。
三、插件化技术
① PF4J
官方文档:https://github.com/pf4j/pf4j
示例demo见:https://github.com/pf4j/pf4j/tree/master/demo
halo项目的最佳实践:https://github.com/halo-dev/halo
依赖引入:
        <dependency>
            <groupId>org.pf4j</groupId>
            <artifactId>pf4j</artifactId>
            <version>3.10.0</version>
        </dependency>