Spring 初始化 bean 的循环依赖,导致InitializingBean 可能存在的空指针风险

提出问题

在 Spring 的框架中开发,当我们想在服务器启动后,就立马执行一个方法时,用的比较多的就是 implements InitializingBean 然后在 afterPropertiesSet 中做我们想做的事情,下面为示例代码:

public class UserServiceImpl implements UserService, InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

    @Resource
    private MessageService messageService;

    // region spring

    /**
     * 初始化的节点 {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons()}
     */
    @Override
    public void afterPropertiesSet() {
        work();
    }

但是这么做,是有风险的,当两个Bean 出现循环依赖的时候,此时使用 InitializingBean 就会出现当前类中的属性被初始化,但是属性中依赖的属性未被初始化。
下面详细的介绍下我所遇到的问题。

示例

介绍问题

  • 首先有两个服务 MessageService,UserService 然后在其对应的实现类中互相依赖对方。当我们在 UserServiceImpl 中的 afterPropertiesSet() 中去通过 messageService 引用 userService 属性的时候,会发现 MessageServiceImpl 中的 userService 属性为 NULL
  • 代码
public interface MessageService {

    String getMessage();

    String getMessage(String msg);

    boolean checkUserNotNull();

}

@Component
public class MessageServiceImpl implements MessageService, InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(MessageServiceImpl.class);

    @Resource
    private UserService userService;

    // region spring

    /**
     * 初始化的节点 {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons()}
     */
    @Override
    public void afterPropertiesSet() {

        LOGGER.info("init-MessageServiceImpl-afterPropertiesSet-user:{}", userService);
//        LOGGER.info("userNotNull:{}", checkUserNotNull());
    }

    // endregion spring

    // region public

    @Override
    public String getMessage() {
        return "hello world";
    }

    @Override
    public String getMessage(String msg) {
        return String.format("test-%s", msg);
    }

    @Override
    public boolean checkUserNotNull() {
        return userService != null;
    }

    // endregion public
}


public interface UserService {

    String getUser();

    String getUser(String msg);

}

@Component
public class UserServiceImpl implements UserService, InitializingBean{

    private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

    @Resource
    private MessageService messageService;

    // region spring

    /**
     * 初始化的节点 {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons()}
     */
    @Override
    public void afterPropertiesSet() {

        LOGGER.info("init-UserServiceImpl-afterPropertiesSet-message:{}", messageService);
        LOGGER.info("afterPropertiesSet-userNotNull:{}", messageService.checkUserNotNull());

    }

    // endregion spring

    // region public

    @Override
    public String getUser() {
        return "hello world";
    }

    @Override
    public String getUser(String msg) {
        return String.format("test-%s", msg);
    }

    // endregion public
}

谈谈我的理解

在 spring 初始化bean 的时候。在 org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization 会去实例化所有的 bean,接着org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) ,org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(),然后在
在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 中
这里有关键的三步。
1: addSingletonFactory 是为了防止 spring 初始化的时候,如果出现了循环依赖而导致的无限递归。但恰恰也就是这一步导致了上述的问题。
2: 给当前bean 的属性填充实例。
3: 这个步骤中会检测:如果实例实现了 InitializingBean 则调用对应的 afterPropertiesSet() 方法。

image_doCreateBean.png

我们来介绍下第一步,当准备初始化当前 messageService 的时候,会将当前bean 进行一些操作


    /**
     * Add the given singleton factory for building the specified singleton
     * if necessary.
     * <p>To be called for eager registration of singletons, e.g. to be able to
     * resolve circular references.
     * @param beanName the name of the bean
     * @param singletonFactory the factory for the singleton object
     */
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

接着走第二步 populateBean() , 在postProcessPropertyValues 中会去实例化当前类中的属性,也就是 userService。然后在 postProcessPropertyValues inject 所有的属性。


// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
        boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
        boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);

        if (hasInstAwareBpps || needsDepCheck) {
            PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
            if (hasInstAwareBpps) {
                for (BeanPostProcessor bp : getBeanPostProcessors()) {
                    if (bp instanceof InstantiationAwareBeanPostProcessor) {
                        InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                        pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                        if (pvs == null) {
                            return;
                        }
                    }
                }
            }
            if (needsDepCheck) {
                checkDependencies(beanName, mbd, filteredPds, pvs);
            }
        }

// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessPropertyValues
    @Override
    public PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {

        InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
        try {
            metadata.inject(bean, beanName, pvs);
        }
        catch (BeanCreationException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
        }
        return pvs;
    }

// org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#inject
    public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
        Collection<InjectedElement> elementsToIterate =
                (this.checkedElements != null ? this.checkedElements : this.injectedElements);
        if (!elementsToIterate.isEmpty()) {
            boolean debug = logger.isDebugEnabled();
            for (InjectedElement element : elementsToIterate) {
                if (debug) {
                    logger.debug("Processing injected element of bean '" + beanName + "': " + element);
                }
                element.inject(target, beanName, pvs);
            }
        }
    }


        /**
         * Either this or {@link #getResourceToInject} needs to be overridden.
         */
        protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
            if (this.isField) {
                Field field = (Field) this.member;
                ReflectionUtils.makeAccessible(field);
                field.set(target, getResourceToInject(target, requestingBeanName));
            }
            else {
                if (checkPropertySkipping(pvs)) {
                    return;
                }
                try {
                    Method method = (Method) this.member;
                    ReflectionUtils.makeAccessible(method);
                    method.invoke(target, getResourceToInject(target, requestingBeanName));
                }
                catch (InvocationTargetException ex) {
                    throw ex.getTargetException();
                }
            }
        }

我们可以注意到上面的 getResourceToInject() 这里尝试从 org.springframework.context.support.AbstractApplicationContext#getBean() 中拿到 userService 这么一个实例,然后就会走到 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(),org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,最后又绕回到上图 image_doCreateBean.png,只不过这次是尝试 实例化 userService。当执行到 populateBean() 的时候,很容易想到,这个时候又会去尝试实例化 UserServiceImpl 中的 messageService 属性。然后又会走到 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean ,尝试拿到 messageService 实例,可以看到在 doGetBean 中执行到 getSingleton(),而getSingleton 会尝试从 singletonFactories 这个一个属性中取得是否存在对应的实例,如果有则返回。而我们回到上面的 addSingletonFactory(),在执行这个方法的时候,已经将 messageService put 到了 map中。而此时这个messageService 是没有userService属性的,因为初始化 messageService 的栈还没结束。
紧接着 userService 拿到了不完整的 messageService 走完了 populateBean(),接着就去执行了 initializeBean() 方法,也就是会走到 UserServiceImpl 中的 afterPropertiesSet(),而这个时候 userService 中 messageService 的 userService 是不存在的,所以一切就明了了。
可以在 image_initializeBean_user.png 中看到 doCreateBean[1] 代表 实例化 messageService 的过程还未出栈,doCreateBean[2] 表示正在实例化 userService。我们可以看到在进入 initializeBean() 的过程中, UserServiceImpl 中的 messageService 的 userService 为 NULL

       // org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
    @SuppressWarnings("unchecked")
    protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {

        final String beanName = transformedBeanName(name);
        Object bean;

        // Eagerly check singleton cache for manually registered singletons.
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            if (logger.isDebugEnabled()) {
                if (isSingletonCurrentlyInCreation(beanName)) {
                    logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                            "' that is not fully initialized yet - a consequence of a circular reference");
                }
                else {
                    logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
                }
            }
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }
  ...
  }

        // org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
image_initializeBean_user.png

更好的办法

  • 为了解决这种可能存在的 NPE,可以使用 implements ApplicationListener 来解决,在onApplicationEvent() 中是不会存在这种情况的。因为 onApplicationEvent 实在 finishRefresh() 中执行,而此时已经执行过 finishBeanFactoryInitialization() 容器内所有的bean 都初始化完成了。
@Component
public class UserServiceImpl implements UserService, InitializingBean, ApplicationListener<ContextRefreshedEvent> {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

    @Resource
    private MessageService messageService;

    // region spring

    /**
     * 初始化的节点 {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons()}
     */
    @Override
    public void afterPropertiesSet() {

        LOGGER.info("init-UserServiceImpl-afterPropertiesSet-message:{}", messageService);
        LOGGER.info("afterPropertiesSet-userNotNull:{}", messageService.checkUserNotNull());

    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {

        ApplicationContext applicationContext = event.getApplicationContext();

        LOGGER.info("onApplicationEvent-userNotNull:{}", messageService.checkUserNotNull());
    }
}
image_refresh.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。