Last updated Dec 8, 2017

Spring IoC in Confluence

Introduction

The Spring Framework provides an inversion of control (IoC) container that Confluence uses for managing objects at runtime. This document provides an overview of how this relates to Confluence, specifically focused at the needs of plugin developers and those extending Confluence.

If you're looking for the quick overview on how to access Confluence managers from your plugin, check out Accessing Confluence Components from Plugin Modules.

The purpose of an IoC container is to manage dependencies between objects. When you go to use an object in Confluence it will have all its dependencies ready and available to use. For example, calling a method on a PageManager will typically require a PageDao to work correctly. Spring ensures that these dependencies are available when they are needed, with a little bit of guidance from us.

Spring contexts

Confluence uses a number of Spring contexts to separate our objects into discrete subsystems. The contexts are declared as servlet context parameters in confluence/WEB-INF/web.xml. The snippet below shows the Spring contexts listed in web.xml for Confluence 2.3:

1
2
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath:/applicationContext.xml,
        classpath:/securityContext.xml,
        classpath:/databaseSubsystemContext.xml,
        classpath:/indexingSubsystemContext.xml,
        classpath:/eventSubsystemContext.xml,
        classpath:/rpcSubsystemContext.xml,
        classpath:/upgradeSubsystemContext.xml,
        classpath:/wikiSubsystemContext.xml,
        classpath:/wikiFiltersSubsystemContext.xml,
        classpath:/importExportSubsystemContext.xml,
        classpath:/schedulingSubsystemContext.xml,
        classpath:/pluginSubsystemContext.xml,
        classpath:/atlassianUserContext.xml
    </param-value>
</context-param>

What this means is there are 13 context XML files in the Confluence classpath which specify the objects in Confluence which are managed by Spring. When I say 'in the Confluence classpath', in practice I mean they live in confluence/WEB-INF/classes/. The biggest and most important is applicationContext.xml, which we'll have a look at now.

Bean declarations

Around line 100 in the Confluence 2.3 applicationContext.xml, you'll find the schemaHelper bean as a good example:

1
2
<bean id="schemaHelper" class="bucket.core.persistence.hibernate.schema.SchemaHelper">
    <property name="mappingResources">
        <ref local="mappingResources"/>
    </property>
    <property name="hibernateConfig">
        <ref bean="hibernateConfig"/>
    </property>
</bean>

The bean has an ID for Spring to reference it ('schemaHelper'), a class name which will be used to automatically create the bean ('bucket.core.persistence.hibernate.schema.SchemaHelper'), and a number of properties. In this case, the properties are references to other beans in the current context, mappingResources and hibernateConfig.

Because we use the setter injection method in Confluence, this declaration means two things about the SchemaHelper Java class:

  • it must have a public no-args constructor
  • it must have two public methods: setMappingResources() and setHibernateConfig(). Both these must take one argument which is an interface implemented by the appropriate bean.

Other than these two requirements, the SchemaHelper class can be any normal Java class. It can have other constructors, other public methods, and can implement or extend any interface or class that you like.

The purpose of registering a bean in Spring is two-fold:

  1. When you access the SchemaHelper bean through Spring, it will have its mappingResources and hibernateConfig dependencies injected before you use it.
  2. You use the bean as a dependency elsewhere, to automatically get it injected into your own class (more on this below).

Only Confluence beans are registered in Spring via an XML context. Spring Component Plugins are registered at runtime when the plugin is enabled. Other plugin classes such as actions are autowired without registration with Spring.

Autowiring

In the bean declaration for schemaHelper bean above, each property has the same name as the Spring bean which is used to satisfy it. For example, the 'mappingResources' property uses the mappingResources bean, which is set by the setMappingResources() method on the schemaHelper. Spring provides a shortcut for leaving these declarations out, called autowiring.

For example, the declaration for themeManager bean is marked as autowire 'byName' (near line 1000):

1
2
<bean id="themeManager" class="com.atlassian.confluence.themes.DefaultThemeManager" autowire="byName" />

Looking at the DefaultThemeManager class, we see it has four setter methods:

  1. public void setBandanaManager(BandanaManager)
  2. public void setEventManager(EventManager)
  3. public void setGlobalTheme(String)
  4. public void setPluginManager(PluginManager)

Spring looks at the names of the four methods, tries to find beans with IDs of 'bandanaManager', 'eventManager', 'globalTheme', and 'pluginManager'. If they exist, it calls the setter method with the relevant bean as an argument.

In this case, methods 1, 2 and 4 will be called by Spring to inject dependencies. Method 3 (setGlobalTheme) is just a setter used for something else, not called by Spring. This is the drawback of autowiring: it is slow and can waste time trying to find dependencies uselessly.

Using autowiring reduces the need for writing a lot of XML, and also provides a method of dependency injection for objects which aren't registered in the Spring context XML files like plugin modules.

Plugin dependency injection

Almost all Confluence plugin types are autowired. What this means, is if your macro plugin needs to access a Confluence page, it can simply do so like this:

1
2
public class MyMacro extends BaseMacro
{
    private PageManager pageManager;

    public String execute(Map parameters, String body, RenderContext renderContext)
    {
        // ...

        Page page = pageManager.getPage(spaceKey, pageTitle);

        // ...
    }

    // ... implement other methods ...

    /**
     * Called by Spring to inject pageManager
     */
    public void setPageManager(PageManager pageManager)
    {
        this.pageManager = pageManager;
    }
}

Autowired components must use the interfaces used by the manager to work with different versions of Confluence. The implementing class used for various managers may change over time, but the bean ID and interface will be preserved.

Internally, the way the components are autowired is via Confluence's ContainerManager. You can also do this with your own objects if required:

1
2
ContainerManager.autowireComponent(object);

Accessing Spring beans directly

If you need access to Confluence managers or other Spring beans without autowiring your class, you can use the ContainerManager directly. For example, to get the pageManager bean:

1
2
PageManager pageManager = (PageManager) ContainerManager.getComponent("pageManager");

You should always use autowiring in preference to this method because it makes your code easier to change and easier to test. Inside Confluence this method is sometimes required to break circular dependencies.

Transaction proxy beans

Confluence uses Spring's transaction handling by wrapping some objects in transaction proxy beans.

Rate this page: