java spring注解维护,从一次工程启动失败谈谈 spring 注解

原标题:从一次工程启动失败谈谈 spring 注解

c82ba8d49af80e29dadacb4398c69410.png

檀宝权

Java 后端开发工程师,负责度假 App 后端和广告后端开发维护工作,熟悉 Tomcat,Spring,Mybatis,会点 Python,Lua。

一、背景

线上环境升级成 JDK8后, Tomcat 启动会经常失败,调整 JVM 的栈大小为 2M 后,失败频率大大降低,但是偶尔还是会失败。捕获启动异常日志,会看到下面异常信息(没找到附件上传地方,暂不上传日志附件,只截取一段,感兴趣的可以直接找我):

at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:519)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:343)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:320)

at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:187)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:861)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:818)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:735)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:551)

at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:284)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)

at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)

at org.springframework.beans.factory.support.AbstractBeanFactory.getTypeForFactoryBean(AbstractBeanFactory.java:1356)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:710)

at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:519)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:343)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:320)

at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:187)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:861)

Caused by: java.lang.StackOverflowError

栈溢出异常,很不正常的行为,为什么启动时候会导致这么长的调用链?

二、相关的版本环境

1. JDK82. Tomcat73. Spring3.1.2.RELEASE4. Mybatis-3.0.6.jar5. Mybatis-spring-1.0.2.jar

三、分析3.1 栈调用分析

仔细分析启动日志,发现下面的异常信息不断重复,熟悉 spring 源码的同学都知道,这段代码是用来创建 bean,但是创建的过程中发现依赖的 bean 不存在,继续创建依赖的 bean。

......

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)

at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)

at org.springframework.beans.factory.support.AbstractBeanFactory.getTypeForFactoryBean(AbstractBeanFactory.java:1356)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:710)

at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:519)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:343)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:320)

at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:187)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:861)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:818)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:735)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:551)

at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:284)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106)

......

关于 bean 之间的依赖,我们一般使用 @Autowired, @Resource 注解来声明。看上面的异常信息,明显使用的是 @Autowired 注解。断点调试发现,这个超长调用栈最终表现形式为:

service1->dao1->dao2->dao3->.....

这就很奇怪了,我们项目中 dao 使用的是 Mybatis Mapper XML 文件形式, dao 表现形式就是一个 Interface 文件, 根本不可能存在 dao 调用 dao 的情况。

3.2 @Autowired 注解

@Autowried 是类型匹配的注解, Spring 使用 AutowiredAnnotationBeanPostProcessor 来解析依赖。具体原理比较简单,大致如下:

遍历项目中所有注入的 BeanDefinition ;

获取每个 BeanDefinition 类型,校验此类型和 @Autowired 声明的类型是否匹配,匹配就认为是候选类型;

如果有多个候选类型,选择最佳的一个,找不到最佳的,就抛出存在多个同一类型的 bean 异常,启动失败。

那么,每个 BeanDefinition 类型是什么了?

普通的 bean,在注入到 spring 中就是其 beanClass 属性;

如果是 FactoryBean ,真正的类型并不是其 beanClass 属性声明的值,而是其方法 getObjectType() 返回的值 这就导致 spring 在查找类型的时候,如果遇到 FactoryBean ,就必须先实例化这个 bean ,只有创建成功后,才能调用对象的方法 getObjectType() 。

Mybatis Mapper dao 被 MapperScannerConfigurer 扫描后,注入到 spring 中的 beanClass 值为 org.mybatis.spring.mapper.MapperFactoryBean ,正好就是个 FactoryBean 。

3.3 MapperFactoryBean

先看看 MapperFacotryBean 的声明:

public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {

......

}

public abstract class SqlSessionDaoSupport extends DaoSupport {

private SqlSession sqlSession;

private boolean externalSqlSession;

@Autowired(required = false)

public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {

if (!this.externalSqlSession) {

this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

}

}

@Autowired(required = false)

public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {

this.sqlSession = sqlSessionTemplate;

this.externalSqlSession = true;

}

......

}

哈哈,MapperFactoryBean 也是使用了 @Autowired , spring 在实例化 MapperFactoryBean 后仍需要继续按照3.2节的过程执行遍历,导致出现这样的调用链:

service->dao1->mapper1->dao2->mapper2->dao3->mapper3->......

所以随着系统 Mybatis Mapper dao 文件慢慢增多,这个调用链会越来越长,最终超过栈深度,抛出栈溢出异常。

3.4 @Resource

spring 启动时候,在解析 @Autowired 依赖注入,需要使用很大的篇幅去寻找候选 beanName ,必须逐一遍历每个 BeanDefinition ,费时费力,即使整个系统只有一个这样类型的 BeanDefinition 。但是 spring 在注入 BeanDefinition , 除了会 add beanDefinitionNames , 还会建立 beanName-> beanDefinitionMap 。所以,使用 @Resource 注解,会大大降低依赖注入 BeanDefinition 寻找过程,也就大大降低 bean 初始化调用栈。

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

throws BeanDefinitionStoreException {

synchronized (this.beanDefinitionMap) {

Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);

if (oldBeanDefinition != null) {

if (!this.allowBeanDefinitionOverriding) {

throw new BeanDefinitionStoreException(beanDefinition.getResourceDeion(), beanName,

"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +

"': There is already [" + oldBeanDefinition + "] bound.");

}

else {

if (this.logger.isInfoEnabled()) {

this.logger.info("Overriding bean definition for bean '" + beanName +

"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");

}

}

}

else {

this.beanDefinitionNames.add(beanName);

this.frozenBeanDefinitionNames = null;

}

this.beanDefinitionMap.put(beanName, beanDefinition);

resetBeanDefinition(beanName);

}

}

四、结论

上面异常的根本原因是项目大量使用 @Autowired 注解,而且 mybatis-spring 版本比较陈旧导致。最新版本的 mybatis-spring 已经去掉了 @Autowired 注解,MapperScannerConfigurer 在扫描后直接为生成的MapperFactoryBean.sqlSession 赋值,不再使用注解去建立依赖关系。

解决办法:

升级 mybatis-spring 版本 最好 mybatis, spring 版本一并升级,目前这个确实太陈旧了

尽量使用 @Resource, 弃用 @Autowired@Autowired 本意是根据注入的类型来选择一个最佳的候选 bean, 当注入到系统的中候选 bean 只有一个时,是不关心 bean 的名称的,当有多个候选 bean时候,会进行 primary 判断或者名称判断。

@Resource 是根据名称查找 BeanDefinition, 当查找的的名称没有注入到 BeanFactory 中,CommonAnnotationBeanPostProcessor 就转到 @Autowired 查找。所以我们完全有理由弃用 @Autowired。返回搜狐,查看更多

责任编辑:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/519263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索Java日志的奥秘:底层日志系统-log4j2

前言 log4j2是apache在log4j的基础上,参考logback架构实现的一套新的日志系统(我感觉是apache害怕logback了)。 log4j2的官方文档上写着一些它的优点: 在拥有全部logback特性的情况下,还修复了一些隐藏问题API 分离&…

大地震!某大厂“硬核”抢人,放话:只要AI人才,高中毕业都行!

特斯拉创始人马斯克,在2019年曾许下很多承诺,其中一个就是:2019年底实现完全的自动驾驶。虽然这个承诺又成了flag,但是不妨碍他今年继续为这个承诺努力。这不,就在上周一,马斯克之间在twitter上放话了&…

Dart编译技术在服务端的探索和应用

前言 最近闲鱼技术团队在FlutterDart的多端一体化的基础上,实现了FaaS研发模式。Dart吸取了其它高级语言设计的精华,例如Smalltalk的Image技术、JVM的HotSpot和Dart编译技术又师出同门。由Dart实现的语言容器,它可以在启动速度、运行性能有不…

Python + ElasticSearch:有了这个超级武器,你也可以报名参加诗词大会了! | 博文精选...

来源 | CSDN 博客作者 | 天元浪子责编 | Carol出品 | CSDN云计算(ID:CSDNcloud)意犹未尽的诗词大会正月十六,中国诗词大会第五季落下帷幕。从2016年2月12日第一季于开播,迄今恰好四周年。在这个舞台上,时年…

Node.js 应用故障排查手册 —— 大纲与常规问题指标简介

楔子 你是否想要尝试进行 Node.js 应用开发但是又总听人说它不安全、稳定性差,想在公司推广扩张大前端的能力范畴和影响又说服不了技术领导。 JavaScript 发展到今天,早已脱离原本浏览器的战场,借助于 Node.js 的诞生将其触角伸到了服务端、P…

蚂蚁金服CTO程立:做工程要有“拧螺丝”的精神

“一台机器可能有无数颗螺丝,需要一个一个地拧,而且需要一圈一圈地拧,才能让系统间严丝合缝,顺利工作。代码的世界里,一个项目到底成功与否,也是取决于几个模型的关键特殊设置,就像拧螺丝一样。…

linux 环境安装DBI和DBD_03

文章目录一、软件下载二、安装DBI2.1. DBI下载2.2. 解压2.3. 安装依赖2.4. 编译2.5. 执行测试2.6. 安装2.6. 修改权限三、安装DBD-ORACLE组件3.1. DBI下载3.2. 修改权限3.3. 切换用户3.4. 解压3.5. 进入目录3.6. 初始化环境变量3.6. 查看配置的环境变量是否配置3.7. 刷新配置文…

像数据科学家一样思考:12步指南(上)

介绍 目前,数据科学家正在受到很多关注,因此,有关数据科学的书籍正在激增。我看过很多关于数据科学的书籍,在我看来他们中的大多数更关注工具和技术,而不是数据科学中细微问题的解决。直到我遇到Brian Godsey的“像数…

Mybatis-plus 大数据量数据流式查询通用接口

文章目录一、案例需求二、使用案例:2.1. 自定义查询接口2.2. 逻辑处理类2.3. 调用案例2.4. 具体逻辑处理案例三、企业案例3.1. key名称获取3.2. 逻辑类测试3.3.最后一个批次处理方案四、 通用SQL预编译处理4.1. 业务场景4.2. xml形式4.3. 注解形式五、企业案例5.1. …

基于MaxCompute的数仓数据质量管理

声明 本文中介绍的非功能性规范均为建议性规范,产品功能无强制,仅供指导。 参考文献 《大数据之路——阿里巴巴大数据实践》——阿里巴巴数据技术及产品部 著。 背景及目的 数据对一个企业来说已经是一项重要的资产,既然是资产&#xff…

IP应用加速 – DCDN迈入全栈新篇章

4月11日,第七届"亚太内容分发大会"暨CDN峰会国际论坛中,阿里云资深技术专家姚伟斌发布了DCDN子产品IP应用加速(IPA)。IPA是基于阿里云CDN本身的资源优化,对传输层(TCP&UDP)协议进…

十年磨一剑,王坚自研的MaxCompute如何解决世界级算力难题

大数据时代,随着企业数据规模的急剧增长,传统软件已无法承载,这也推动了大数据技术的发展,Google、AWS、微软等硅谷巨头纷纷投入大数据技术的研发;而在国内,王坚也在十年前带领阿里云团队研发MaxCompute&am…

matlab和robotstudio,MATLAB与Robotstudio建立socket通信(初探)

前记:听一首《不想病》,歌词唱开头:做什么都不对,说什么都浪费,想什么我都可悲....;感觉就是不一样,好歌!哎,,,回到正题。好多事要去做,还得挤时间…

贾扬清:我对人工智能方向的一点浅见

阿里妹导读:作为 AI 大神,贾扬清让人印象深刻的可能是他写的AI框架Caffe ,那已经是六年前的事了。经过多年的沉淀,成为“阿里新人”的他,对人工智能又有何看法?最近,贾扬清在阿里内部分享了他的…

Node.js 应用故障排查手册 —— 类死循环导致进程阻塞

类死循环导致进程阻塞 楔子 在实践篇一中我们看到了两个表象都是和 CPU 相关的生产问题,它们基本也是我们在线上可能遇到的这一类问题的典型案例,而实际上这两个案例也存在一个共同点:我们可以通过 Node.js 性能平台 导出进程对应的 CPU Pro…

如何使用5个Python库管理大数据?

来源 | medium编译 | 武明利责编 | Carol出品 | CSDN云计算(ID:CSDNcloud)如今,Python真是无处不在。尽管许多看门人争辩说,如果他们不使用比Python更难的语言编写代码,那么一个人是否真是软件开发人员&…

如何把创建ECS(CreateInstance)作为触发器来触发函数计算

问题描述 函数计算虽然不支持直接集成到ECS的管控事件上,但是函数计算本身是支持日志服务作为触发器的。即可以配置日志服务中logstore里的增强日志作为触发器来触发函数计算服务中的函数,同时可以传递project 和 logstore的name以及beginCursor/endCur…

ORACLE添加字段、删除字段

文章目录1. 删除表2. 创建表3. 添加字段4. 删除指定字段5. 修改指定字段长度1. 删除表 DROP TABLE SYS_JOB;2. 创建表 -- CREATE TABLE CREATE TABLE SYS_JOB (JOB_ID NUMBER(30) NOT NULL,JOB_NAME VARCHAR2(30) NOT NULL ); ALTER TABLE SYS_JOB ADD CONSTRA…

像数据科学家一样思考:12步指南(中)

像数据科学家一样思考:12步指南(上)《像数据科学家一样思考》 7-工程产品 下一步是建立统计软件。如果统计是分析和从数据中得出结论的框架,那么软件就是将这个框架付诸行动的工具。数据科学家必须为任何项目做出许多软件选择。如…

2020云计算,是四强争霸还是赢家通吃?

来源 | 架构师技术联盟责编 | Carol出品 | CSDN云计算(ID:CSDNcloud)近日,谷歌母公司Alphabet首次公布了谷歌云计算业务的数据,这一举动将云计算行业重新推到了聚光灯下。众所周知,全球云市场竞争激烈&…