最近在项目中使用 mongo,ORM 使用的是 spring-boot-starter-data-mongo。在使用过程中,对于数据库字段,每次都要写,觉得麻烦,然后想起以前使用 mybatis-plus,只需要使用 lambda 获取字段名称即可,如下:
public List<UserInfo> getListByName(String name) {
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserInfo::getName, name);
return list(queryWrapper);
}
下面利用Lambda 函数式接口,通过 SerializedLambda
原理来获取 Java Bean 的实例。
定义函数接口
@FunctionalInterface
public interface Func<E, R> extends Function<E, R>, Serializable {
}
这里函数式接口继承 Serializable
接口,在 JDK 1.8 之后,JDK提供了一个新的类,凡是继承了 Serializable
的函数式接口的实例,都可以获取一个属于它的 SerializedLambda
实例,并通过它获取实例的信息。所以,我们可以通过这个原理来实现如何通过 Lambda 来获取 Java Bean 的属性名称。
实现获取方法字段
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
等。我们就可以根据这些信息,获取我们所需要的属性字段了。
测试
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这个字段。