背景与目标###

使用函数接口和枚举实现配置式编程(Java与Scala实现),使用了函数接口和枚举实现了配置式编程。读者可先阅读此文,再来阅读本文。

有时,需要将一些业务逻辑,使用配置化的方式抽离出来,供业务专家或外部人员来编辑和修改。这样,就需要将一些代码用脚本的方式实现。在Java语言体系中,与Java粘合比较紧密的是Groovy语言,本例中,将使用Groovy实现Java代码的可配置化。

目标: 指定字段集合,可输出指定对象的相应字段的值。实现可配置化目标。

方法:使用groovy的语法和脚本实现相应功能,然后集成到Java应用中。

实现###

本文的示例代码都可以在工程 https://github.com/shuqin/ALLIN 下的包 zzz.study.groovy 下找到并运行。 记得安装 lombok 插件以及调整运行时到Java8。

依赖JAR包####

本文依赖如下Jar包:groovy-all, fastjson, yamlbeans, lombok ,以及 Java8 (函数语法)

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.12</version>
</dependency> <dependency>
<groupId>com.esotericsoftware.yamlbeans</groupId>
<artifactId>yamlbeans</artifactId>
<version>1.09</version>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.36</version>
</dependency>

从脚本开始####

要实现可配置化,显然要进行字段定义。 简单起见,字段通常包含三个要素: 标识、标题、字段逻辑。 采用 yaml + groovy 的方式来实现。放在 src/main/resources/scripts/ 下。 如下所示:

name: studentId
title: 学生编号
script: |
stu.studentId
name: studentName
title: 学生姓名
script: |
stu.name
name: studentAble
title: 特长
script: |
stu.able

字段配置的定义类 :

package zzz.study.groovy;

import lombok.Data;

/**
* Created by shuqin on 17/11/22.
*/
@Data
public class ReportFieldConfig { /** 报表字段标识 */
private String name; /** 报表字段标题 */
private String title; /** 报表字段逻辑脚本 */
private String script; }

配置解析####

接下来,需要编写配置解析器,将配置文件内容加载到内存,建立字段映射。 配置化的核心,实际就是建立映射关系。

YamlConfigLoader 实现了单个配置内容的解析。

package zzz.study.groovy;

import com.alibaba.fastjson.JSON;
import com.esotericsoftware.yamlbeans.YamlReader; import java.util.List;
import java.util.stream.Collectors; /**
* Created by yuankui on 17/6/13.
*/
public class YamlConfigLoader { public static ReportFieldConfig loadConfig(String content) {
try {
YamlReader reader = new YamlReader(content);
Object object = reader.read();
return JSON.parseObject(JSON.toJSONString(object), ReportFieldConfig.class);
} catch (Exception e) {
throw new RuntimeException("load config failed:" + content, e);
}
} public static List<ReportFieldConfig> loadConfigs(List<String> contents) {
return contents.stream().map(YamlConfigLoader::loadConfig).collect(Collectors.toList());
}
}

YamlConfigDirLoader 从指定目录下加载所有配置文件,并使用 YamlConfigLoader 建立所有字段的映射关系。实际工程应用中,通常是将配置保存在DB中,并从DB里读取配置。

package zzz.study.groovy;

import org.springframework.util.StreamUtils;

import java.io.File;
import java.io.FileInputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors; /**
* Created by shuqin on 17/11/23.
*/
public class YamlConfigDirLoader { private String dir; public YamlConfigDirLoader(String dir) {
this.dir = dir;
} public List<ReportFieldConfig> loadConfigs() {
File[] files = new File(dir).listFiles();
return Arrays.stream(files).map(
file -> {
try {
String
content =
StreamUtils.copyToString(new FileInputStream(file), Charset.forName("utf-8"));
return YamlConfigLoader.loadConfig(content);
} catch (java.io.IOException e) {
System.err.println(e.getMessage());
throw new RuntimeException(e);
}
}
).collect(Collectors.toList());
} }

FieldsConfigLoader 在应用启动的时候,调用 YamlConfigDirLoader 的能力加载所有配置文件。

package zzz.study.groovy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.HashMap;
import java.util.List;
import java.util.Map; /**
* Created by shuqin on 17/11/22.
*/
public class FieldsConfigLoader { private static Logger logger = LoggerFactory.getLogger(FieldsConfigLoader.class); private static Map<String, ReportFieldConfig> fieldConfigMap = new HashMap<>();
static {
try {
List<ReportFieldConfig> fieldConfigs = new YamlConfigDirLoader("src/main/resources/scripts/").loadConfigs();
fieldConfigs.forEach(
fc -> fieldConfigMap.put(fc.getName(), fc)
);
logger.info("fieldConfigs: {}", fieldConfigs);
} catch (Exception ex) {
logger.error("failed to load fields conf", ex);
} } public static ReportFieldConfig getFieldConfig(String name) {
return fieldConfigMap.get(name);
} }

客户端集成####

package zzz.study.groovy;

import groovy.lang.Binding;
import groovy.lang.GroovyShell; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors; import zzz.study.function.basic.Person;
import zzz.study.function.basic.Student; /**
* Created by shuqin on 17/11/23.
*/
public class StudentOutput { static List<String> fields = Arrays.asList("studentId", "studentName", "studentAble"); public static void main(String[] args) {
List<Person> students = getPersons();
List<String> stundentInfos = students.stream().map(
p -> getOneStudentInfo(p, fields)
).collect(
Collectors.toList());
System.out.println(String.join("\n", stundentInfos));
} private static String getOneStudentInfo(Person p, List<String> fields) {
List<String> stuInfos = new ArrayList<>();
fields.forEach(
field -> {
ReportFieldConfig fieldConfig = FieldsConfigLoader.getFieldConfig(field);
Binding binding = new Binding();
binding.setVariable("stu", p);
GroovyShell shell = new GroovyShell(binding);
Object result = shell.evaluate(fieldConfig.getScript());
//System.out.println("result from groovy script: " + result);
stuInfos.add(String.valueOf(result));
}
);
return String.join(",", stuInfos);
} private static List<Person> getPersons() {
Person s1 = new Student("s1", "liming", "Study");
Person s2 = new Student("s2", "xueying", "Piano");
return Arrays.asList(new Person[]{s1, s2});
} }

这里使用了 GroovyShell, Binding 的基本功能来运行 groovy 。虽然例子中只是简单的取属性值,实际上还可以灵活调用传入对象的方法,展示更复杂的业务逻辑。比如 stu.name 还可写成 stu.getName() 。

运行后得到如下结果:

 s1,liming,Study
s2,xueying,Piano

至此,DEMO 完成。实际工程集成的时候,需要先将所有字段定义的脚本配置加载到内存并解析和缓存起来,在需要的时候直接使用,而不会像demo里每个字段都new一次。

脚本缓存####

Groovy 脚本每次运行都会生成一个新的类。开销比较大,需要进行缓存。


@Component("scriptExecutor")
public class ScriptExecutor { private static Logger logger = LoggerFactory.getLogger(ScriptExecutor.class); private LoadingCache<String, GenericObjectPool<Script>> scriptCache; @Resource
private GlobalConfig globalConfig; @PostConstruct
public void init() {
scriptCache = CacheBuilder
.newBuilder().build(new CacheLoader<String, GenericObjectPool<Script>>() {
@Override
public GenericObjectPool<Script> load(String script) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(globalConfig.getCacheMaxTotal());
poolConfig.setMaxWaitMillis(globalConfig.getMaxWaitMillis());
return new GenericObjectPool<Script>(new ScriptPoolFactory(script), poolConfig);
}
});
logger.info("success init scripts cache.");
} public Object exec(String scriptPassed, Binding binding) {
GenericObjectPool<Script> scriptPool = null;
Script script = null;
try {
scriptPool = scriptCache.get(scriptPassed);
script = scriptPool.borrowObject();
script.setBinding(binding);
Object value = script.run();
script.setBinding(null);
return value;
} catch (Exception ex) {
logger.error("exxec script error: " + ex.getMessage(), ex);
return null;
} finally {
if (scriptPool != null && script != null) {
scriptPool.returnObject(script);
}
} } }

小结###

本文使用了yaml+groovy实现了Java代码的可配置化。可配置化的优势是,可以将一些简单的逻辑公开给外部编辑和使用,增强了互操作性;而对于复杂逻辑来说,可配置化代码的调试则会比较麻烦。因此,可配置化的度要掌握好。 配置本身就是代码,只是配置具有公开化的特点。

使用yaml+groovy实现Java代码可配置化的更多相关文章

  1. [译]17-spring基于java代码的配置元数据

    spring还支持基于java代码的配置元数据.不过这种方式不太常用,但是还有一些人使用.所以还是很有必要介绍一下. spring基于java代码的配置元数据,可以通过@Configuration注解 ...

  2. Groovy学习:第一章 用Groovy简化Java代码

    1. Groovy的安装 目前Groovy的最新版本为2.1.2版,下载地址为:http://groovy.codehaus.org/Download下载后解压groovy-binary-2.1.2. ...

  3. 使用java代码动态配置与xml文件结合的方式使用mybatis-generator生成代码配置

    1.使用java代码动态配置与xml文件结合的方式使用mybatis-generator生成代码配置 2.上代码:在resources目录下新建:generatorConfiguration.xml文 ...

  4. EditPlus编辑java代码 常规配置

  5. 基于纯Java代码的Spring容器和Web容器零配置的思考和实现(3) - 使用配置

    经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - ...

  6. 转!!Java代码规范、格式化和checkstyle检查配置文档

    为便于规范各位开发人员代码.提高代码质量,研发中心需要启动代码评审机制.为了加快代码评审的速度,减少不必要的时间,可以加入一些代码评审的静态检查工具,另外需要为研发中心配置统一的编码模板和代码格式化模 ...

  7. JAVA版Kafka代码及配置解释

    伟大的程序员版权所有,转载请注明:http://www.lenggirl.com/bigdata/java-kafka.html.html 一.JAVA代码 kafka是吞吐量巨大的一个消息系统,它是 ...

  8. Spring MVC框架下在java代码中访问applicationContext.xml文件中配置的文件(可以用于读取配置文件内容)

    <bean id="propertyConfigurer" class="com.****.framework.core.SpringPropertiesUtil& ...

  9. Java代码规范、格式化和checkstyle检查配置文档

    http://www.blogjava.net/amigoxie/archive/2014/05/31/414287.html 文件下载: http://files.cnblogs.com/files ...

随机推荐

  1. 【PyQt5-Qt Designer】QSlider滑块

    QSlider滑块 QSlider简介 QSlider小部件提供了一个垂直或水平滑块. 滑块是控制有界值的经典控件.它允许用户沿水平或垂直凹槽移动滑块手柄,并将手柄的位置转换为合法范围内的整数值. Q ...

  2. 【JMeter】【微信好文收藏】Jmeter接口测试实战-有趣的cookie

    场景: 接口测试时常都需要登录,请求方式(post), 登录常用的方法有通过获取token, 获取session, 获取cookie, 等等. 这几种都有一个共同的特点, 有效期(expires). ...

  3. 前端HTML目录

    前端 HTML 简介 前端 HTML文档结构介绍 前端 HTML文档 详解 前端 HTML 注释 前端 HTML标签介绍 前端 HTML的规范 前端 HTML 常用标签 head标签相关内容 前端 H ...

  4. dbdeployer安装TokuDB MySQL

    下载最新的dbdeployer1.6.0,使用非root账户安装dbdeployer,特别是mv的时候. 1,解压 dbdeployer unpack Percona-Server-5.7.22-22 ...

  5. Python3学习之路~6.5 私有属性和私有方法

    属性分为静态属性和动态属性,静态属性就是变量,动态属性就是方法.但是一般我们说的属性就是变量,方法就是方法.私有属性/方法就是外面访问不了,只有自己能够访问的属性/方法.如何将属性和方法分别变成私有属 ...

  6. open '/dev/hwlog_switch' fail -1, 13. Permission denied

    https://blog.csdn.net/qq_36317441/article/details/79376522 将HBuilder开发的APP运行在华为手机上时,控制台显示 open '/dev ...

  7. 【剑指offer】复杂链表的复制

    一.题目: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用, ...

  8. Redis入门到高可用(八)——list

    一.结构 key - value 结构,value是一个有序队列. 可进行左边的添加及弹出,右边的添加及弹出. 可获取列表的长度,删除列表中指定元素,获取列表的子列表,按照索引获取列表的指定元素. 特 ...

  9. 虚拟机开启时 VMware Authorization Service 这个服务找不到的解决办法

    有些时候我们启动虚拟机 会出现 The VMware Authorization Service is not running 正常情况下我们只要进 我的电脑-------> 管理------- ...

  10. Golang--Hello World

    //1)go语言以包作为管理单位 //2)每个文件必须先声明包 //3)程序必须有一个main包 package main import "fmt" //入口函数 func mai ...