点击蓝色“程序猿DD”关注我哟

加个“星标”,不忘签到哦

来源:字节观


关注我,回复口令获取可获取独家整理的学习资料:

001 :领取《Spring Boot基础教程》

002 :领取《Spring Cloud基础教程》

>>当当大促,160买400的书,点击进入<<

从事java开发的小伙伴们,应该基本都用过Spring,但是估计很多人可能都不知道本文要说的这个知识点(标题中的80%是拍脑袋得出来的^_^),什么知识点这么神秘呢?且听我从一个实际问题慢慢说起。

这个问题是我一个朋友碰到的,问题的知识背景是这样的(为了大家更容易看明白,我用一些示意类进行说明):

有这样一个接口类HelloService:

public interface HelloService {       public void sayHello();
}

我朋友提供这样一个实现类HelloServiceImpl (这里需要大家注意:它加了@Component注解):

package com.xx.yy;  @org.springframework.stereotype.Component
public class HelloServiceImpl extends HelloService {
@Override
public void sayHello() {    System.out.println("Hello world!");   }
}

编译完后得到HelloServiceImpl.class,然后再使用加密工具对HelloServiceImpl.class文件内容加密后打包成一个jar包丢给业务方使用。请大家注意这里:HelloServiceImpl.class文件内容是经过加密的

业务方项目基于SpringBoot搭建,在使用的时候将HelloServiceImpl所在jar包依赖加进来,并且将它的包路径 com.xx.yy 加到了bean扫描路径里面,简单示意如下:

@Configuration
@EnableAutoConfiguration
@ComponentScan("com.xx.yy")
public class Application {
@Autowired
private HelloService helloService;  public static void main(String[] args) {    SpringApplication.run(Application.class,args);  }   }

业务方将该SpringBoot项目打包成demo.jar,然后使用类似下面的命令启动:

java -jar -agentpath:/usr/lib/xxdecrypt.so demo.jar

之前说过,HelloServiceImpl.class 这个文件的内容是加密过的,如果在这个类加载过程中不进行解密,那么在jvm加载这个类的校验阶段是通不过的进而引发加载报错。

那么在什么地方对class进行解密呢?请大家注意这个启动参数:

-agentpath:/usr/lib/xxdecrypt.so,这是用C++写的一个native agent。

Agent可以利用JVM提供的JVMTI(JVM Tool Interface)接口来和JVM进行通讯,它可以订阅自己感兴趣的JVM事件(比如类加载卸载、方法出入、线程始末等等),当这些事件发生时JVM会回调Agent的代码。利用这个机制我们可以编写一个Agent,在JVM加载class文件的时候进行拦截,对字节码进行解密后返回给JVM,从而达到对调用方透明加解密的目的。详细的JVMTI相关知识,大家可以参考@江南白衣写的《入门科普,围绕JVM的各种外挂技术》

到这里,背景知识介绍完毕。这时候问题出现了,上面的启动命令报错了,程序启动失败,报错信息如下:

2019-06-04 11:38:37.357 ERROR 10219 --- [           main] o.s.boot.SpringApplication               : Application run failed    org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: URL [jar:file:/home/sample/demo.jar!/com/xx/yy/HelloServiceImpl.class]; nested exception is java.lang.ArrayIndexOutOfBoundsException: 15467
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:454) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:316) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:275) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:288) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:202) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:170) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:316) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at com.xht.Sample5App.main(Sample5App.java:28) [xht-jni-sample5_encrypt.jar:na]
Caused by: java.lang.ArrayIndexOutOfBoundsException: 15467
at org.springframework.asm.ClassReader.<init>(ClassReader.java:198) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.asm.ClassReader.<init>(ClassReader.java:168) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.asm.ClassReader.<init>(ClassReader.java:445) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:54) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:123) ~[spring-core-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:430) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]   ... 20 common frames omitted

从报错日志来看,是Spring加载HelloServiceImpl.class这个类失败了。可能原因是什么呢?

很自然地,即使你不知道本文即将揭晓的这个Spring知识点,按照逻辑也能想到问题的原因可能有如下两个:

1、HelloServiceImpl.class这个文件是不是解密出错导致解密后的字节码不符合jvm规范?

2、HelloServiceImpl.class的加载没有走Agent的解密逻辑,导致没有解密?

朋友反馈说如果执行了解密逻辑的话会打印日志,而这个case没有看到相关日志,说明没有执行解密逻辑。难道启动命令参数不对导致Agent没起作用?朋友尝试了一下不把HelloServiceImpl所在的包路径加到Spring扫描范围,由业务代码自己new HelloServiceImpl的话,用同样的启动命令是可以成功启动并正常运行的。所以问题到底是啥呢?朋友百思不得其解找来求助于我。

我反问他:你知道Spring是怎么找到加了@Component等注解的类的么?

朋友回答:不就是遍历指定的包路径下的所有类,然后用类加载器加载这个类,通过反射判断这个类是否有相应的注解么?

我说:不是的。试想一下,如果Spring都把指定的包路径下的类都一一用类加载器加载一遍,那如果程序中其实没有使用到这些类,那不是白白加载了很多无用类么?

好了,不卖关子了,现在公布本文要说的Spring的知识点:Spring在扫描指定包路径下的类时,并不会一一用类加载器加载它们,而是自己把类文件当成普通文件从本地磁盘中读进来变成一个字节数组(并没有经过JVM类加载过程),然后用ASM去解析这个字节数组得到这个类的元数据,然后判断这个类的元数据里面是否有@Component等相关Spring的注解。如果有的话后面才会进一步使用类加载器去加载这个类,没有的话就不会尝试去加载。Spring相关源代码如下:

SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException { InputStream is = new BufferedInputStream(resource.getInputStream());   ClassReader classReader;
try {   classReader = new ClassReader(is); }
catch (IllegalArgumentException ex) {
throw new NestedIOException("ASM ClassReader failed to parse class file - " +
"probably due to a new Java class file version that isn't supported yet: " + resource, ex); }
finally {
is.close(); }   AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);  classReader.accept(visitor, ClassReader.SKIP_DEBUG);    this.annotationMetadata = visitor;
// (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
this.classMetadata = visitor;
this.resource = resource;  }

本文的case中看到的错误信息就是从classReader = new ClassReader(is) 抛出来的:因为直接读取的类文件二进制内容,是已经经过加密的,肯定是不符合JVM规范的,所以按照正常的JVM规范去解析一个不符合规范的字节码,那就很容易出错,进而报了数组越界的异常。

掌握这点知识之后,朋友就恍然大悟找到了解决办法。在此就不公布解决办法了,相信小伙伴们在了解这个知识点后自己能找到解决方案。没找到的话可以尝试在公众号发送”help“四个字母获取答案。

新群招募

如果你喜欢自己装机、刷系统、玩黑群,或对各类科技产品有浓厚的兴趣!那么赶紧加入,一起聊聊喜欢的东西,在大促的时候种种草。亦或是聊聊你想买的东西,让大伙给你拔拔草。^_^

添加微信:zyc_enjoy,根据指引,加入讨论群


推荐阅读

  • 如何像技术高手一样解决问题

  • Maven 虐我千百遍,我待 Maven 如初恋

  • 初探性能优化:2个月到4小时的性能提升

  • MySQL跑在CentOS 6 和 7上的性能比较

  • Spring Boot 配置文件中的花样,看这一篇足矣!


自律到极致 - 人生才精致:第9期

明日即将发布!

关注我,加个星标,不忘签到哦~

2019

与大家聊聊技术人的斜杠生活

点一点“阅读原文”小惊喜在等你

80%以上Javaer可能不知道的一个Spring知识点相关推荐

  1. 苹果手机小技巧大全计算机,80%的人都不知道的五个iPhone实用小技巧,不知道的话赶紧看看...

    原标题:80%的人都不知道的五个iPhone实用小技巧,不知道的话赶紧看看 现在越来越多的人使用iPhone手机,而且iPhone新机也是出了一段时间了,但是还是有很多人不知道iPhone中的一些隐藏 ...

  2. 80%的人都不知道的排版利器,博士生都在用它!

    不知你有没有想过这样一个情况:在Word上如何排出优美的数学公式? 如果你没想过,或许是因为你不是理工科本科生或研究生.如果你想过且编辑过的话,你就会知道用Word排学术论文有多麻烦. 我在写学术论文 ...

  3. 抖音变现模式?80%的人都不知道的秘密,三类更适合玩私域的产品

    这篇内容关于适合玩私域的三类产品,精简一下让大家可以3分钟看完.不玩悬念,直接上千货. 上一篇在我赢助手小禾呈序上给大家讲了三个变现的建议,以及两种我比较推荐的变现方式.带货和支付费.今天再讲一个主流 ...

  4. 80%的经理人都不知道的邮件常识

    一封好邮件里总藏不住满满的细心和周到,让人有说不尽的舒服. 一封好邮件的基本标准是少犯错误. 为此我们必须使用一个容错率高的流程,像这样: 标题-->正文-->附件-->收件人   ...

  5. 80%开发者都不知道的以太坊骚操作:「事件」和「日志」还可以这么玩!

    80%开发者都不知道的以太坊骚操作:「事件」和「日志」还可以这么玩! 2018年05月02日 00:00:00 阅读数:366 作者 | 蔡一  志顶科技技术总监 4月6日,Daniel Larime ...

  6. 射频混频器matlab,一个很多射频/天线工程师不知道的神奇网站

    [[一只射频攻城狮]一个很多射频/天线工程师不知道的神奇网站]https://toutiao.com/group/6858648754199724551/?app=explore_article&am ...

  7. 3个你很可能不知道的实验楼新功能,有一个会让你喊“哇哦”!

    最近上线了3个,你很可能不知道的新功能. 如果你都知道,那请让我给你一个拥抱,你一定是我们的灵魂用户.这3个功能的初衷,都是希望帮助大家在使用实验楼.以及学习中获得更多的激励和帮助.下面让我们来看看是 ...

  8. 一个你所不知道的暗黑游戏圈

    私服.侵权.大公司的战略转型这些内容只是游戏圈的表面,与暗黑游戏圈相比这些内容简直弱爆了,在暗黑游戏圈里,洗钱.血腥追杀创业者等事件层出不穷,这里充满着丛林法则,但是却从来无人揭示这个圈子. 近期,我 ...

  9. net4.0 程序没反应_@Java程序员,精通Spring,你不得不知道的那些书

    程序员书库(ID:OpenSourceTop)编译 链接:https://www.whizlabs.com/blog/spring-framework-books/ Java是业界最著名的语言之一,不 ...

最新文章

  1. 日志切割清理工具 Log-Cutter
  2. 无监督学习与监督学习的区别
  3. 颜色协调模型Color Harmoniztion
  4. php 读取三级分类,php excel 导入 导入三级分类 表格应该怎么设计才能得到想要的数据格式?汗血宝马...
  5. future.cancel不能关闭线程_多线程与高并发笔记
  6. 自定义SpringBoot的运行动画---美女
  7. 一文读懂 etcd 的 mvcc 实现
  8. [ZJOI2007]捉迷藏 (线段树,括号序列)
  9. Elasticsearch报错:NodeDisconnectedException[[][IP:9300][cluster:monitor/nodes/liveness] disc
  10. 路由器OpenWRT、LEDE、潘多拉、华硕、梅林、老毛子系统区别和特点
  11. LocalDate、LocalDateTime计算时间差
  12. 山西大同大学计算机分数线,山西大同大学录取分数线2021是多少分(附历年录取分数线)...
  13. k8s学习-CKA真题-Pod指定节点部署
  14. 我所见的沈阳世界园艺博览会
  15. linux proc目录全称,Linux命令 今天说一说Linux 命令缩写全称
  16. 通州区机器人比赛活动总结_2019年通州区中小学生机器人智能大赛举行
  17. 使用axios进行下载Excel文件
  18. # 使用Scratch 3.0制作弹球游戏(三)——游戏关卡及难度设计
  19. unity3d 重要类+方法 。。。再来一遍吧。。。
  20. 痞子衡嵌入式:恩智浦i.MX RTxxx系列MCU特性介绍(2)- RT685EVKA性能实测(Dhrystone)...

热门文章

  1. docker 错误 request canceled while waiting for connection 或 TLS handshake timeout 解决方案
  2. web安全攻防演练网站 靶机 测试环境 收集
  3. 利用位运算和指针实现的交换两个数的程序
  4. Android实现手机手电筒
  5. Android开发--详解SAX解析XML文件
  6. css限制字体三行_CSS美化网页
  7. 路由器无服务器无响应是怎么回事啊,wifi服务器无响应怎么解决(图文)
  8. c++连接oracle数据库程序,无法从c++程序连接到我的oracle数据库
  9. linux磁盘分配方案,安装Linux系统磁盘分配方案.doc
  10. 厉害的组件_企业级React UI组件库——React Suite