Refresh/rebuild specific beans during Spring integration testing

Craig Otis :

Our existing Spring Boot integration setup was using @DirtiesContext to rebuild the entire bean pool in-between different test methods.

This was fairly slow, and so we started working with beans that could be "refreshed" or torn down/rebuild internally without re-creating the instance.

The problem is that only some beans support this. If we control UsersBean, we can implement a UsersBean.refresh() method and call it in our @After method.

But if we have existing beans/classes that don't support refreshing, or we don't control, how can we conditionally indicate that certain beans need to be dirtied/rebuilt after a specific test?

Or more succinctly: Is there a way to mark as dirty a subsection of your bean pool, for rebuilding, at the end of a test method?

df778899 :

It looks like this is possible, at least within a Spring Boot environment. The ApplicationContext implementation there is a GenericApplicationContext which has the ability to removeBeanDefinition(), which can then be re-registered via registerBeanDefinition().

This even cascades through to remove beans that hold a reference to the bean that's being removed (the implementation of this can be seen in DefaultSingletonBeanRegistry.destroyBean()).

For example if Bean1 is referenced by Bean2 :

@Component
public class Bean1 {

}

@Component
public class Bean2 {
    @Autowired
    public Bean1 bean1;
}

Then a test can remove bean1 from the context, and see bean2 replaced as well:

@RunWith(SpringRunner.class)
public class BeanRemovalTest implements ApplicationContextAware {
    @Autowired
    private Bean1 bean1;

    @Autowired
    private Bean2 bean2;

    private ApplicationContext applicationContext;

    @Test
    public void test1() throws Exception {
        System.out.println("test1():");
        System.out.println("  bean1=" + bean1);
        System.out.println("  bean2.bean1=" + bean2.bean1);

        resetBean("bean1");
    }

    @Test
    public void test2() throws Exception {
        System.out.println("test2():");
        System.out.println("  bean1=" + bean1);
        System.out.println("  bean2.bean1=" + bean2.bean1);
    }

    private void resetBean(String beanName) {
        GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext;
        BeanDefinition bd = genericApplicationContext
                .getBeanDefinition(beanName);
        genericApplicationContext.removeBeanDefinition("bean1");
        genericApplicationContext.registerBeanDefinition("bean1", bd);
    }

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

This shows both bean instances being replaced:

test1():
  bean1=hello.so.Bean1@61d6015a
  bean2.bean1=hello.so.Bean1@61d6015a

test2():
  bean1=hello.so.Bean1@2e570ded
  bean2.bean1=hello.so.Bean1@2e570ded

(If the resetBean("bean1") is commented out, it is the same instance both times round).

There are bound to be edges where this doesn't work out - e.g. if another bean is holding onto a reference obtained from ApplicationContext.getBean().

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=97017&siteId=1