美文网首页
当javassist遇见Spring boot

当javassist遇见Spring boot

作者: 胖兔子cherry | 来源:发表于2019-03-28 10:47 被阅读0次

    最近在开发自己的开源项目REACTIVE DUBBO过程中,需要修改Dubbo的一个工具类RpcUtils,通过选型决定用字节码工具javassist对一个静态方法进行魔改。在编码阶段很顺利实现了我想要的效果,但是当打包进行验证时问题出现了。

    问题分析

    首先来到事发地点

    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);
            }
    

    这段代码的用途是将RpcUtilsgetReturnTypes方法重命名,并增加自定义的方法。在开发阶段运行正常,然而在使用spring-boot:run运行(或者用Uber jar运行)时会报如下错误:

    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]
    

    报错语句为classPool.get(RPCUTILS_CLASS_NAME),由于这段代码是要修改存量的类,javassist需要首先读class文件。debug发现执行到Object.class.getResource(jarname)时,用IDE启动能返回该类的URL,而Spring boot启动则返回空。

    继续跟断点发现,最终是使用类加载器Launcher$AppClassLoader完成加载操作,这样问题定位是出在ClassLoader身上,要想解决这个问题首先要从JVM类加载机制以及Spring boot的启动原理说起。

    成功获取Class:


    成功获取Class

    未能获取Class:


    未能获取Class

    JVM类加载

    类加载器

    类加载器用来动态加载Java类到Java虚拟机的内存空间中,分为Bootstrap、Extension和System以及User-Defined。其中Bootstrap负责加载<JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的类;Extension负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类;System负责加载ClassPath下的类。

    双亲委派模型

    除了Bootstrap,每个类加载器都有父类加载器,当类加载器接收到类加载请求时它会先将请求发给父类加载器处理,如果加载不成功才自己尝试加载。所以通过子类加载器可以找到父类加载器加载的类,反之不可以

    JVM ClassLoader

    Spring boot启动原理导致的差异

    通过IDE(IntelliJ)启动

    开发阶段可以通过项目的主函数启动Spring boot,通过启动命令我们发现IDE会自动将依赖加入classpath,这样的启动方式和普通Java项目并无二致,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启动

    Spring boot定义了一套打包标准,将依赖的jar都打包成一个Uber jar,启动类变成了Spring boot打包进去的JarLauncherJarLauncher会通过自定义的LaunchedURLClassLoader加载Uber jar内部的lib jar内部的class😂,
    由于所寻找的类不在classpath内,因此通过之前的Launcher$AppClassLoader是无法找到的,这正是报错的原因。

    ── 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默认使用Object类的Launcher$AppClassLoader,而所依赖的jar都是通过Spring boot的LaunchedURLClassLoader加载的。根据JVM的双亲委派模型可知,使用Launcher$AppClassLoader是无法查找子类加载器LaunchedURLClassLoader所加载类的。幸好javassist为我们留下了扩展的方法,classPool.appendClassPath(new LoaderClassPath(RpcUtilsCracker.class.getClassLoader()));,通过向ClassPool添加一个能访问到类RpcUtils的ClassLoader就可以解决问题。这里我选了代码所在的类RpcUtilsCracker,由于我开发的是一个Spring boot starter,因此该类会被LaunchedURLClassLoader所加载。经过编译打包发现,报错不再出现😎。

    总结

    Spring boot是Spring生态重要的组成,给程序员带来了极大便利,但不代表这个项目问题就少,而且底层框架不出问题则已,一有问题就让你怀疑人生。之前就曾经遇到过使用Spring boot热部署工具,导致类型判定出现问题。

    最近在学习响应式编程,最开始有一定门槛,一旦迈过去就欲罢不能。趁热写了一个小项目REACTIVE DUBBO,Make your Dubbo reactive!

    相关文章

      网友评论

          本文标题:当javassist遇见Spring boot

          本文链接:https://www.haomeiwen.com/subject/pcnfbqtx.html