关于Spring框架应用BeanPostProcessor和BeanFactoryPostProcessor接口导致的注入及事务失效问题

前言:

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使用,使依赖注入无效):

  1. 添加一个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
  1. 删除掉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

猜你喜欢

转载自blog.csdn.net/vipshop_fin_dev/article/details/108693333