1. 為什么可以獲得函數(shù)的參數(shù)名?
一般 javac 編譯出的 .class 方法是不包含參數(shù)名的, 但 .class 文件除了反編譯出的類, 還包含額外的信息, 其中對(duì)日常最有用的2個(gè)概念是:
- LineNumberTable
記錄字節(jié)碼偏移量和代碼行號(hào)的 (拋異常時(shí), 顯示的哪行代碼出錯(cuò), 就是用的LineNumberTable的信息) -
LocalVariableTable(本地變量表)
記錄了方法中的局部變量和源代碼中的局部變量的對(duì)應(yīng)關(guān)系 (獲取函數(shù)的參數(shù)名等)
ASM 可以讀取 .class 文件中的 LocalVariableTable
2. 獲得參數(shù)名的3個(gè)方法:
- 如果是 java8, 在編譯時(shí)加上
-parameters參數(shù)java.lang.reflect.Parameter自動(dòng)解析編譯在 .class 文件的 輸出獲取參數(shù)名- 示例
- 類中使用 Parameter 反射
public class TestParamName { public static void main1(String[] args) throws NoSuchMethodException { Method method = TestParamName.class.getMethod("test1", String.class, Integer.class); int parameterCount = method.getParameterCount(); Parameter[] parameters = method.getParameters(); // 打印輸出: System.out.println("方法參數(shù)總數(shù):" + parameterCount); Arrays.stream(parameters).forEach( p -> System.out.println(p.getType() + "----" + p.getName()) ); } public void test1(String name, Integer age) { System.out.println(name + "::" + age); } }- javac 上加上
-parameters參數(shù)編譯額外信息
javac -parameters TestParamName.java- 輸出:
// 編譯時(shí)加上 -parameters 參數(shù), 輸出: 方法參數(shù)總數(shù):2 class java.lang.String----name class java.lang.Integer----age // 編譯時(shí)不加 -parameters 參數(shù), 輸出: 方法參數(shù)總數(shù):2 class java.lang.String----arg0 class java.lang.Integer----arg1 - 缺點(diǎn)
- 這種方式強(qiáng)制要求編譯時(shí)增加參數(shù), 且 java 版本在 1.8以上. 編譯參數(shù)往往每個(gè)人不一樣. 解決辦法是通過 maven 插件打包. (但是這容易和 idea 開發(fā)時(shí)輸出不一致, idea 的 java compiler 選項(xiàng)可能沒加 -parameters 參數(shù))
<!-- 編譯環(huán)境在1.8編譯 且附加編譯參數(shù):-parameters--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <!-- 編譯參數(shù) --> <compilerArgs> <arg>-parameters</arg> </compilerArgs> <source>${java.version}</source> <target>${java.version}</target> <compilerVersion>${java.version}</compilerVersion> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin>
- 示例
- 如果是 java8 以下, 添加
-g參數(shù), 但因?yàn)闆]有java.lang.reflect.Parameter類, 需要自己使用javap -verbose將局部變量日志輸出, 然后進(jìn)行解析.- 缺點(diǎn): 代碼里需要解析 javap 的輸出
- 使用 ASM 工具 (推薦)
ASM 是一個(gè)字節(jié)碼操控框架, 它能夠動(dòng)態(tài)增強(qiáng)類功能, 改變類型為, 甚至生成新類
3. Spring mvc 為何可以推斷出參數(shù)名, 而 Mybatis 推斷不出來(lái)?
-
有一個(gè)很疑惑的現(xiàn)象是:
- 在使用Spring MVC的時(shí)候, 即便不加 @Param 注解, 也可以將 json 的 key 和 controller 方法的參數(shù)綁定
- 在使用MyBatis(接口模式)時(shí),接口方法向 sql 語(yǔ)句傳參時(shí),必須使用@Param('')指定函數(shù)參數(shù)和 sql 參數(shù)的綁定關(guān)系
-
幾種操作字節(jié)碼方式的區(qū)別
-
ASM: 操縱底層的 JVM 匯編指令. 要求使用者對(duì)class組織結(jié)構(gòu)和JVM匯編指令有一定的了解 -
java 動(dòng)態(tài)代理:
動(dòng)態(tài)代理是調(diào)用Proxy的newProxyInstance()方法創(chuàng)建一個(gè)新的代理類$Proxy0, 內(nèi)部是一系列反射 constructor . 該代理類繼承自java.lang.reflect.Proxy, 且 implement 了需要代理的接口.$Proxy0 extends Proxy implements MyInterface{ ... }.
java 的單繼承機(jī)制決定了不能對(duì)抽象類代理, 只能對(duì)接口代理 -
CGLIB 動(dòng)態(tài)代理: 使用ASM框架操縱類實(shí)現(xiàn)對(duì)類(或接口)的代理
-
3.為什么 spring mvc 可以自定映射 json key 和方法參數(shù)名?
Spring MVC 借助 ASM 來(lái)實(shí)現(xiàn)參數(shù)名讀取, 不依賴于 -parameters 參數(shù),也不依賴于 -g 編譯參數(shù). 最終依賴 spring core 的 DefaultParameterNameDiscoverer 類獲取參數(shù)名
```java
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
public DefaultParameterNameDiscoverer() {
if (KotlinDetector.isKotlinReflectPresent() && !GraalDetector.inImageCode()) {
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
}
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
}
// 只要有一個(gè)處理器獲取了參數(shù)名, 就返回
public String[] getParameterNames(Method method) {
for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
String[] result = pnd.getParameterNames(method);
if (result != null) {
return result;
}
}
return null;
}
}
```
DefaultParameterNameDiscoverer 使用職責(zé)鏈模式處理參數(shù)名獲取. 職責(zé)鏈中包括 StandardReflectionParameterNameDiscoverer 和 LocalVariableTableParameterNameDiscoverer. 前者依賴 -parameters 的編譯輸出; 后者使用 ASM 實(shí)現(xiàn)參數(shù)名解析
- 為什么 Mybatis 的 dao 無(wú)法自動(dòng)完成方法參數(shù)名和 sql 參數(shù)名的映射
- 首先, ASM 有個(gè)問題是, 無(wú)法獲得 interface 的方法的參數(shù)名.
因?yàn)閷?duì)于抽象方法沒有方法體, 也就不存在方法內(nèi)的局部變量, 也就沒有記錄對(duì)應(yīng)的LocalVariableTable. 所以 ASM 無(wú)法讀取 - Mybatis 是通過接口和 sql 綁定后生成代理類完成的查詢映射, 所以使用 ASM 也無(wú)法獲取參數(shù)名
但是, 如果在編譯時(shí)加上Mapper @Repository // 這個(gè)注解可以不加, 純粹是為了 idea 在 autowired 中不報(bào)錯(cuò) public interface CasbinDao { // todo 測(cè)試自動(dòng)生成 id @Insert("INSERT INTO casbin_rule(id, p_type, v0, v1) VALUES(#{id}, #{pType}, #{v0}, #{v1})") @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") int insert(CasbinRule casbin); @Update(value = { "<script>", "UPDATE users", "<set>", "<if test='username != null'>, username = #{username}</if>", "<if test='password != null'>, password = #{password}</if>", "</set>", "WHERE id = #{id}", "</script>" }) int updateById(CasbinRule user); }-parameters參數(shù), 是可以使用 java8 的反射獲取參數(shù)名的. 因?yàn)樗灰蕾?LocalVariableTable. 單位了兼容性, mybatis 放棄了這種做法 - 首先, ASM 有個(gè)問題是, 無(wú)法獲得 interface 的方法的參數(shù)名.