首先来看两段MyBatis-Puls的代码
LambdaQueryWrapper<Channel> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper
.eq(ObjectUtil.isNotEmpty(param.getName()),Channel::getName, param.getName());
QueryWrapper<Channel> queryWrapper = new QueryWrapper<>();
queryWrapper
.eq(ObjectUtil.isNotEmpty(param.getName()),"name", param.getName());
发现什么没有?
Channel::getName代替了"name"原本的字段名
那么Channel::getName明明是调用getName的方法,怎么会变成"name"???
不得不说,这很酷,不是吗?我们的方法代码是不是也可以这样呢?怎么实现呢?
Java8中给我们提供了实现方式,首先要做的就是定义一个可序列化的函数式接口(实现Serializable),实现如下:
import java.io.Serializable;
import java.util.function.Function;
@FunctionalInterface
public interface SerializableFunction<T, R> extends Function<T, R>, Serializable {
}
然后需要解析这个SerializableFunction,完整实现如下ReflectionUtil
import com.baomidou.mybatisplus.annotation.TableName;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ReflectionUtil {
private static Map<SerializableFunction<?, ?>, Field> cache = new ConcurrentHashMap<>();
public static <T, R> String getFieldName(SerializableFunction<T, R> function) {
Field field = ReflectionUtil.getField(function);
return field.getName();
}
public static <T, R> Field getField(SerializableFunction<T, R> function) {
return cache.computeIfAbsent(function, ReflectionUtil::findField);
}
public static <T, R> Field findField(SerializableFunction<T, R> function) {
Field field = null;
String fieldName = null;
try {
SerializedLambda serializedLambda = getSerializedLambda(function);
// 第2步 implMethodName 即为Field对应的Getter方法名
String implMethodName = serializedLambda.getImplMethodName();
if (implMethodName.startsWith("get") && implMethodName.length() > 3) {
fieldName = Introspector.decapitalize(implMethodName.substring(3));
} else if (implMethodName.startsWith("is") && implMethodName.length() > 2) {
fieldName = Introspector.decapitalize(implMethodName.substring(2));
} else if (implMethodName.startsWith("lambda$")) {
throw new IllegalArgumentException("SerializableFunction不能传递lambda表达式,只能使用方法引用");
} else {
throw new IllegalArgumentException(implMethodName + "不是Getter方法引用");
}
Class<?> aClass = getClass(serializedLambda);
// 第4步 Spring 中的反射工具类获取Class中定义的Field
field = ReflectionUtils.findField(aClass, fieldName);
} catch (Exception e) {
e.printStackTrace();
}
// 第5步 如果没有找到对应的字段应该抛出异常
if (field != null) {
return field;
}
throw new NoSuchFieldError(fieldName);
}
public static <T, R> String tableName(SerializableFunction<T, R> function) {
Class<?> aClass = null;
try {
aClass = getClass(function);
TableName annotation = aClass.getAnnotation(TableName.class);
if (annotation == null){
throw new NoSuchFieldError("注解不存在!");
}
return annotation.value();
} catch (Exception e) {
e.printStackTrace();
throw new NoSuchFieldError(e.toString());
}
}
public static <T, R> SerializedLambda getSerializedLambda(SerializableFunction<T, R> function) throws Exception {
// 第1步 获取SerializedLambda
Method method = function.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
return (SerializedLambda) method.invoke(function);
}
public static Class<?> getClass(SerializedLambda serializedLambda) throws ClassNotFoundException {
// 第3步 获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
String declaredClass = serializedLambda.getImplClass().replace("/", ".");
Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
return aClass;
}
public static <T, R> Class<?> getClass(SerializableFunction<T, R> function) throws Exception {
return getClass(getSerializedLambda(function));
}
}
接下来我们就可以自定义自己想要的方法了
public static void main(String[] args) {
test(BsMarketingChannel::getChannelName,BsMarketingChannel::getId,BsMarketingChannel::getCompany);
}
public static <T, R> void test(SerializableFunction<T, R> fieldNameMethod,SerializableFunction<T, ?>... columns){
String tableName = ReflectionUtil.tableName(fieldNameMethod);
String selectSql = "";
if (ArrayUtils.isNotEmpty(columns)) {
List<SerializableFunction<T, ?>> sFunctions = Arrays.asList(columns);
selectSql = sFunctions.stream().map((i) -> {
return ReflectionUtil.getFieldName(i);
}).collect(Collectors.joining(","));
}
}
发现什么没?我明明传的是getChannelName方法,但是我获取的是tableName,即@TableName注解中的表名
如果你详细看了ReflectionUtil,就会发现,怎么通过方法就获取到class了,那拿到类class,通过反射,不就什么都可以拿到嘛!重要的是SerializedLambda
SerializedLambda serializedLambda = getSerializedLambda(function);
String declaredClass = serializedLambda.getImplClass().replace("/", ".");
Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
因为,如果一个函数式接口实现了 Serializable 接口,那么它的实例就会自动生成了一个返回 SerializedLambda 实例的 writeReplace 方法,可以从 SerializedLambda 实例中获取到这个函数式接口的运行时信息。这些运行时的信息就是 SerializedLambda 的属性。
更详细的可以查看以下文章
然后?你已经大概明白了原理,然后当然就可以为所欲为了,嘿嘿嘿。
感谢: