java spring注解维护,从一次工程启动失败谈谈 spring 注解
原標題:從一次工程啟動失敗談談 spring 注解
檀寶權
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。返回搜狐,查看更多
責任編輯:
總結
以上是生活随笔為你收集整理的java spring注解维护,从一次工程启动失败谈谈 spring 注解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux两个卷组可以合并,Linux系
- 下一篇: java项目短信群发接口_JAVA实现第