0%

使用 SerializedLambda 替代字符串

最近在项目中使用 mongo,ORM 使用的是 spring-boot-starter-data-mongo。在使用过程中,对于数据库字段,每次都要写,觉得麻烦,然后想起以前使用 mybatis-plus,只需要使用 lambda 获取字段名称即可,如下:

1
2
3
4
5
public List<UserInfo> getListByName(String name) {
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserInfo::getName, name);
return list(queryWrapper);
}

下面利用Lambda 函数式接口,通过 SerializedLambda 原理来获取 Java Bean 的实例。

定义函数接口

1
2
3
4
@FunctionalInterface
public interface Func<E, R> extends Function<E, R>, Serializable {

}

这里函数式接口继承 Serializable 接口,在 JDK 1.8 之后,JDK提供了一个新的类,凡是继承了 Serializable 的函数式接口的实例,都可以获取一个属于它的 SerializedLambda 实例,并通过它获取实例的信息。所以,我们可以通过这个原理来实现如何通过 Lambda 来获取 Java Bean 的属性名称。

实现获取方法字段

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
public class ReflectLambdaUtils {

private static final Logger logger = LoggerFactory.getLogger(ReflectLambdaUtils.class);

/**
* 根据参数获取字段名称
*
* @param func
* @return
*/
public static <E, R> String getFieldName(Func<E, R> func) {
Field field = getField(func);
return field.getName();
}

/**
* 获取表达式的字段
*
* @param func
* @return
*/
public static <E, R> Field getField(Func<E, R> func) {
try {
Method method = func.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
// 1.调用 writeReplace 方法,返回一个 writeReplace 对象
SerializedLambda serializedLambda = (SerializedLambda) method.invoke(func);
String getterMethod = serializedLambda.getImplMethodName();
String fieldName = Introspector.decapitalize(getterMethod.replace("get", ""));
// 2. 获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
String declaredClass = serializedLambda.getImplClass().replace("/", ".");
Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
// 3.通过Spring 中的反射工具类获取Class中定义的Field
return ReflectionUtils.findField(aClass, fieldName);
} catch (ReflectiveOperationException e) {
logger.error("解析类字段出现异常", e);
throw new MongoPlusException("解析字段时出现异常");
}
}
}

其中 writeReplace() 这个方法,是虚拟机加上去的,虚拟机会自动给实现 Serializable 接口的 Lambda 表达式生成 writeReplace() 方法。如果是被序列化后,实体对象就会有 writeReplace() 方法,调用该方法,会返回 SerializedLambda 对象去做序列化,即被序列化的对象被替换了。

这个时候,返回的 SerializedLambda 对象中包含了 Lambda 表达式中所有的信息,比如函数名implMethodName、函数签名 implMethodSignature 等。我们就可以根据这些信息,获取我们所需要的属性字段了。

测试

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
public class UserInfo {
private String name;
@Field(value = "user_age")
private Integer age;
private String userId;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getUserId() {
return userId;
}

public void setUserId(String userId) {
this.userId = userId;
}

@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", age=" + age +
", userId='" + userId + '\'' +
'}';
}
}

public class AppTest {

@Test
public void getFieldName() {
UserInfo userInfo = new UserInfo();
userInfo.setAge(20);
userInfo.setName("张三");
System.out.println(ReflectLambdaUtils.getFieldName(UserInfo::getUserId));
}
}

总结

上面的一个简单例子,通过 SerializedLambda 实现了通过 Lambda 获取 Java Bean 实例属性的简单案例。对于字段的处理,比如 is、get 等不同开头的,需要大家自己去做处理,这里我只展示了处理getField这个字段。

客官,赏一杯coffee嘛~~~~