camel系列-如何调用类静态字段的方法做数据转换

背景

在使用 mapstruct 框架进行数据转换时,可以有 2 种方法进行对象类型转换,第一种是通过将对象注入到 Spring 容器中进行调用,这种方法则强依赖 Spring 容器;第二种声明一个静态的转换器实例字段,这种方法则不依赖 Spring,调用比较方便,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Mapper(componentModel = "cdi")
public interface CarMapper {

CarDto carToCarDto(Car car);
}

@Mapper
public interface GolfPlayerMapper {

GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class );

GolfPlayerDto toDto(GolfPlayer player);

GolfPlayer toPlayer(GolfPlayerDto player);

}

案例解析

根据以上场景,我们在做数据转换时,需要在 camel 的 dsl 中,通过获取类静态字段作为对象,再进行方法调用。

首先看一下常规的数据方式实现

示例数据如下

  1. 转换的数据类型定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Data
public class EmployeeBean {
private String name;
}

@Data
public class PersonBean {

private String name;
}

public interface EmployeeConverter {

EmployeeConverter converter = new EmployeeConverterImpl();

EmployeeBean convert(PersonBean personBean);
}

public class EmployeeConverterImpl implements EmployeeConverter {

public EmployeeBean convert(PersonBean personBean) {
EmployeeBean employeeBean = new EmployeeBean();
employeeBean.setName(personBean.getName());
return employeeBean;
}
}
  1. 转换的 DSL 定义

可以由 2 种方式实现

  • 第一种,直接调用具体的实现类 EmployeeConverterImpl,缺点是暴露了接口的实现类
  • 第二种,将 EmployeeConverterImpl 类注入到容器中,使用注入的实例引用调用 employeeConverter,缺点是依赖 Spring 容器
1
2
3
4
5
6
7
8
9
10
11
12
13
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<bean method="convert" beanType="org.apache.camel.spring.converter.model.EmployeeConverterImpl"></bean>
</route>

<bean id="employeeConverter" class="org.apache.camel.spring.converter.model.EmployeeConverterImpl"/>

<route>
<from uri="direct:start"/>
<bean method="convert" ref="employeeConverter"></bean>
</route>
</camelContext>

除了以上方法,最直接的方式是如何在不依赖 Spring 的情况下,直接获取 EmployeeConverter 的静态字段 converter?

方案

首先要解决的问题是如何获取类的静态字段,通过 simple 脚本表达式的 type 前缀,可以获取字段的结果,如下示例

employeeConverter 设置的

1
2
3
4
5
6
7
8
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<setHeader name="employeeConverter">
<simple>${type:org.apache.camel.spring.converter.model.EmployeeConverter.converter}</simple>
</setHeader>
</route>
</camelContext>

type 前缀的表达式实现逻辑如下,发现实际返回的是字段的 toString 结果(lookupConstantFieldValue 方法实现),不满足诉求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public static Expression typeExpression(final String name) {
return new ExpressionAdapter() {
private ClassResolver classResolver;
private Expression exp;

@Override
public void init(CamelContext context) {
classResolver = context.getClassResolver();
exp = ExpressionBuilder.simpleExpression(name);
exp.init(context);
}

public Object evaluate(Exchange exchange) {
// it may refer to a class type
String text = exp.evaluate(exchange, String.class);
Class<?> type = classResolver.resolveClass(text);
if (type != null) {
return type;
}

int pos = text.lastIndexOf('.');
if (pos > 0) {
String before = text.substring(0, pos);
String after = text.substring(pos + 1);
type = classResolver.resolveClass(before);
if (type != null) {
return ObjectHelper.lookupConstantFieldValue(type, after);
}
}

throw CamelExecutionException.wrapCamelExecutionException(exchange,
new ClassNotFoundException("Cannot find type " + text));
}

@Override
public String toString() {
return "type:" + name;
}
};
}

public static String lookupConstantFieldValue(Class<?> clazz, String name) {
if (clazz == null) {
return null;
}

// remove leading dots
if (name.startsWith(".")) {
name = name.substring(1);
}

for (Field field : clazz.getFields()) {
if (field.getName().equals(name)) {
try {
Object v = field.get(null);
return v.toString();
} catch (IllegalAccessException e) {
// ignore
return null;
}
}
}

return null;
}

那么我们可以通过自定义表达式的方式来获取字段对象

1
2
3
4
5
6
7
8
9
10
11
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<setHeader name="employeeConverter">
<language language="lx">object:org.apache.camel.spring.converter.model.EmployeeConverter.converter</language>
</setHeader>
<transform>
<simple>${header.employeeConverter.convert}</simple>
</transform>
</route>
</camelContext>

自定义表达式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class LxLanguage  extends LanguageSupport implements Language {
@Override
public Predicate createPredicate(String expression) {
return ExpressionToPredicateAdapter.toPredicate(createExpression(expression));
}

@Override
public Expression createExpression(String expression) {
String remainder = ifStartsWithReturnRemainder("object:", expression);
if (remainder != null)
return objectExpression(remainder);

return null;
}

private String ifStartsWithReturnRemainder(String prefix, String text) {
if (text.startsWith(prefix)) {
String remainder = text.substring(prefix.length());
if (remainder.length() > 0) {
return remainder;
}
}
return null;
}

public static Expression objectExpression(final String name) {
return new ExpressionAdapter() {
private ClassResolver classResolver;
private Expression exp;

@Override
public void init(CamelContext context) {
classResolver = context.getClassResolver();
exp = ExpressionBuilder.simpleExpression(name);
exp.init(context);
}

public Object evaluate(Exchange exchange) {
// it may refer to a class type
String text = exp.evaluate(exchange, String.class);
Class<?> type = classResolver.resolveClass(text);
if (type != null) {
return type;
}

int pos = text.lastIndexOf('.');
if (pos > 0) {
String before = text.substring(0, pos);
String after = text.substring(pos + 1);
type = classResolver.resolveClass(before);
if (type != null) {
return lookupFieldValue(type, after);
}
}

throw CamelExecutionException.wrapCamelExecutionException(exchange,
new ClassNotFoundException("Cannot find type " + text));
}

@Override
public String toString() {
return "object:" + name;
}
};
}

public static Object lookupFieldValue(Class<?> clazz, String name) {
if (clazz == null) {
return null;
}

// remove leading dots
if (name.startsWith(".")) {
name = name.substring(1);
}

for (Field field : clazz.getFields()) {
if (field.getName().equals(name)) {
try {
Object v = field.get(null);
return v;
} catch (IllegalAccessException e) {
// ignore
return null;
}
}
}

return null;
}
}