当前位置: 首页 > article >正文

AS技术探索

AS技术探索

一.在as中使用java中自定义方法

导入单一的自定义方法有不少

1.自定义函数

  • 1.1 参数固定:继承AbstractFunction类并override相应方法即可。

    public class TestAviator {
        public static void main(String[] args) {
            //注册函数
            AviatorEvaluator.addFunction(new AddFunction());
            System.out.println(AviatorEvaluator.execute("add(1, 2)"));           // 3.0
            System.out.println(AviatorEvaluator.execute("add(add(1, 2), 100)")); // 103.0
        }
    }



    class AddFunction extends AbstractFunction {
        @Override
        public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
            Number left = FunctionUtils.getNumberValue(arg1, env);
            Number right = FunctionUtils.getNumberValue(arg2, env);
            return new AviatorDouble(left.doubleValue() + right.doubleValue());
        }
        public String getName() {
            return "add";
        }
    }
  • 1.2.参数不固定:继承 AbstractVariadicFunction 类,只要实现其中的 variadicCall 方法即可

举例:实现一个找到第一个参数不为 null 的函数

public class GetFirstNonNullFunction extends AbstractVariadicFunction {

    public AviatorObject variadicCall(Map<String, Object> env, AviatorObject... args) {
        if (args != null) {
            for (AviatorObject arg : args) {
                if (arg.getValue(env) != null) {
                    return arg;
                }
            }
        }
        return new AviatorString(null);
    }


    @Override
    public String getName() {
        return "getFirstNonNull";
    }

}

2.使用Java类方法作为自定义方法

  • 2.1 导入类中静态方法
AviatorEvaluator.addStaticFunctions("str", StringUtils.class);
str.isBlank('')
  • 2.2 导入类中实例方法
AviatorEvaluator.addInstanceFunctions("s", String.class);
s.indexOf("hello", "l"); ## 这里传了一个对象
  • 2.3 使用可变参数方法

先通过静态导入方式将方法导入

  public static String join(final String... args) {
    if (args == null || args.length == 0) {
      return "";
    }
    StringBuilder sb = new StringBuilder();
    boolean wasFirst = true;
    for (int i = 0; i < args.length; i++) {
      if (wasFirst) {
        sb.append(args[i]);
        wasFirst = false;
      } else {
        sb.append(",").append(args[i]);
      }
    }
    return sb.toString();
  }

在使用上面方式导入后,在表达式里必须先用 seq.array 创建数组来调用:

test.join(seq.array(java.lang.String, 'hello','dennis'))
  • 2.4 批量方法导入,直接导入java类下所有的方法(重点)
AviatorEvaluator.importFunctions(StringUtils.class);

这样就可以用使用类下所有的静态方法和实例方法了

定制化:

如果需要定制导入的 namespace 和范围,可以对 java 类使用 Import 标注:

@Import(ns = "test", scopes = {ImportScope.Static})
public class StringUtils {
  ...
 
}

ns 指定导入后的 namespace, scopes 指定导入的方法范围。如果想忽略某个方法,可以用 Ignore 标注:

@Import(ns = "test", scopes = {ImportScope.Static})
public class StringUtils {
  ...

  @Ignore
  public static double test(final double a, final double b) {
    return a + b;
  }
}

同时可以用 Function 标注导入的方法名字,默认都是原来的方法名:

@Import(ns = "test", scopes = {ImportScope.Static})
public class StringUtils {
  ...

  @Function(rename = "is_empty")
  public boolean isEmpty(final String s) {
    return s.isEmpty();
  }
}

二.在as中使用java中变量或bean

携带参数导入一个hashmap。env中

    @Test
    public void dateTest(){
        Map<String, Object> env = new HashMap<String, Object>();
        final Date date = new Date();
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS").format(date);
        env.put("date", date);
        env.put("dateStr", dateStr);
        Boolean result = (Boolean) AviatorEvaluator.execute("date==dateStr", env);
        System.out.println(result);  // true
        result = (Boolean) AviatorEvaluator.execute("date > '2010-12-20 00:00:00:00' ", env);
        System.out.println(result);  // true
        result = (Boolean) AviatorEvaluator.execute("date < '2200-12-20 00:00:00:00' ", env);
        System.out.println(result);  // true
        result = (Boolean) AviatorEvaluator.execute("date==date ", env);
        System.out.println(result);  // true
    }

bean服务也是

    @Test
    public void db2ServiceTest() throws IOException, IllegalAccessException, NoSuchMethodException, SQLException {
        //注册函数
        AviatorEvaluator.importFunctions(DoctorService.class);
        Map<String, Object> env = new HashMap<String, Object>();
        DoctorService doctorService = new DoctorService("123");
        env.put("doctor", doctorService);

        AviatorEvaluator.execute("doctor.query()");
//        AviatorEvaluator.getInstance().compileScript("doctor.query()").execute();
    }

*三.更优雅的使用as

1.bean服务导入

如果需要用到很多bean服务,那么就需要一个个往env中进行数据填充,代码写死扩展性差

  • 解决方案
  1. 只导入这个bean工具实例就实现动态按需获取其他bean服务

工具类实现

/**
 * 将bean都注入到其中
 */
@Component
public class BeanUtil implements ApplicationContextAware {

    private ApplicationContext applicationContext;
    private static List<avServiceEnums> asList = new ArrayList();
    static {
        asList = Arrays.asList(avServiceEnums.values());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public <T> T getBean(Class<T> cls) throws ClassNotFoundException {
        return (T) applicationContext.getBean(cls);
    }

    public <T> T getBean(String key) throws ClassNotFoundException {
        for (avServiceEnums o : asList) {
            String serviceKey = o.getKey();
            if (StringUtils.equals(key, serviceKey)) {
                return (T) getBean(o.getServiceClass());
            }
        }

        return null;
    }
}

2.定义枚举类,每次as都通过该key获取对应服务,每加入一个服务就加入一个枚举,也是一个说明文档和流程化处理

@Getter
public enum avServiceEnums {
    HERB_ORDER_SERVICE("herbOrderService", HerbOrderDao.class, "订单表服务"),
    CYCLE_REWARD_SERVICE("cycleRewardService", CycleRewardDao.class, "周期奖励表服务"),
    DAY_REWARD_SERVICE("dayRewardService", DayRewardDao.class, "日度期奖励表服务"),
    MONTH_REWARD_SERVICE("monthRewardService", MonthRewardDao.class, "月度奖励表服务"),
    ;

    private final String key;
    private final Class serviceClass;
    private final String desc;


    avServiceEnums(String key, Class aClass, String desc) {
        this.key = key;
        this.serviceClass = aClass;
        this.desc = desc;
    }
}

3.使用

    @Test
    public void simpleTest() throws IOException, IllegalAccessException, NoSuchMethodException, SQLException {
        Map<String, Object> env = new HashMap<String, Object>();
        env.put("beanUtil", beanUtil);

        System.out.println(aviatorEvaluator.compileScript("examples/test.av").execute(env));
    }
return BeanUtil.getBean(beanUtilService, "dayRewardService");

返回结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.根据as文件去导入相关类

在as中可标注use java.lang.System;去动态标注导入了什么类,这里把主动权交给了客户,否则我们需要去考虑和思考客户可能用到的所有类一个个导入,想当于写死代码,扩展性差

use java.lang.System;

return BeanUtilService.getBean(beanUtilService, "dayRewardService");

那我们需要去解除这个问题,解析as代码并动态导入类

对as代码,它可能注入的是文本也可能是一个文件,我解析获取所有use xx.xx.xx.xx.{xx,xx};并动态按照类名导入

工具类:

package cn.glfs.as.util;

import com.googlecode.aviator.AviatorEvaluatorInstance;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class AsUtil {
    /**
     * 运行as文本
     * @param param:携带的参数例如doctorId
     * @param unknownAsScript:as代码字符串
     * @param beanUtilService:bean生成服务实例
     * @param aviatorEvaluator :AviatorEvaluatorInstance实例
     * @return as中实际返回值
     */
    public static <P> Object calculateByText(P param, String unknownAsScript, BeanUtilService beanUtilService, AviatorEvaluatorInstance aviatorEvaluator) throws IOException, ClassNotFoundException, IllegalAccessException, NoSuchMethodException {
        Map<String, Object> env = new HashMap<String, Object>();
        env.put("param", param);
        env.put("beanUtilService", beanUtilService);
        List<String> strings = StringSplit(unknownAsScript);
        strings = extractClassesFromUseStatements(strings);

        for (String s : strings) {
            // todo 这里可能出现项目中找不到的类情况
            Class<?> aClass = Class.forName(s);
            aviatorEvaluator.importFunctions(aClass);
        }
        return aviatorEvaluator.compile(unknownAsScript).execute(env);
    }

    /**
     * 运行as文件
     * @param param:携带的参数例如doctorId
     * @param filePath:as文件路径
     * @param beanUtilService:bean生成服务实例
     * @param aviatorEvaluator :AviatorEvaluatorInstance实例
     * @return as中实际返回值
     */
    public static <P> Object calculateByFilePath(P param, String filePath, BeanUtilService beanUtilService, AviatorEvaluatorInstance aviatorEvaluator) throws IOException, ClassNotFoundException, IllegalAccessException, NoSuchMethodException {
        Map<String, Object> env = new HashMap<String, Object>();
        env.put("param", param);
        env.put("beanUtilService", beanUtilService);

        List<String> strings = readAviatorScriptFile(filePath);
        strings = extractClassesFromUseStatements(strings);

        for (String s : strings) {
            // todo 这里可能出现项目中找不到的类情况
            Class<?> aClass = Class.forName(s);
            aviatorEvaluator.importFunctions(aClass);
        }
        return aviatorEvaluator.compileScript(filePath).execute(env);
    }


    /**
     * 解析字符串数组中需要导入的类
     * @param lines
     * @return
     */
    public static List<String> extractClassesFromUseStatements(List<String> lines) {
        HashSet<String> classNames = new HashSet<>();

        // 处理 {xx, xx} 格式的正则表达式
        Pattern patternWithBrackets = Pattern.compile("use (.+?)\{(.+?)\};");
        // 处理单个类的正则表达式
        Pattern patternSingleClass = Pattern.compile("use (.+?);");

        for (String line : lines) {
            if (line.startsWith("use")) {
                Matcher matcherWithBrackets = patternWithBrackets.matcher(line);
                Matcher matcherSingleClass = patternSingleClass.matcher(line);

                if (matcherWithBrackets.find()) {
                    // 处理 {xx, xx} 格式的情况
                    String packagePrefix = matcherWithBrackets.group(1);
                    String contentInBrackets = matcherWithBrackets.group(2);
                    String[] splitClasses = contentInBrackets.split(",");
                    for (String className : splitClasses) {
                        className = className.trim();
                        classNames.add(packagePrefix + className);
                    }
                } else if (matcherSingleClass.find()) {
                    // 处理单个类的情况
                    String fullClassName = matcherSingleClass.group(1);
                    classNames.add(fullClassName);
                }
            }
        }

        List<String> collect = classNames.stream().collect(Collectors.toList());
        return collect;
    }


    /**
     * 将字符串按照;分割,聚合为一个字符串文本
     * @param text
     * @return
     */
    public static List<String> StringSplit(String text) {
        String[] parts = text.split(";");
        List<String> resultList = new ArrayList<>();
        for (int i = 0; i < parts.length - 1; i++) {
            resultList.add(parts[i] + ";");
        }
        resultList.add(parts[parts.length - 1]);
        return resultList;
    }

    /**
     * 读取as文件按行获取String数组
     * @param filePath
     * @return
     */
    public static List<String> readAviatorScriptFile(String filePath) {
        StringBuilder content = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine())!= null) {
                content.append(line).append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return StringSplit(content.toString());
    }

}

调用:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class AwardTest {
    @Setter(onMethod_ = {@Autowired})
    private AviatorEvaluatorInstance aviatorEvaluator;

    @Autowired
    private BeanUtilService beanUtilService;

    @Test
    public void daoSimpleFileTest() throws IOException, IllegalAccessException, NoSuchMethodException, SQLException, ClassNotFoundException {
        String param = "1";
        String asScriptPath = "/Users/wangzhikang/IdeaProjects/untitled1/src/main/resources/examples/test.av";
        Object o = AsUtil.calculateByFilePath(param, asScriptPath, beanUtilService, aviatorEvaluator);
        System.out.println(o);
    }


    @Test
    public void simpleTextTest() throws IOException, IllegalAccessException, NoSuchMethodException, SQLException, ClassNotFoundException {
        String param = "1";
        String asScriptText = "use java.lang.System;return System.currentTimeMillis();";
        Long calculate = (Long)AsUtil.calculateByText(param, asScriptText, beanUtilService, aviatorEvaluator);
        System.out.println(calculate);
    }
}

当然另一种方法我也做了实现————统一导入包下所有类,这里我将AviatorEvaluatorInstance作为一个bean作为上下文使用,这样可以直接一次性导入客户所有可能使用的所有类

package cn.glfs.as.config;

import cn.glfs.as.util.BeanUtilService;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class AsConfig {
    @Bean
    AviatorEvaluatorInstance init() throws IllegalAccessException, NoSuchMethodException, IOException, ClassNotFoundException {
        AviatorEvaluatorInstance instance = AviatorEvaluator.getInstance();
        instance.importFunctions(BeanUtilService.class);

        String packageToScan = "cn.glfs.as";
        List<Class<?>> classes = getClasses(packageToScan);
        for (Class<?> clazz : classes) {
            instance.importFunctions(clazz);
        }

        // instance.importFunctions(System.class);
        return instance;
    }


    /**
     * 包解析获取其中所有类对象
     * @param packageName
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private static List<Class<?>> getClasses(String packageName) throws IOException, ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        String path = packageName.replace('.', '/');
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL resource = classLoader.getResource(path);
        if (resource != null) {
            File directory = new File(resource.getFile());
            if (directory.exists()) {
                File[] files = directory.listFiles();
                if (files != null) {
                    for (File file : files) {
                        if (file.isDirectory()) {
                            List<Class<?>> subClasses = getClasses(packageName + "." + file.getName());
                            classes.addAll(subClasses);
                        } else if (file.getName().endsWith(".class")) {
                            String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
                            Class<?> clazz = Class.forName(className);
                            classes.add(clazz);
                        }
                    }
                }
            }
        }
        return classes;
    }
}


http://www.kler.cn/a/380769.html

相关文章:

  • 【Android】Gradle 7.0+ 渠道打包配置
  • jenkins 构建报错 Cannot run program “sh”
  • 【Nginx】前端项目开启 Gzip 压缩大幅提高页面加载速度
  • 使用 SSH 蜜罐提升安全性和记录攻击活动
  • LeetCode46. 全排列(2024秋季每日一题 57)
  • MySQL之JDBC入门详解
  • 设计模式之结构型模式---装饰器模式
  • ubuntu22.04 docker-compose搭建apisix高可用
  • Spring框架的事务管理
  • 868历年真题算法设计题+程序设计题
  • leetcode-3-无重复字符的最长子串
  • Pr 视频效果:过渡
  • 使用Python Flask实战构建Web应用
  • 告别传统营销,HubSpot AI分析工具带你玩转新潮流
  • BERT预训练的MLM和NSP任务的损失函数都是什么?
  • 一文快速预览经典深度学习模型(一)——CNN、RNN、LSTM、Transformer、ViT
  • 架构师之路-学渣到学霸历程-43
  • 只允许指定ip远程连接ssh
  • 【3】流程控制
  • HarmonyOS鸿蒙开发入门,常用ArkUI组件学习(一)
  • Spring cloud
  • QT下载安装
  • 为什么要使用Docker?
  • c# 值类型
  • 青少年编程与数学 02-003 Go语言网络编程 02课题、网络分层模型
  • RHCE selinux 和 防火墙(fireword|iptable)