(點選上方公眾號,可快速關註)
來源:琴水玉 ,
www.cnblogs.com/lovesqcc/p/7882991.html
背景與標的
在使用函式介面和列舉實現配置式程式設計(Java與Scala實現),使用了函式介面和列舉實現了配置式程式設計。讀者可先閱讀此文,再來閱讀本文。
http://www.cnblogs.com/lovesqcc/p/6649018.html
有時,需要將一些業務邏輯,使用配置化的方式抽離出來,供業務專家或外部人員來編輯和修改。這樣,就需要將一些程式碼用指令碼的方式實現。在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 (函式語法)
org.codehaus.groovy
groovy-all
2.4.12
從指令碼開始
要實現可配置化,顯然要進行欄位定義。 簡單起見,欄位通常包含三個要素: 標識、標題、欄位邏輯。 採用 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
loadConfigs(List 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
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
fieldConfigMap = new HashMap<>(); static {
try {
List
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
fields = Arrays.asList(“studentId”, “studentName”, “studentAble”);
public static void main(String[] args) {
List
students = getPersons(); List
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
fields) { List
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
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一次。
小結
本文使用了yaml+groovy實現了Java程式碼的可配置化。可配置化的優勢是,可以將一些簡單的邏輯公開給外部編輯和使用,增強了互操作性;而對於複雜邏輯來說,可配置化程式碼的除錯則會比較麻煩。因此,可配置化的度要掌握好。 配置本身就是程式碼,只是配置具有公開化的特點。
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能