前言:
Spring框架是一个以IOC和AOP为核心的业务框架,在使用它时,会因为技术和业务的不断迭代,业务框架本身越来越庞大,对应的技术融合也越来越多,原来可以成功执行的,后面加入一些组件就导致了无效问题发生。例如:使用了BeanPostProcessor或者BeanFactoryPostProcessor的一些用法,导致了BEAN的提 前初始化,随后在一些场景中,导致BEAN的注入无效、Spring的事务不起作用。
思考:在解决这些问题时,其实当时造成这种问题时,只是为了解决功能,但没有在整体方案上思考一个良好的规范。从而经常碰到有新的问题或功能来临时,将一些挖的坑给释放出来,从来变得既要向前兼容,还要解决新的功能问题的两难局面。
下面,作者专门编写了一个最简单的例子展现给大家。
简单示例-准备:
这里使用简单的示例,提前让一些BEAN初始化,导致spring的bean ioc无效引发空指针异常
编写applicationContext.xml 内容非常简单,配置一个数据源,并声明一个dao,并给它注入datasource
@Override
<context:component-scan base-package="com.riso.spring"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
<property name="username" value="test"></property>
<property name="password" value="test"></property>
<property name="initialSize" value="2"></property>
<property name="maxActive" value="5"></property>
</bean>
<bean class="com.riso.spring.dao.Test1DaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
编写对应的简单dao和简单service
public class BaseDaoImpl {
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
}
public JdbcTemplate getJdbcTemplate() {
if (jdbcTemplate == null) {
throw new IllegalArgumentException("'dataSource' or 'jdbcTemplate' is required");
}
return jdbcTemplate;
}
}
public class Test1DaoImpl extends BaseDaoImpl {
private static String INSERT_SQL = "insert into tr_test1(`test_name`) values(?)";
public void insert(String testName) {
int count = getJdbcTemplate().update(INSERT_SQL, testName);
log.debug("insert count:{}", count);
}
}
@Service
public class TestServiceImpl {
@Autowired
private Test1DaoImpl test1Dao;
@Transactional
public void insertSingleSourceTrsSuccess() {
test1Dao.insert("testSuccess");
}
}
编写对应的单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/applicationContext.xml"}) //加载spring配置文件 启动入口
public class TestServiceImplTest {
@Autowired
TestServiceImpl testService;
@Test
public void insertSingleSourceTrsSuccess() {
testService.insertSingleSourceTrsSuccess();
}
}
然后我们跑一下单元测试,结果是成功的
简单示例-复盘(添加BeanPostProcessor或者BeanFactoryPostProcessor使用,使依赖注入无效):
- 添加一个BeanPostProcessor的实现(仅仅实现接口,仅仅打印日志,实测,不打日志,也是一样)
@Slf4j
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("postProcessBeforeInitialization beanName:{}", beanName);
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
log.info("postProcessAfterInitialization beanName:{}", beanName);
return null;
}
}
然后跑一下单元测试,失败了
失败的异常见:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testMultiServiceImpl': Unsatisfied dependency expressed through field 'test1Dao'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.riso.spring.dao.Test1DaoImpl#0' defined in class path resource [applicationContext.xml]: Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'dataSource' threw exception; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:128)
at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:108)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:251)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
... 24 more
- 删除掉BeanPostProcessor的实现, 添加一个BeanFactoryPostProcessor的实现(特别注意:beanFactory.getBeansWithAnnotation)
@Slf4j
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
int beanDefinitionCount = beanFactory.getBeanDefinitionCount();
log.info("beanDefinitionCount:{}", beanDefinitionCount);
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
log.info("beanDefinitionName:{}", beanDefinitionName);
}
//Service 上面的代码是没有问题下面的代码是导致问题的关键代码
Map<String, Object> annotationMap = beanFactory.getBeansWithAnnotation(Service.class);
}
}
然后跑一下单元测试,失败了
失败的异常见:(此次导致的问题 ,就是一个NullPointerException,还没有上面的错误的失败例子好分析。单看异常信息会莫名其妙
java.lang.NullPointerException
at com.riso.spring.service.TestServiceImpl.insertSingleSourceTrsSuccess(TestServiceImpl.java:19)
at com.riso.spring.service.TestServiceImplTest.insertSingleSourceTrsSuccess(TestServiceImplTest.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
原因分析及解决方案:
原因分析: 在使用spring的BeanPostProcessor和BeanFactoryPostProcessor的时候,在spring的初始化过程中,提前将还不打算实例化的bean,实例化了,这些实例化的BEAN,其实还有依赖一些配置或者BEAN,从来引发问题解决方案有二个方案:
1.在使用spring的BeanPostProcessor和BeanFactoryPostProcessor的时候,避免提前实例化bean的动作,这块引发的API可能有不少,作者本人也并没有枚举掉所有,需要实际项目人经过测试识别出来。
2.但作者本人认为根本的解决方案:
建立一个BEAN的良好规范、比如说所有的BEAN应用,应该是懒加载的,从硬的代码生成,或软的开发标准上,都偏向于懒加载,避免在Spring本身的组件初始化及依赖引发的初始化,造成失效问题。
查找问题中一些有参考意义的文章:
Spring 的类扫描器分析 - ClassPathBeanDefinitionScanner
浅谈BeanPostProcessor加载次序及其对Bean造成的影响分析
Spring 钩子之BeanFactoryPostProcessor和BeanPostProcessor的源码学习
最后附上我之前写的博文入口:
[1] https://blog.csdn.net/vipshop_fin_dev/article/details/89303458
[2] https://blog.csdn.net/vipshop_fin_dev/article/details/85323076
[3] https://blog.csdn.net/vipshop_fin_dev/article/details/85239099
[4] https://blog.csdn.net/vipshop_fin_dev/article/details/79618067
[5] https://blog.csdn.net/vipshop_fin_dev/article/details/79313432
[6] https://blog.csdn.net/vipshop_fin_dev/article/details/106862227
朱杰
2020-09-20