Last updated Apr 19, 2024

This page explains how to convert your P2 app to use Spring Java configuration, if it currently declares its components in its atlassian-plugin.xml.

POM changes

  1. Add the necessary dependencies.
  2. Add the <Atlassian-Plugin-Key> header to your AMPS <instructions>, to indicate that your plugin should not be transformed at runtime into an OSGi bundle. For example:
1
2
<project ...>
  <!-- ... -->
  <build>
    <plugins>
      <plugin>
        <groupId>com.atlassian.maven.plugins</groupId>
        <artifactId>amps-maven-plugin</artifactId>
        <!-- Or jira-maven-plugin, etc. -->
        <!-- For AMPS pre-8.0, use maven-amps-plugin, maven-jira-plugin, etc. -->
        <version>8.1.0</version>
        <configuration>
          <instructions>
            <!-- You may want to extract the plugin key to a POM property, for reuse elsewhere -->
            <Atlassian-Plugin-Key>my-plugin-key</Atlassian-Plugin-Key>
            <!-- You may need to import some packages whose usage is not detected by the maven-bundle-plugin, or
                 mark some packages as optional, or add version ranges to them, etc. -->
            <Import-Package>*</Import-Package>
            <!-- Ensure the plugin is Spring-powered -->
            <Spring-Context>*</Spring-Context>
            <!-- ... -->
          </instructions>
          <!-- ... -->
        </configuration>
      </plugin>
      <!-- ... -->
    </plugins>
  </build>
  <!-- ... -->
</project>

Create a Java config class

  1. Create a class for your Spring config, called (for example) SpringBeans, in a new package somewhere under src/main/java, for example com.example.myplugin.spring.
  2. Annotate that class with @org.springframework.context.annotation.Configuration.

Change your Spring XML

You will currently have a Spring XML file in src/main/resources/META-INF/spring (it could be called anything ending in .xml). Modify this file as follows:

  • Remove any mentions of the osgi namespace from the root element.
  • Replace any usages of the osgi namespace with equivalent @Bean methods in your Java config class.
  • Register your new @Configuration classes.

Importing services from OSGi

For each service you’re importing via an osgi:reference element in your Spring XML files (if any), create a method like this in your Spring config class:

1
2
@Bean(destroyMethod = "")
public FooService fooService() {
    return importOsgiService(FooService.class);
}

… where the importOsgiService method is statically imported from com.atlassian.plugins.osgi.javaconfig.OsgiServices. The empty destroyMethod attribute is only necessary if the service implements close() or shutdown(), for example the CacheManager API from atlassian-cache; for most services, you can simply use @Bean, with no attributes.

Do the same for each <component-import> element in your plugin XML. Note that since version 0.2.0 of this helper library, you can optionally specify an LDAP filter, just as you could add a filter attribute to <component-import>. Here's an example that works with 0.3.1 and later:

1
2
@Bean
public FooService fooService() {
    return importOsgiService(FooService.class, ImportOptions.defaultOptions().withFilter("(some.key=some.value)"));
}

Here, the FooService will be imported only if it has a some.key property with the value some.value.

To import a collection of services as with osgi:list or osgi:set, since 0.6.0 the following approach may be used:

1
2
@Bean
public List<FooService> fooServices() {
    return importOsgiServiceCollection(list(FooService.class));
}

where importOsgiServiceCollection is statically imported from com.atlassian.plugins.osgi.javaconfig.OsgiServices and list is a static factory method for com.atlassian.plugins.osgi.javaconfig.ServiceCollection. As opposed to importOsgiService, the default "availability" for this method is "optional".

Exporting services to OSGi

Replace each <component … public=”true”> element in your plugin XML with a pair of methods like this in your Spring config class:

1
2

@Bean
public BarService barService() {
    // TODO instantiate and return your BarService implementation
}

@Bean
public FactoryBean<ServiceRegistration> exportBarService(
        final BarService barService) {
    return exportOsgiService(barService, ExportOptions.as(BarService.class));
}

… where the exportOsgiService method is statically imported from com.atlassian.plugins.osgi.javaconfig.OsgiServices.

You can also remove any @Named or @Component annotations from the class that implements BarService (since you’re now instantiating it programmatically).

Defining plugin module types

Replace each <module-type> element in your plugin XML with a pair of methods like this in your Spring config class:

1
2
@Bean
public ListableModuleDescriptorFactory myModuleDescriptorFactory() {
    return new MyModuleDescriptorFactory();
}

@Bean
public FactoryBean<ServiceRegistration> exportMyModuleType(
        final ListableModuleDescriptorFactory moduleDescriptorFactory) {
    return exportAsModuleType(moduleDescriptorFactory);
}

… where the exportAsModuleType method is statically imported from com.atlassian.plugins.osgi.javaconfig.OsgiServices.

Decomposing your config classes [optional]

If your @Configuration class has become too big or messy, you can easily split it up as follows:

  1. Pick one of your config classes (or create a new config class) to be the “main” config class.
  2. Move some/all of its beans to one or more new @Configuration classes, according to whatever categorisation makes sense (for example, put all your web-related beans into MyWebBeans.class, or split up the beans by feature, etc).
  3. At the top of the main config class, add a Spring @Import annotation that lists these subsidiary config classes, for example:
1
2
@Configuration
@Import({
    MyPersistenceBeans.class,
    MyServiceBeans.class,
    MyWebBeans.class
})
public class MainSpringConfig {}

Rate this page: