Spring应用程序上下文 - FactoryBean.getObject()在FactoryBean初始化之前调用

时间:2017-10-30 16:30:43

标签: java spring applicationcontext

我遇到了一些奇怪的问题,在FactoryBean.getObject()初始化之前调用了FactoryBean。我创建了一个小样本,显示出一些奇怪的东西。这不是我在更大的应用程序中看到的确切问题,但我认为它是相关的。

这可能是由循环引用所引起的

  • node1引用node2
  • node2引用node1

我正在使用春季版4.2.4.RELEASE

SpringTestContext.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
>
    <bean id="node1" class="com.hsbc.gbm.dsl.dtm.test.SpringTest$NodeFactory">
        <property name="next" ref="node2" />
    </bean>

    <bean id="node2" class="com.hsbc.gbm.dsl.dtm.test.SpringTest$NodeFactory">
        <property name="prev" ref="node1" />
    </bean>
</beans>

SpringTest.java

public class SpringTest {
    public static class Node {
        private final Node next;
        private final Node prev;
        public Node(Node prev, Node next) {
            super();
            this.prev = prev;
            this.next = next;
        }
    }

    public static class NodeFactory implements FactoryBean<Node>, BeanNameAware, ApplicationContextAware, InitializingBean {
        private Node next;
        private Node prev;
        private String beanName;
        private Node node;
        private ApplicationContext applicationContext;
        private boolean initialized;

        public void setNext(Node next) {
            this.next = next;
        }

        public void setPrev(Node prev) {
            this.prev = prev;
        }

        @Override
        public void setBeanName(String beanName) {
            this.beanName = beanName;
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        @Override
        public Class<?> getObjectType() {
            return Node.class;
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            node = new Node(prev, next);
            initialized = true;
        }

        @Override
        public Node getObject() {
            System.out.println(String.format("NodeFactory: getObject() beanName=%s, initialized=%s, applicationContext=%s", 
                beanName, initialized, applicationContext));

            if (!initialized) {
                throw new RuntimeException("NodeFactory not initialized before getObject() for " + beanName);
            }
            return node;
        }

        public boolean isSingleton() {
            return true;
        }
    }

    @Test
    public void test() {
        ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("com/hsbc/gbm/dsl/dtm/test/SpringTestContext.xml");
        appContext.start();
    }
}

输出

NodeFactory: getObject() beanName=null, initialized=false, applicationContext=null

异常

Caused by: java.lang.RuntimeException: NodeFactory not initialized before getObject() for null
    at com.foo.SpringTest$NodeFactory.getObject(SpringTest.java:74)
    at com.foo.SpringTest$NodeFactory.getObject(SpringTest.java:1)
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168)
    ... 53 more

完整追踪

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'node1' defined in class path resource [com/foo/SpringTestContext.xml]: Cannot resolve reference to bean 'node2' while setting bean property 'next'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'node2' defined in class path resource [com/foo/SpringTestContext.xml]: Cannot resolve reference to bean 'node1' while setting bean property 'prev'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'node1': FactoryBean threw exception on object creation; nested exception is java.lang.RuntimeException: NodeFactory not initialized before getObject() for null
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1481)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1226)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    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:753)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
    at com.foo.SpringTest.test(SpringTest.java:117)
    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:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'node2' defined in class path resource [com/foo/SpringTestContext.xml]: Cannot resolve reference to bean 'node1' while setting bean property 'prev'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'node1': FactoryBean threw exception on object creation; nested exception is java.lang.RuntimeException: NodeFactory not initialized before getObject() for null
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1481)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1226)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    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.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
    ... 38 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'node1': FactoryBean threw exception on object creation; nested exception is java.lang.RuntimeException: NodeFactory not initialized before getObject() for null
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:175)
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:127)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1585)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:254)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
    ... 48 more
Caused by: java.lang.RuntimeException: NodeFactory not initialized before getObject() for null
    at com.foo.SpringTest$NodeFactory.getObject(SpringTest.java:74)
    at com.foo.SpringTest$NodeFactory.getObject(SpringTest.java:1)
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168)
    ... 53 more

1 个答案:

答案 0 :(得分:0)

对于任何发现相同问题的人,我通过删除循环依赖来解决这个问题。我通过为Node属性设置字符串而不是prev来完成此操作。

例如:

    public static class NodeFactory implements FactoryBean<Node>, ... {
        private Node next;
        private String prevBeanId;
        ...
    }

XML

<bean id="node2" class="com.hsbc.gbm.dsl.dtm.test.SpringTest$NodeFactory">
    <property name="prevBeanId" value="node1" />
</bean>

我原本以为Spring会抛出某种形式的CircularReferenceException而不是在Factory.getObject()初始化之前调用它。

在我的实际应用程序中,循环引用更难以看到,因为圆圈中涉及许多bean而不是示例测试用例中的两个bean。