最近在项目中使用 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);
public static <E, R> String getFieldName(Func<E, R> func) { Field field = getField(func); return field.getName(); }
public static <E, R> Field getField(Func<E, R> func) { try { Method method = func.getClass().getDeclaredMethod("writeReplace"); method.setAccessible(Boolean.TRUE); SerializedLambda serializedLambda = (SerializedLambda) method.invoke(func); String getterMethod = serializedLambda.getImplMethodName(); String fieldName = Introspector.decapitalize(getterMethod.replace("get", "")); String declaredClass = serializedLambda.getImplClass().replace("/", "."); Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader()); 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这个字段。