文章

Java低代码、配置化、DSL总结

技术能力对比

序号

技术能力

技术实战

应用场景

1

表达式引擎

aviator、SpringEL、Groovy

轻量级:简单的求值场景

3

动态脚本

Groovy、Java Dynamic Class Loader

中量级:涉及大块复杂逻辑,涉及到代码块级别变化且需要调接口级别api。

3

插件化技术

PF4J

重量级:偏模块化的功能

一、表达式引擎

应用场景总结:针对于需要进行脚本求值场景,性能上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>