How can I get a list of instantiated beans from Sp

2019-01-11 07:06发布

问题:

I have several beans in my Spring context that have state, so I'd like to reset that state before/after unit tests.

My idea was to add a method to a helper class which just goes through all beans in the Spring context, checks for methods that are annotated with @Before or @After and invoke them.

How do I get a list of instantiated beans from the ApplicationContext?

Note: Solutions which simply iterate over all defined beans are useless because I have many lazy beans and some of them must not be instantiated because that would fail for some tests (i.e. I have a beans that need a java.sql.DataSource but the tests work because they don't need that bean).

回答1:

For example:

 public static List<Object> getInstantiatedSigletons(ApplicationContext ctx) {
            List<Object> singletons = new ArrayList<Object>();

            String[] all = ctx.getBeanDefinitionNames();

            ConfigurableListableBeanFactory clbf = ((AbstractApplicationContext) ctx).getBeanFactory();
            for (String name : all) {
                Object s = clbf.getSingleton(name);
                if (s != null)
                    singletons.add(s);
            }

            return singletons;

    }


回答2:

I had to improve it a little

@Resource
AbstractApplicationContext context;

@After
public void cleanup() {
    resetAllMocks();
}

private void resetAllMocks() {
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    for (String name : context.getBeanDefinitionNames()) {
        Object bean = beanFactory.getSingleton(name);
        if (Mockito.mockingDetails(bean).isMock()) {
            Mockito.reset(bean);
        }
    }
}


回答3:

I am not sure whether this will help you or not.

You need to create your own annotation eg. MyAnnot. And place that annotation on the class which you want to get. And then using following code you might get the instantiated bean.

ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(MyAnnot.class));
for (BeanDefinition beanDefinition : scanner.findCandidateComponents("com.xxx.yyy")){
    System.out.println(beanDefinition.getBeanClassName());
}

This way you can get all the beans having your custom annotation.



回答4:

I've created a gist ApplicationContextAwareTestBase.

This helper class does two things:

  1. It sets all internal fields to null. This allows Java to free memory that isn't used anymore. It's less useful with Spring (the Spring context still keeps references to all the beans), though.

  2. It tries to find all methods annotated with @After in all beans in the context and invokes them after the test.

That way, you can easily reset state of your singletons / mocks without having to destroy / refresh the context.

Example: You have a mock DAO:

public void MockDao implements IDao {

    private Map<Long, Foo> database = Maps.newHashMap();

    @Override
    public Foo byId( Long id ) { return database.get( id ) );

    @Override
    public void save( Foo foo ) { database.put( foo.getId(), foo ); }

    @After
    public void reset() { database.clear(); }
}

The annotation will make sure reset() will be called after each unit test to clean up the internal state.



回答5:

Using the previous answers, I've updated this to use Java 8 Streams API:

@Inject
private ApplicationContext applicationContext;

@Before
public void resetMocks() {
    ConfigurableListableBeanFactory beanFactory = ((AbstractApplicationContext) applicationContext).getBeanFactory();
    Stream.of(applicationContext.getBeanDefinitionNames())
            .map(n -> beanFactory.getSingleton(n))
            // My ConfigurableListableBeanFactory isn't compiled for 1.8 so can't use method reference. If yours is, you can say
            // .map(ConfigurableListableBeanFactory::getSingleton)
            .filter(b -> Mockito.mockingDetails(b).isMock())
            .forEach(Mockito::reset);
}