最近在開發(fā)自己的開源項(xiàng)目REACTIVE DUBBO過程中,需要修改Dubbo的一個(gè)工具類
RpcUtils
桩盲,通過選型決定用字節(jié)碼工具javassist對一個(gè)靜態(tài)方法進(jìn)行魔改。在編碼階段很順利實(shí)現(xiàn)了我想要的效果席吴,但是當(dāng)打包進(jìn)行驗(yàn)證時(shí)問題出現(xiàn)了。
問題分析
首先來到事發(fā)地點(diǎn)
try {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get(RPCUTILS_CLASS_NAME);
CtMethod ctMethod = ctClass.getDeclaredMethod("getReturnTypes");
//rename from `getReturnTypes` to `getReturnTypes0`
ctClass.removeMethod(ctMethod);
ctMethod.setName("getReturnTypes0");
ctClass.addMethod(ctMethod);
//add new `getReturnTypes` method according to RpcUtilsCracker.getReturnTypes
CtClass ctClass1 = classPool.get(RpcUtilsCracker.class.getName());
ctMethod = new CtMethod(ctClass1.getDeclaredMethod("getReturnTypes"),ctClass,null);
ctClass.addMethod(ctMethod);
ctClass.toClass();
} catch (NotFoundException|CannotCompileException e) {
logger.warn("crack RpcUtils failed",e);
}
這段代碼的用途是將RpcUtils
的getReturnTypes
方法重命名捞蛋,并增加自定義的方法孝冒。在開發(fā)階段運(yùn)行正常,然而在使用spring-boot:run運(yùn)行(或者用Uber jar運(yùn)行)時(shí)會(huì)報(bào)如下錯(cuò)誤:
javassist.NotFoundException: com.alibaba.dubbo.rpc.support.RpcUtils
at javassist.ClassPool.get(ClassPool.java:452) ~[javassist-3.20.0-GA.jar:na]
at com.github.cherrythefatbunny.reactive.dubbo.extensions.rpc.support.RpcUtilsCracker.hack(RpcUtilsCracker.java:28) ~[reactive-dubbo-extensions-1.0.2-SNAPSHOT.jar:na]
at com.github.cherrythefatbunny.reactive.dubbo.boot.ReactiveApplicationContextInitializer.initialize(ReactiveApplicationContextInitializer.java:14) [reactive-dubbo-starter-1.0.2-SNAPSHOT.jar:na]
at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:649) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:373) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at com.github.cherrythefatbunny.demo.consumer.ConsumerApplication.main(ConsumerApplication.java:12) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:558) [spring-boot-maven-plugin-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
報(bào)錯(cuò)語句為classPool.get(RPCUTILS_CLASS_NAME)
拟杉,由于這段代碼是要修改存量的類庄涡,javassist需要首先讀class文件。debug發(fā)現(xiàn)執(zhí)行到Object.class.getResource(jarname)
時(shí)搬设,用IDE啟動(dòng)能返回該類的URL穴店,而Spring boot啟動(dòng)則返回空。
繼續(xù)跟斷點(diǎn)發(fā)現(xiàn)拿穴,最終是使用類加載器Launcher$AppClassLoader
完成加載操作泣洞,這樣問題定位是出在ClassLoader身上,要想解決這個(gè)問題首先要從JVM類加載機(jī)制以及Spring boot的啟動(dòng)原理說起默色。
成功獲取Class:
未能獲取Class:
JVM類加載
類加載器
類加載器用來動(dòng)態(tài)加載Java類到Java虛擬機(jī)的內(nèi)存空間中球凰,分為Bootstrap、Extension和System以及User-Defined。其中Bootstrap負(fù)責(zé)加載<JAVA_HOME>/lib
路徑下的核心類庫或-Xbootclasspath
參數(shù)指定的路徑下的類呕诉;Extension負(fù)責(zé)加載<JAVA_HOME>/lib/ext
目錄下或者由系統(tǒng)變量-Djava.ext.dir
指定位路徑中的類缘厢;System負(fù)責(zé)加載ClassPath下的類。
雙親委派模型
除了Bootstrap甩挫,每個(gè)類加載器都有父類加載器贴硫,當(dāng)類加載器接收到類加載請求時(shí)它會(huì)先將請求發(fā)給父類加載器處理,如果加載不成功才自己嘗試加載伊者。所以通過子類加載器可以找到父類加載器加載的類英遭,反之不可以。
Spring boot啟動(dòng)原理導(dǎo)致的差異
通過IDE(IntelliJ)啟動(dòng)
開發(fā)階段可以通過項(xiàng)目的主函數(shù)啟動(dòng)Spring boot删壮,通過啟動(dòng)命令我們發(fā)現(xiàn)IDE會(huì)自動(dòng)將依賴加入classpath贪绘,這樣的啟動(dòng)方式和普通Java項(xiàng)目并無二致,javassist也能順利找到類RpcUtils
央碟。
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/bin/java
-agentlib:jdwp=transport=dt_socket,address=127.0.0.1:59051,suspend=y,server=n
-Dvisualvm.id=202724856185364 -XX:TieredStopAtLevel=1 -noverify
-Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote
-Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true
-javaagent:/Users/cherry/Library/Caches/IntelliJIdea2018.3/captureAgent/debugger-agent.jar -Dfile.encoding=UTF-8
-classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/charsets.jar:
...
/Users/cherry/IdeaProjects/reactive-dubbo/demo/consumer/target/classes:
/Users/cherry/IdeaProjects/reactive-dubbo/demo/facade/target/classes:
...
/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter/2.1.2.RELEASE/spring-boot-starter-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot/2.1.2.RELEASE/spring-boot-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.1.2.RELEASE/spring-boot-autoconfigure-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.1.2.RELEASE/spring-boot-starter-logging-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/cherry/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/cherry/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.11.1/log4j-to-slf4j-2.11.1.jar:/Users/cherry/.m2/repository/org/apache/logging/log4j/log4j-api/2.11.1/log4j-api-2.11.1.jar:/Users/cherry/.m2/repository/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar:/Users/cherry/.m2/repository/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar:/Users/cherry/.m2/repository/org/springframework/spring-core/5.1.4.RELEASE/spring-core-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-jcl/5.1.4.RELEASE/spring-jcl-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/yaml/snakeyaml/1.23/snakeyaml-1.23.jar:/Users/cherry/.m2/repository/com/alibaba/spring/spring-context-support/1.0.2/spring-context-support-1.0.2.jar:/Users/cherry/.m2/repository/com/alibaba/boot/dubbo-spring-boot-starter/0.2.1.RELEASE/dubbo-spring-boot-starter-0.2.1.RELEASE.jar:/Users/cherry/.m2/repository/com/alibaba/boot/dubbo-spring-boot-autoconfigure/0.2.1.RELEASE/dubbo-spring-boot-autoconfigure-0.2.1.RELEASE.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo/2.6.5/dubbo-2.6.5.jar:/Users/cherry/.m2/repository/org/springframework/spring-context/5.1.4.RELEASE/spring-context-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-aop/5.1.4.RELEASE/spring-aop-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-beans/5.1.4.RELEASE/spring-beans-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-expression/5.1.4.RELEASE/spring-expression-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar:/Users/cherry/.m2/repository/org/jboss/netty/netty/3.2.5.Final/netty-3.2.5.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-all/4.1.31.Final/netty-all-4.1.31.Final.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.9.8/jackson-databind-2.9.8.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.9.0/jackson-annotations-2.9.0.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.9.8/jackson-core-2.9.8.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter-webflux/2.1.2.RELEASE/spring-boot-starter-webflux-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.1.2.RELEASE/spring-boot-starter-json-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.9.8/jackson-datatype-jdk8-2.9.8.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.8/jackson-datatype-jsr310-2.9.8.jar:/Users/cherry/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.9.8/jackson-module-parameter-names-2.9.8.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-starter-reactor-netty/2.1.2.RELEASE/spring-boot-starter-reactor-netty-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/io/projectreactor/netty/reactor-netty/0.8.4.RELEASE/reactor-netty-0.8.4.RELEASE.jar:/Users/cherry/.m2/repository/io/netty/netty-codec-http/4.1.31.Final/netty-codec-http-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-codec/4.1.31.Final/netty-codec-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-codec-http2/4.1.31.Final/netty-codec-http2-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-handler/4.1.31.Final/netty-handler-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-buffer/4.1.31.Final/netty-buffer-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-transport/4.1.31.Final/netty-transport-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-resolver/4.1.31.Final/netty-resolver-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-handler-proxy/4.1.31.Final/netty-handler-proxy-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-codec-socks/4.1.31.Final/netty-codec-socks-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-transport-native-epoll/4.1.31.Final/netty-transport-native-epoll-4.1.31.Final-linux-x86_64.jar:/Users/cherry/.m2/repository/io/netty/netty-common/4.1.31.Final/netty-common-4.1.31.Final.jar:/Users/cherry/.m2/repository/io/netty/netty-transport-native-unix-common/4.1.31.Final/netty-transport-native-unix-common-4.1.31.Final.jar:/Users/cherry/.m2/repository/org/hibernate/validator/hibernate-validator/6.0.14.Final/hibernate-validator-6.0.14.Final.jar:/Users/cherry/.m2/repository/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar:/Users/cherry/.m2/repository/org/jboss/logging/jboss-logging/3.3.2.Final/jboss-logging-3.3.2.Final.jar:/Users/cherry/.m2/repository/com/fasterxml/classmate/1.4.0/classmate-1.4.0.jar:/Users/cherry/.m2/repository/org/springframework/spring-web/5.1.4.RELEASE/spring-web-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/spring-webflux/5.1.4.RELEASE/spring-webflux-5.1.4.RELEASE.jar:/Users/cherry/.m2/repository/org/synchronoss/cloud/nio-multipart-parser/1.1.0/nio-multipart-parser-1.1.0.jar:/Users/cherry/.m2/repository/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar:/Users/cherry/.m2/repository/org/synchronoss/cloud/nio-stream-storage/1.1.3/nio-stream-storage-1.1.3.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-registry-zookeeper/2.6.5/dubbo-registry-zookeeper-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-registry-api/2.6.5/dubbo-registry-api-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-cluster/2.6.5/dubbo-cluster-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-rpc-api/2.6.5/dubbo-rpc-api-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-serialization-api/2.6.5/dubbo-serialization-api-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-container-api/2.6.5/dubbo-container-api-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-remoting-zookeeper/2.6.5/dubbo-remoting-zookeeper-2.6.5.jar:/Users/cherry/.m2/repository/com/alibaba/dubbo-common/2.6.5/dubbo-common-2.6.5.jar:/Users/cherry/.m2/repository/commons-logging/commons-logging/1.2/commons-logging-1.2.jar:/Users/cherry/.m2/repository/log4j/log4j/1.2.16/log4j-1.2.16.jar:/Users/cherry/.m2/repository/com/alibaba/hessian-lite/3.2.4/hessian-lite-3.2.4.jar:/Users/cherry/.m2/repository/com/alibaba/fastjson/1.2.46/fastjson-1.2.46.jar:/Users/cherry/.m2/repository/com/esotericsoftware/kryo/4.0.1/kryo-4.0.1.jar:/Users/cherry/.m2/repository/com/esotericsoftware/reflectasm/1.11.3/reflectasm-1.11.3.jar:/Users/cherry/.m2/repository/com/esotericsoftware/minlog/1.3.0/minlog-1.3.0.jar:/Users/cherry/.m2/repository/de/javakaffee/kryo-serializers/0.42/kryo-serializers-0.42.jar:/Users/cherry/.m2/repository/de/ruedigermoeller/fst/2.48-jdk-6/fst-2.48-jdk-6.jar:/Users/cherry/.m2/repository/com/cedarsoftware/java-util/1.9.0/java-util-1.9.0.jar:/Users/cherry/.m2/repository/com/cedarsoftware/json-io/2.5.1/json-io-2.5.1.jar:/Users/cherry/.m2/repository/org/apache/zookeeper/zookeeper/3.4.9/zookeeper-3.4.9.jar:/Users/cherry/.m2/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar:/Users/cherry/.m2/repository/jline/jline/0.9.94/jline-0.9.94.jar:/Users/cherry/.m2/repository/io/netty/netty/3.10.5.Final/netty-3.10.5.Final.jar:/Users/cherry/.m2/repository/com/101tec/zkclient/0.2/zkclient-0.2.jar:/Users/cherry/.m2/repository/org/apache/curator/curator-framework/2.12.0/curator-framework-2.12.0.jar:/Users/cherry/.m2/repository/org/apache/curator/curator-client/2.12.0/curator-client-2.12.0.jar:/Users/cherry/.m2/repository/com/google/guava/guava/16.0.1/guava-16.0.1.jar:/Users/cherry/IdeaProjects/reactive-dubbo/reactive-dubbo-starter/target/classes:/Users/cherry/IdeaProjects/reactive-dubbo/reactive-dubbo-extensions/target/classes:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-autoconfigure-processor/2.1.2.RELEASE/spring-boot-autoconfigure-processor-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/org/springframework/boot/spring-boot-configuration-processor/2.1.2.RELEASE/spring-boot-configuration-processor-2.1.2.RELEASE.jar:/Users/cherry/.m2/repository/io/projectreactor/reactor-core/3.2.5.RELEASE/reactor-core-3.2.5.RELEASE.jar:/Users/cherry/.m2/repository/org/reactivestreams/reactive-streams/1.0.2/reactive-streams-1.0.2.jar:/Users/cherry/.m2/repository/org/projectlombok/lombok/1.18.4/lombok-1.18.4.jar:/Users/cherry/.m2/repository/org/ow2/asm/asm/5.0.4/asm-5.0.4.jar:/Users/cherry/.m2/repository/org/objenesis/objenesis/2.6/objenesis-2.6.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar" com.github.cherrythefatbunny.demo.consumer.ConsumerApplication
通過jar啟動(dòng)
Spring boot定義了一套打包標(biāo)準(zhǔn)税灌,將依賴的jar都打包成一個(gè)Uber jar,啟動(dòng)類變成了Spring boot打包進(jìn)去的JarLauncher
亿虽,JarLauncher
會(huì)通過自定義的LaunchedURLClassLoader
加載Uber jar內(nèi)部的lib jar內(nèi)部的class??,
由于所尋找的類不在classpath內(nèi)菱涤,因此通過之前的Launcher$AppClassLoader
是無法找到的,這正是報(bào)錯(cuò)的原因洛勉。
── BOOT-INF
│ ├── classes
│ │ ├── application.properties
│ │ └── com
│ │ └── github
│ │ └── cherrythefatbunny
│ │ └── demo
│ │ └── consumer
│ │ ├── ConsumerApplication.class
│ │ └── PersonController.class
│ └── lib
│ ├── asm-5.0.4.jar
│ ...
│ ├── zkclient-0.2.jar
│ └── zookeeper-3.4.9.jar
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── com.github.cherrythefatbunny.demo
│ └── consumer
│ ├── pom.properties
│ └── pom.xml
└── org
└── springframework
└── boot
└── loader
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
├── LaunchedURLClassLoader.class
├── Launcher.class
...
解決問題
找到了問題的根源是javassist默認(rèn)使用Object
類的Launcher$AppClassLoader
粘秆,而所依賴的jar都是通過Spring boot的LaunchedURLClassLoader
加載的。根據(jù)JVM的雙親委派模型可知收毫,使用Launcher$AppClassLoader
是無法查找子類加載器LaunchedURLClassLoader
所加載類的攻走。幸好javassist為我們留下了擴(kuò)展的方法,classPool.appendClassPath(new LoaderClassPath(RpcUtilsCracker.class.getClassLoader()));
,通過向ClassPool添加一個(gè)能訪問到類RpcUtils
的ClassLoader就可以解決問題此再。這里我選了代碼所在的類RpcUtilsCracker
昔搂,由于我開發(fā)的是一個(gè)Spring boot starter,因此該類會(huì)被LaunchedURLClassLoader
所加載输拇。經(jīng)過編譯打包發(fā)現(xiàn)摘符,報(bào)錯(cuò)不再出現(xiàn)??。
總結(jié)
Spring boot是Spring生態(tài)重要的組成策吠,給程序員帶來了極大便利逛裤,但不代表這個(gè)項(xiàng)目問題就少,而且底層框架不出問題則已猴抹,一有問題就讓你懷疑人生带族。之前就曾經(jīng)遇到過使用Spring boot熱部署工具,導(dǎo)致類型判定出現(xiàn)問題洽糟。
最近在學(xué)習(xí)響應(yīng)式編程炉菲,最開始有一定門檻堕战,一旦邁過去就欲罷不能。趁熱寫了一個(gè)小項(xiàng)目REACTIVE DUBBO拍霜,Make your Dubbo reactive嘱丢!