Making Spring DI work in an Activator (on the API side)

Tags: #<Tag:0x00007f8288c51a68> #<Tag:0x00007f8288c519a0> #<Tag:0x00007f8288c518d8>

Hi all,

I would like to instantiate org.openmrs.module.formentryapp.FormManager in my module’s activator. How do I achieve this so that all its @Autowired members are constructed properly?

If I simply do this:

FormManager formManager = new FormManager();

It “works”, but in fact the @Autowired members are not instantiated. For instance its (FormEntryAppService) appService member remains null. Of course, clearly, this way I simply bypassed Spring DI.

My question is: How can I trigger Spring DI in my module’s activator as it happens in controllers? …where something like @SpringBean("formManager") FormManager formManager would be passed as an argument to some controller’s method.

Use the call below

 //Spring defaults to the simple name as the beanId if none is specified on the bean you are fetching, so in this case it would be formManager   
Context.getRegisteredComponent(beanId, FormManager.class)

I think the method above was added in 1.11, so for older versions you might to do this

Context.getRegisteredComponents(FormManager.class).get(0);

Thank you so much, I am on the 1.11.x branch and this indeed worked:

FormManager formManager = Context.getRegisteredComponent("formManager", FormManager.class);

I guessed that the bean name (bean ID) was "formManager" because that’s what it was in some controllers that I have come across to. But, otherwise, how do I figure out what it is? I guess this is configured in some file somewhere?

To be explicit: OpenMRS module activators are not Spring-managed beans and therefore @Autowired doesn’t automatically work.

What people have typically does is what @wyclif says: use Context.getRegisteredComponents to manually get the Spring beans, and then manually set those on FormManager.

An alternative, which I haven’t seen before, but I think is better (and we should add support for in openmrs-core), would be to do something like:

ApplicationContext appCtx = Context
    .getRegisteredComponent(ApplicationContext.class).get(0);
appCtx.getAutowireCapableBeanFactory().autowireBean(myBean);

Note that you probably need to do either of these approaches in the activator’s started() method, which is called after the Spring application context is loaded.

Out of curiosity. This works:

FormEntryAppService hfeAppService = Context.getRegisteredComponent("formEntryAppService", FormEntryAppService.class);

This does not work:

FormEntryAppService hfeAppService = Context.getService(FormEntryAppService.class);

However some services can be instantiated the (just) above way, such as:

HtmlFormEntryService hfeService = Context.getService(HtmlFormEntryService.class);

How do we know (before trying)?

From this code, the class is picked up because of the @Component annotation but it has no name(bean Id) specified which actually is bad practice for a pluggable project where some other jar files can possibly introduce other classes with the same simple class name which will cause spring to complain. Anyways, spring in this case defaults to the simple class name with the first letter converted to lower case which would make it formManager

@wyclif ok thanks for clarifying this.

As for your question about Context.getService(FormEntryAppService.class) failing, it is because it is not declared as a module service, having @Service annotation doesn’t make it a module service but rather a spring bean. Openmrs’ module engine maintains a list of all registered module services which are autowired via the moduleService property of the ServiceContext object in the module’s application context file as seen below

Which means if something is not explicitly registered as module service it wouldn’t be added to the service context.

1 Like

So, is this a bug in a refapp module that needs to be fixed (i.e. the lack of making the service a real OpenMRS service?)