最近项目中遇到一个问题,动态排序。前端传过来一个对象数组,结构类似下面这种
[
{
"sortFiled":"name",
"sortDirection":"descending"
},
...
]
最终会拼接到sql语句的orderby中,后台处理query参数用的是mybatis-plus的LambdaQueryWrapper,问题来了,如何通过"name"这个字符串生成get方法对应的方法引用,也就是User::getName这种。
本来想过用QueryWrapper直接传字符串进去,但是前端字段一般都是驼峰的,需要转成下划线形式的,否则会报错。而且其他查询条件也要跟着动,所以就放弃了这种方案。
看源码eq这种方法需要的是SFunction,网上关于这方面的资料好难找。写了个工具类专门用来创建方法引用。
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.springframework.cglib.core.ReflectUtils;
import java.beans.PropertyDescriptor;
import java.lang.invoke.*;
import java.util.*;
public class LambdaUtil {
public static final Map<Class<?>, PropertyDescriptor[]> cache = new HashMap<>();
public static <T> SFunction<T, ?> getLambdaGetter(Class<T> clazz, String prop) throws Throwable {
PropertyDescriptor[] beanGetters;
if (cache.containsKey(clazz)) {
beanGetters = cache.get(clazz);
} else {
beanGetters = ReflectUtils.getBeanGetters(clazz);
cache.put(clazz, beanGetters);
}
var lookup = MethodHandles.lookup();
var optional = Arrays.stream(beanGetters)
.filter(pd -> pd.getName().equals(prop))
.findFirst();
if (optional.isPresent()) {
// 反射获取getter方法
var readMethod = optional.get().getReadMethod();
// 拿到方法句柄
final MethodHandle methodHandle = lookup.unreflect(readMethod);
// 创建动态调用链
var callSite = LambdaMetafactory.altMetafactory(
lookup,
"apply",
MethodType.methodType(SFunction.class),
MethodType.methodType(Object.class, Object.class),
methodHandle,
MethodType.methodType(readMethod.getReturnType(), clazz),
LambdaMetafactory.FLAG_SERIALIZABLE
);
return (SFunction<T, ?>) callSite.getTarget().invokeExact();
}
return null;
}
}
单元测试
SFunction<User, ?> getFun = LambdaUtil.getLambdaGetter(User.class, "name");
User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(getFun, "超级管理员"));
System.out.println("user = " + user);
最终正常返回结果