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中进行数据填充,代码写死扩展性差
- 解决方案
- 只导入这个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;
}
}