Tricking Out Spring Bean Factories – How to Embed and Programmatically Define Beans in Spring

I recently had the task of writing an integration test utility that would setup a complex Java Content Repository (JCR) for use by integration tests.  This turned out to be much more fun than I thought all because of Spring ;).  As I’ve worked with Spring over the years, I’ve found that staying in of the normal mode of declaring Spring xml files and letting it rule your application works great.  However, once you start treating Spring like a 2nd class citizen and telling it what to do, it will make your life pretty difficult.  One thing I will say though, is just about anything is possible; you will just have to work at it.  In my case, trial, error, and luckily happening upon useful javadoc eventually got me the answer.

Here is what I set out to do…

  1. I needed to control the loading of Spring in an encapsulated way, inside my utility class.  I did not want to require every JUnit test that uses my utility to have to annotate with a  @RunWith(SpringJUnit4ClassRunner.class) and a @ContextConfiguration(locations = { “classpath:/some.spring.xml” }).  I wanted the JUnit test writer to not care about how the JCR subsystem was configured and started.
  2. The test utility needs to be able to read more than one Spring beans xml file for configuration.  Two Spring beans files are required to configure the test JCR environment: the production-ready one, plus one with a few overrides specific to the test environment.
  3. Lastly, and the requirement that was the most difficult to get working, I needed to programmatically configure/override one of the beans that was declared prior by one of Spring xml files.  Spring was managing the jcrRepository factory bean, which writes data to a specified dir on your filesystem.  In my integration test environment, I needed that folder to be unique for each test run.  That’s why declaring the “home” dir in a Spring xml file just wouldn’t do.  The test utility needed to generate a unique dirname.

After many different attempts at using ApplicationContexts and BeanFactories in various interesting ways, I came upon the answer.  DefaultListableBeanFactory is the best suited Spring bean factory for complex configuration.  Once I discovered it’s ability to both programmatically define beans *and* read in more than one Spring xml bean file, the problem was solved.  Here is a snippet showing how, in my test utility class, I was able to instantiate a DefultListableBeanFactory.  The code illustrates using XmlBeanDefinitionReader to configure beans based on Spring xml files, then how to programmatically define an additional bean (with the unique dir name as a property).

    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader bdr = new XmlBeanDefinitionReader(beanFactory);
    bdr.loadBeanDefinitions(new ClassPathResource("repository.spring.xml"));
    bdr.loadBeanDefinitions(new ClassPathResource("repository-test-override.spring.xml"));
    BeanDefinitionBuilder beanBuildr = BeanDefinitionBuilder
        .rootBeanDefinition("org.springframework.extensions.jcr.jackrabbit.RepositoryFactoryBean");
    beanBuildr.setScope(BeanDefinition.SCOPE_SINGLETON);
    beanBuildr.addPropertyValue("configuration", new ClassPathResource("/jackrabbit-test-repo.xml"));
    beanBuildr.addPropertyValue("homeDir", new FileSystemResource(tempDir));
    beanFactory.registerBeanDefinition("jcrRepository", beanBuildr.getBeanDefinition());
Advertisements

About phytodata

Lead software engineer with broad experience over 13 years designing and writing Java based open source and commercially licensed applications ranging from healthcare to banking to business intelligence and telecom.
This entry was posted in Uncategorized and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s