使用 yaml+groovy 实现 Java 代码可配置化

栏目: Java · 发布时间: 6年前

内容简介:使用 yaml+groovy 实现 Java 代码可配置化

背景与目标

使用函数接口和枚举实现配置式编程(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一次。

小结

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入浅出强化学习:原理入门

深入浅出强化学习:原理入门

郭宪、方勇纯 / 电子工业出版社 / 2018-1 / 79

《深入浅出强化学习:原理入门》用通俗易懂的语言深入浅出地介绍了强化学习的基本原理,覆盖了传统的强化学习基本方法和当前炙手可热的深度强化学习方法。开篇从最基本的马尔科夫决策过程入手,将强化学习问题纳入到严谨的数学框架中,接着阐述了解决此类问题最基本的方法——动态规划方法,并从中总结出解决强化学习问题的基本思路:交互迭代策略评估和策略改善。基于这个思路,分别介绍了基于值函数的强化学习方法和基于直接策略......一起来看看 《深入浅出强化学习:原理入门》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具