Unable to inject the PluginSettingsFactory in a Confluence app

I stumbled on a weird behaviour of Confluence regarding dependency injection using the new Spring Scanner (@Scanned/@ComponentImport) approach (see https://bitbucket.org/atlassian/atlassian-spring-scanner). It cost me a few hours to find out what was going on in my Confluence app after all of a sudden the dependency injection of the PluginSettingsFactory stopped working. So I can answer this question myself but I thought I’d share this with the community just in case someone else encounters the same situation.

I’m writing a Confluence plugin targeting Confluence 6.6.0 which needs access to the PluginSettingsFactory to store and read the plugin settings in several places (as described here https://developer.atlassian.com/server/framework/atlassian-sdk/storing-plugin-settings/). I have two classes, which need this factory:

  • a Webwork action which is used for the plugin configuration page
  • a REST controller which wants to read the plugin configuration

At first I wrote my Webwork action class which receives the PluginSettingsFactory through setter injection. The class looks roughly like this (no annotations necessary):

public class AdminConfigurationAction extends ConfluenceActionSupport implements FormAware {

    public void setPluginSettingsFactory(PluginSettingsFactory pluginSettingsFactory) {
        this.pluginSettingsFactory = pluginSettingsFactory;
    }
	
	// ...
}

This worked without any issues. The factory was properly injected and everything was fine.

Next I extended my existing REST controller so that it could access and read the plugin settings. I added the PluginSettingsFactory as a new parameter to the controller’s constructor which at the time declared other Spring components to be injected. The controller class now looked like that:

  @Scanned
  @Path("/configuration")
  public class RestPluginConfigurationService {

    @ComponentImport
    private PluginLicenseManager pluginLicenseManager;

    @ComponentImport
    private UserAccessor userAccessor;

    @Inject
    public RestPluginConfigurationService(PluginLicenseManager pluginLicenseManager, PluginSettingsFactory pluginSettingsFactory, UserAccessor userAccessor) {
        this.pluginLicenseManager = pluginLicenseManager;
        this.userAccessor = userAccessor;
		// do something with the plugin settings factory...
    }
	
	// ...
}

After I redeployed my changes, the dependency injection in the Webwork action suddendly stopped working altogether. The setter AdminConfigurationAction#setPluginSettingsFactory() wasn’t invoked anymore and subsequently I got a NullPointerException whenever the PluginSettingsFactory was accessed.

Now I was at a loss. I tried to add @Scanned and @ComponentImport annotations to the Webwork action and temporarily replaced setter-based injection with constructor-based injection. All I achieved by that was to summon eerie NoSuchBeanDefinitionExceptions telling me that there is no such thing as a PluginSettingsFactory object eligible for injection.

What could I do? The solution follows in my answer below.

After a lot of fussing and tampering with my code, I found out that I missed a small detail in my REST controller: the @ComponentImport annotation for the PluginSettingsFactory. So I changed the constructor of the controller to be

@Inject
public RestPluginConfigurationService(PluginLicenseManager pluginLicenseManager, @ComponentImport PluginSettingsFactory pluginSettingsFactory, UserAccessor userAccessor)
{...}

and lo and behold, everything worked smoothly again. So by missing out a single @ComponentImport annotation in one random place, the dependency injection will stop working altogether for this particular component.

Does anybody have an idea how this can be explained?

Bit late to the party, but that makes sense, actually. Most likely you had @ComponentImport on the other dependencies elsewhere in your code, but not on the PluginSettingsFactory. If you’re using Spring Scanner, you have to declare every dependency that you need to import into your project with a @ComponentImport.