4
votes

I'm facing an issue trying to define a context hierarchy using AnnotationConfigApplicationContext.

The problem is when defining a module context inside beanRefContext.xml and setting the 'parent' property with another context (XML/Annotated based).

Example:

beanRefContext.xml in module A

<bean id="moduleA_ApplicationContext"  
      class="org.springframework.context.support.ClassPathXmlApplicationContext">
    <property name="configLocations">
        <list>
            <value>classpath:db-context.xml</value>
        </list>
    </property>
</bean>

db-context.xml

<bean id="dataSource" 
      class="org.apache.commons.dbcp.BasicDataSource" 
      destroy-method="close"
      p:driverClassName="org.h2.Driver"
      p:url="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;TRACE_LEVEL_SYSTEM_OUT=2"/>

<!-- Hibernate Session Factory -->
<bean name="sessionFactory" 
      class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="useTransactionAwareDataSource" value="true"/>
        <property name="packagesToScan">
            <list>
                <value>com.example.model</value>
            </list>
        </property>
        <property name="hibernateProperties">
        <!-- hibernate props -->
        </property>
</bean>

beanRefContext.xml in module B

<bean id="moduleB_ApplicationContext" 
      class="org.springframework.context.annotation.AnnotationConfigApplicationContext" >
    <property name="parent" ref="moduleA_ApplicationContext"/>
        <constructor-arg>
            <list>
                <value>com.example.dao</value>
            </list>
        </constructor-arg>
</bean>

FooHibernateDao

class FooHibernateDao implements FooDao {
    @Autowired
    @Qualifier("sessionFactory")
    private SessionFactory sessionsFactory;

    // CRUD methods
}

Module B application context fails to find bean defined in module A application context.
From looking at the code of AnnotationConfigApplicationContext it seems that the scanning process doesn't use the parent as a reference to resolve beans.

Is there something I'm doing wrong or my attempt to create a hierarchy is impossible with annotation configuration?

5
This should work fine. Can you give an example of the bean definition that isn't being found, and how the child context is trying to resolve it?skaffman
db-context.xml has datasource and sessionFactory configured in it (simple XML bean configuration), but when trying to autowire them in module_B application context it says it can not find sessionsFactory to satisfy dao dependencies.Bivas
Please edit your question, showing us the relevant components. Your description is fine, but there's something about the specifics that's stopping it from working.skaffman
@skaffman I added the components, hope it helpsBivas

5 Answers

7
votes

The problem stems from the fact that the constructor of the AnnotationConfigApplicationContext does the scan. Thus the parent is not set at this stage, it is only set after the scan is done as the parent is set by a property - thus the reason why it does not find your bean.

The default AnnotationConfigApplicationContext bean does not have a constructor that takes a parent factory - not sure why.

You can either use the normal xml based application context and configure your annotation scanning in there or you can create a custom fatory bean that will do create the annotation application context. This would specify the parent reference and then do the scan.

Take a look at the source...

The factory would look like this:

public class AnnotationContextFactory implements FactoryBean<ApplicationContext> {

private String[] packages;
private ApplicationContext parent;

@Override
public ApplicationContext getObject() throws Exception {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.setParent(parent);
    context.scan(packages);
    context.refresh();
    return context;
}

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

@Override
public boolean isSingleton() {
    return true;
}

public void setPackages(String... args) {
    this.packages = args;
}

public void setParent(ApplicationContext parent) {
    this.parent = parent;
    }
}

And your bean definition:

<bean id="moduleB_ApplicationContext" class="za.co.test2.AnnotationContextFactory">
    <property name="parent" ref="moduleA_ApplicationContext" />
    <property name="packages">
        <list>
            <value>za.co.test2</value>
        </list>
    </property>
</bean>
3
votes

Don't use XML for the child context. Use ctx.setParent then ctx.register. Like this:

public class ParentForAnnotationContextExample {

    public static void main(String[] args) {
        ApplicationContext parentContext = new AnnotationConfigApplicationContext(ParentContext.class);

        AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
        childContext.setParent(parentContext);
        childContext.register(ChildContext.class); //don't add in the constructor, otherwise the @Inject won't work
        childContext.refresh();

        System.out.println(childContext.getBean(ParentBean.class));
        System.out.println(childContext.getBean(ChildBean.class));

        childContext.close();
    }

    @Configuration
    public static class ParentContext {
        @Bean ParentBean someParentBean() {
            return new ParentBean();
        }
    }

    @Configuration
    public static class ChildContext {
        @Bean ChildBean someChildBean() {
            return new ChildBean();
        }
    }

    public static class ParentBean {}

    public static class ChildBean {
        //this @Inject won't work if you use ChildContext.class in the child AnnotationConfigApplicationContext constructor
        @Inject private ParentBean injectedFromParentCtx;
    }
}
1
votes

I run into the same problem,

Another possibility is to extend AnnotationConfigApplicationContext and add just the required constructor or build the context programmatically, if you are instantiating the AnnotationConfigApplicationContext from java.

0
votes

What I did was the following:

BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance("classpath:beanRefContext.xml");
BeanFactoryReference parentContextRef = locator.useBeanFactory("ear.context");
ApplicationContext parentContext = (ApplicationContext) parentContextRef.getFactory();
childContext.setParent(parentContext);

And guess what, it worked :)

PS: If anyone knows how to replace the classpath:beanRefContext.xml with an @Configuration class, please let us all know.

0
votes

I also run into a similar problem and after some research I found the following approach using a constructor from AnnotationConfigApplicationContext that allows to set the a hierarchy between contexts

DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(parentContext);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(lbf);
context.register(annotatedClass1.class, annotatedClass2.class);
context.refresh();