【解决方案】如何做数据映射 - hippowc/hippowc.github.io GitHub Wiki

技术设计参考

目前有开源软件atlas-mapping是目前发现的映射做的比较好的,可以参考其数据映射思路。

数据映射的关键问题

  • 源目数据的数据结构描述,如何去定义模型的各个属性,在映射过程中我们需要关注模型和属性的哪些方面?
  • 如何描述映射的数据结构

数据结构描述

在不同用途下,会有不同的属性描述,譬如Java中就使用Field描述一个类的属性,会有所属Class,访问控制等等描述字段,但是这些在数据映射中都使用不到,在数据映射中更关心的是:类型、是否必填、可选值等等。

使用JsonSchema描述

JSON 模式是一种基于 JSON 格式定义 JSON 数据结构的规范。它可以用于描述数据结构,对结构进行验证。Json Schema本身也遵循Json数据结构,是一个Json字符串(自己描述自己,很有意思)。一个例子:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Product",
    "description": "A product from Acme's catalog",
    "type": "object",
    "properties": {
        "id": {
            "description": "The unique identifier for a product",
            "type": "integer"
        },
        "name": {
            "description": "Name of the product",
            "type": "string"
        },
        "price": {
            "type": "number",
            "minimum": 0,
            "exclusiveMinimum": true
        }
    },
    "required": ["id", "name", "price"]
}

$schema	$schema 关键字状态,表示这个模式与 v4 规范草案书写一致。	
title	标题,用来描述结构	
description	描述	
type	类型	.
properties	定义属性	
required	必需属性

Json Schema类型

  • object
  • array
  • string
  • integer
  • number
  • enum

映射结构描述

Atlas内部映射逻辑:

AtlasContextFactory factory = atlasContextFactory = DefaultAtlasContextFactory.getInstance(); 
AtlasContext context = factory.createContext(new File("./my-mapping.xml")); 
AtlasSession session = context.createSession(); 
session.setSourceDocument("myJsonSourceDoc", "{...}"); 
context.process(session); 
Object targetDoc = session.getTargetDocument("myXmlTargetDoc");

扩展性

转换函数

可扩展的转换函数方法:spi实现

Java Spi ServiceLoader使用

在ServiceLoader.load的时候,根据传入的接口类,遍历META-INF/services目录下的以该类命名的文件中的所有类,并实例化返回。

1 定义接口和实现
2 编写 services 实现指定,在 resources 目录下,创建 META-INF/services 文件夹,以接口全路径名
com.github.houbb.forname.Say 为文件名称,内容为对应的实现类全路径。
如果是多个,就直接换行隔开。
3 实现:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        ServiceLoader<Say> loader = ServiceLoader.load(Say.class, classLoader);

        for (Say say : loader) {
            say.say();
        }
4 ServiceLoader是个工具类,需要提供classLoader来实现,从外部加载类可以考虑使用URLClassLoader

Java 中,可以通过 ServiceLoader 类比较方便的找到该类的所有子类实现。META-INF/services 下的实现指定和实现子类实现完全可以和接口定义完全分开。但是每次都要手动创建实现指定文件,比较麻烦

于是有了google 的 auto,可以在编译时自动为我们生成对应的接口实现指定文件。在 target 对应的文件下可以看到。实现原理,也相对简单。通过 java 的编译时注解,生成对应的文件即可。只要在实现类上加注解就可以了

相关技术参考

表达式语言

在java平台使用动态语言,有两种方式:

  • 表达式语言
  • 动态语言

JUEL(Java 统一表达式语言)

Java统一表达式语言(英语:Unified Expression Language,简称JUEL)是一种特殊用途的编程语言,主要的开源实现有:OGNL ,MVEL ,SpEL ,JUEL ,Java Expression Language (JEXL) ,JEval ,Jakarta JXPath 等。

OGNL表达式

OGNL 是 Object-Graph Navigation Language 的缩写,从语言角度来说:它是一个功能强大的表达式语言,用来获取和设置 java 对象的属性 , 它旨在提供一个更高抽象度语法来对 java 对象图进行导航,通过一个“路径”来完成对象信息的导航,这个“路径”可以是到 java bean 的某个属性,或者集合中的某个索引的对象,等等,而不是直接使用 get 或者 set 方法来完成。

当在 web 框架中使用表达式语言的时候,则可以有效的处理这种代码的复杂性。而不需要你,调用 servelet API,类型转换,然后再调用 getter 方法,多数的 Els 都可将这个过程简化为类似于:#session.cart.id 这中更可读的表达式。 表达式:#session.cart.id 与 java 代码不一样的是:没有 java 代码的 get 方法调用和类型转换。

OGNL 的基本语法

OGNL 表达式的基本单位是“导航链”,往往简称为“链”。最简单的链包含如下部分:

  • 属性名称 如上述示例中的 name 和 headline.text
  • 方法调用 hashCode() 返回当前对象的哈希码。
  • 数组元素 listeners[0] 返回当前对象的监听器列表中的第一个元素。

所有的 OGNL 表达式都基于当前对象的上下文来完成求值运算,链的前面部分的结果将作为后面求值的上下文。你的链可以写得很长,例如:

name.toCharArray()[0].numericValue.toString()

使用 OGNL

最简单的使用是直接使用 ognl.Ognl 类来评估一个 OGNL 表达式。 Ognl 类提供一些静态方法用来解析和解释 OGNL 表达式,最简单的示例是不使用上下文从一个对象中获取某个表达式的值

result = ognl.Ognl.getValue(expression, root);

Ognl 类的获取和设置方法也可以接受一个 context map 参数,他允许你放一些自己的变量,并能在 Ognl 表达式中使用。缺省的上下文里只包含 #root 和 #context 两个键。

root.setName("sakura");     
Map context = new HashMap(); 
context.put("who", "Who am i?");   
String who1 = (String)Ognl.getValue("#who", context, root);   
String who2 = (String)Ognl.getValue("#context.who", context, root); 
Object whoExp = Ognl.parseExpression("#who"); 
String who3 = (String)Ognl.getValue(whoExp, context, root); 
String name1 = (String)Ognl.getValue("name", root);  属性引用 name
String name2 = (String)Ognl.getValue("#root.name", root); 变量引用 #key

OGNL性能

OGNL,或者说表达式语言的性能主要又两方面来决定,一个就是对表达式的解析 (Parser),另一个是表达式的执行,OGNL 采用 javaCC 来完成 parser 的实现,在 OGNL 2.7 中又对 OGNL 的执行部分进行了加强,使用 javasisit 来 JIT(Just-In-Time) 的生成 byte code 来完成表达式的执行。

mvel表达式

MVEL全称为:MVFLEX Expression Language,是用来计算Java语法所编写的表达式值的表达式语言。MVEL最初作为Mike Brock创建的 Valhalla项目的表达式计算器。相比最初的OGNL、JEXL和JUEL等项目,而它具有远超它们的性能、功能和易用性 - 特别是集成方面。它不会尝试另一种JVM语言,而是着重解决嵌入式脚本的问题。MVEL的语法很大程度上受到Java语法的启发,但为了使表达式语法更高效,还是有一些基本差异,例如可以像正则表达式一样直接支持集合、数组和字符串匹配的运算。除了表达式语言之外,MVEL还用作配置和字符串构造的模板语言。

使用

MVEL是基于Java语法的表达式语言,具有特定于MVEL的一些明显差异。与Java不同,MVEL是动态类型化(可选类型化),意味着在源代码中不需要类型限定。

<dependency>
    <groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.2.8.Final</version>
</dependency>
Map<String,Object> params = new HashMap<String,Object>();
params.put("x",10);
params.put("y",30);
// 变量都在上下文中
Object result = MVEL.eval("x+y",params);

基本语法

  • 简单属性表达式
user.name
// 计算布尔表达式
user.name =='John Doe'
// MVEL支持所有优先级规则,包括通过括号来控制执行顺序。
(user.name == 'John Doe') && ((x * 2) - 1) > 20

在MVEL中我们称它为属性表达式,因为表达式的唯一目的就是从上下文中提取出变量或者对象的属性。属性表达式是最常见的用途之一

  • 复合语句
//使用分号来表示语句的终止,使用任意数量的语句编写脚本。分号在所有情况下都是必需的,除非在脚本中只有一个语句或最后一个语句。
statement1; statement2; statement3
  • 返回值
// MVEL是被设计为一个集成语言作为核心,允许开发人员提供简单的脚本设置绑定和逻辑。
// MVEL表达式使用“last value out”原则(输出最后值原则)。这意味着,尽管MVEL支持return关键字,但却没必要使用它
a = 10;
b = (a = a * 2) + 10;
a;
// 表达式返回a的值

值判断

在MVEL中所有的判断是否相等,都是对值的判断,而没有对引用的判断,因此表达式foo == 'bar'等价于Java中的foo.equals("bar")。

  • 判断空值
foo == empty
  • 判断Null值
// MVEL中,null和nil都可以用来表示一个Null值
foo == null;
foo == nil; 
  • 强制转换
// 当两个不同类型且没有可比性的值进行比较时,MVEL会应用类型强制转换系统,即将左边的值强制转换成右边的值的类型
"123" == 123;

内联Lists、Maps和数组Arrays

["Bob" : new Person("Bob"), "Michael" : new Person("Michael")]
// 等价于
Map map = new HashMap();
map.put("Bob", new Person("Bob"));
map.put("Michael", new Person("Michael"));
// 用这种结构描述MVEL内部数据结构,功能非常强大,你可以在任何地方使用它
something.someMethod(["foo" : "bar"]);
  • Lists
["Jim", "Bob", "Smith"]
  • Maps
["Foo" : "Bar", "Bar" : "Foo"]
  • 数组Arrays
{"Jim", "Bob", "Smith"}
  • 数组强制转换
// 它可以被强制转换成其它类型的数组,当你声明一个数组时,是不直接指定其类型的,
// 但你可以通过将其传递给一个接收int[]类型参数的方法来指定。
foo.someMethod({1,2,3,4});

属性导航

MVEL属性导航遵循在其他语言(如Groovy,OGNL,EL等)中bean属性表达式中公认惯例的使用方式。和其它语言必须通过底层的方法来控制权限不同的是,MVEL提供了一种单一的,统一的语法来访问属性,静态字段和maps等。

  • Bean属性
// 大多数java开发者可能会通过下面的方式访问一个对象的属性
user.getManager().getName();
// 可以使用以下表达式访问相同的属性
user.manager.name
  • Bean的安全属性导航
// 有时,当你的表达式中会含有null元素时,这时就需要你进行一个为空判断
user.?manager.name
// 相当于
if (user.manager != null) { return user.manager.name; } else { return null; }
  • 集合
// List的访问
user[5]
// Map的访问
user["foobar"]
// 字符串作数组
foo = "My String";
foo[0]; 

文字常量

  • 字符串常量
// 字符串常量可以用一对单引号或一对双引号来界定
"This is a string literal"
'This is also string literal'
  • 数字常量
125 // 十进制
0353 // 八进制
0xAFF0 // 十六进制
  • 浮点型常量
10.503 // double型
94.92d // double型
14.5f // float型
  • 大数字常量
104.39484B // BigDecimal
8.4I // BigInteger
  • 布尔型常量用保留关键字true和false来表示。
  • 空常量 用null或nil来表示。

类型常量

类型常量的处理方式与Java中的相同,格式为:”.“

嵌套类不能通过MVEL 2.0中的标准点表示法(如Java中)来访问。 相反,你必须用$符号限定这些类。

org.proctor.Person$BodyPart

流程控制

  • If-Then-Else
if (var > 0) {
   System.out.println("Greater than zero!");
} else if (var == -1) { 
   System.out.println("Minus one!");
} else { 
   System.out.println("Something else!");
}
  • 三目运算符
var > 0 ? "Yes" : "No";
  • Foreach
foreach (name : people) {
   count++;
   System.out.println("Person #" + count + ":" + name);
}
  • for循环
for (int i =0; i < 100; i++) { 
   System.out.println(i);
}
  • While, Do Until
do { 
   x = something();
} 
while (x != null);

do {
   x = something();
}
until (x == null);
  • While, Until
while (isTrue()) {
   doSomething();
}

until (isFalse()) {
   doSomething();
}

投影和交集

投影是一种描述集合的方式。 通过非常简单的语法,您可以检索集合中非常复杂的对象模型。你有一个User对象的集合。 每个对象都有一个Parent。 现在你想获得集合users中的所有parent的name的列表(假设Parent中有字段name)

parentNames = (parent.name in users);

甚至可以执行嵌套操作,假设,User对象有个集合成员叫做familyMembers,现在我们想获得一个所有家庭成员姓名的集合:

familyMembers = (name in (familyMembers in users));

赋值

MVEL允许你对表达式中的变量进行赋值,以便在运行时获取,或在表达式内部使用。因为MVEL是动态类型语言,所以你不必为了声明一个变量而指定其类型。当然,你也可以选择指定。

str =“My String”; // valid
String str =“My String”; // valid

与java语言不同的是,当给一个指定类型的变量赋值时,MVEL会提供自动的类型转换

函数定义

MVEL可以使用def或function关键字来定义本地函数

def hello() { System.out.println("Hello!"); }
hello(); // 调用函数

def addTwo(a, b) { 
   a + b;
}
  • 闭包
// 定义一个接收一个参数的函数
def someFunction(f_ptr) { f_ptr(); }

// 定义变量a
var a = 10;

// 传递函数闭包
someFunction(def { a * 10 });

Lambda表达式

上面的例子定义了一个Lambda,并将其赋值给变量”threshold”.Lambda实质上就是一个用来给变量赋值的函数,也是闭包。

threshold = def (x) { x >= 10 ? x : 0 };
result = cost + threshold(lowerBound);
⚠️ **GitHub.com Fallback** ⚠️