引子:MyBatisPlus的lambdaQuery,可以在構造查詢條件時傳遞方法的引用,MyBatis能夠將方法引用解析成爲要查詢的DB字段名,如下:
Wrappers.<Member>lambdaQuery().eq(Member::getMemberId, memberId);
解釋:構建出的sql條件:member_id=1000L
上述方法傳入的是lambda表達式,拿到的卻是方法引用的方法名,並將駝峯命名法轉化爲蛇形命名法。
借鑑
lambda表達式實現了Serializable
接口:
@FunctionalInterface
public interface IGetter<T> extends Serializable {
Object get(T source);
}
@FunctionalInterface
public interface ISetter<T, U> extends Serializable {
void set(T t, U u);
}
工具類:
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.base.CaseFormat;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BeanUtils {
private static Map<Class, SerializedLambda> CLASS_LAMDBA_CACHE = new ConcurrentHashMap<>();
/***
* 轉換方法引用爲屬性名
* @param fn
* @return
*/
public static <T> String convertToFieldName(IGetter<T> fn) {
SerializedLambda lambda = getSerializedLambda(fn);
String methodName = lambda.getImplMethodName();
String prefix = null;
if (methodName.startsWith("get")) {
prefix = "get";
} else if (methodName.startsWith("is")) {
prefix = "is";
}
if (prefix == null) {
log.warn("無效的getter方法: " + methodName);
}
return toSnake(methodName.replace(prefix, ""));
}
public static <T, U> String convertToFieldName(ISetter<T, U> fn) {
SerializedLambda lambda = getSerializedLambda(fn);
String methodName = lambda.getImplMethodName();
if (!methodName.startsWith("set")) {
log.warn("無效的setter方法:" + methodName);
}
return toSnake(methodName.replace("set", ""));
}
/**
* 關鍵在於這個方法
*/
public static SerializedLambda getSerializedLambda(Serializable fn) {
SerializedLambda lambda = CLASS_LAMDBA_CACHE.get(fn.getClass());
if (lambda == null) {
try {
Method method = fn.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
lambda = (SerializedLambda) method.invoke(fn);
CLASS_LAMDBA_CACHE.put(fn.getClass(), lambda);
} catch (Exception e) {
log.error("", e);
}
}
return lambda;
}
private static String toSnake(String fieldName) {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, fieldName);
}
}
測試方法:
@Data
public class Person {
private int id;
private String name;
}
public class Test {
public static void main(String[] args) {
String getName = BeanUtils.convertToFieldName(Person::getId);
System.out.println(getName);
String setName = BeanUtils.convertToFieldName(Person::setName);
System.out.println(setName);
}
}
原理
如果一個函數式接口實現了Serializable
接口,那麼它的實例就會自動生成了一個返回SerializedLambda
實例的 writeReplace
方法,可以從SerializedLambda
實例中獲取到這個函數式接口的運行時信息。這些運行時的信息就是 SerializedLambda
的屬性: