Last updated Mar 27, 2024

This page describes how apply conditions to Spring beans defined in your P2 app's Spring Java configuration.

Overview

You can apply Conditions to your @Bean methods or even entire @Configuration classes in the usual Spring way. You simply annotate the class or method with @Conditional and nominate the Condition class (or classes) to apply, for example

1
2
@Conditional({SomeCondition.class, SomeOtherCondition.class})
@Bean
public FooService fooService() {
    // instantiate and return the FooService implementation
}

Useful conditions

Our helper library (see Dependencies) includes the following conditions to get you started:

  • AbstractSystemPropertyCondition: subclass this and provide the name and value of a system property. The condition will be true if that property is set to that value.
  • DevModeOnly: returns true if the atlassian.dev.mode system property is set to true.
  • BambooOnly: returns true if the host product is Bamboo.
  • BitbucketOnly: returns true if the host product is Bitbucket Server.
  • ConfluenceOnly: returns true if the host product is Confluence Server.
  • CrowdOnly: returns true if the host product is Crowd.
  • FecruOnly: returns true if the host product is Fisheye/Crucible.
  • JiraOnly: returns true if the host product is Jira Server (or any variant of it).
  • RefappOnly: returns true if the host product is the Atlassian Reference Application.

The @ConditionalOnClass annotation

Sometimes the Conditions listed above are not sufficient to decide whether the class will be present, for instance because it is present in some versions of the product but not others, or is provided by a plugin which might not be installed and enabled.

In these cases you can annotate a config class with @ConditionalOnClass({ClassThatMightNotExist.class, ...}). This will prevent that config class from being processed if any of the classes mentioned cannot be loaded. You can also annotate an @Bean method, but if you annotate a method instead of the entire class you will need to ensure that the class which may not be able to be loaded is not used in the signature of the annotated method, except as a type parameter.

This annotation has the same name and semantics as the equivalent Spring Boot annotation, but it is a separate implementation.

Caveats

There are a couple of things to be aware of when making bean definitions conditional.

Missing types

If a conditional bean method's signature and/or return type refer to a class that is not always on the classpath, i.e. is not available in all intended target environments (for example because it's product or version specific), then you need to do two things:

Avoid OSGi missing package errors

Declare the OSGi import for that class as optional. Otherwise, your plugin will throw an OSGi missing package error (and thereby fail to load) in any environment in which that class does not exist. For example, if you declare a bean method that accepts or returns Jira's IssueService, and your plugin needs to load into products other than Jira, then you should declare that type's package as optional in the AMPS OSGi instructions, as follows:

1
2
<Import-Package>
    com.atlassian.jira.bc.issue;resolution:=optional,
    *
</Import-Package>

Avoid ClassNotFoundExceptions

When Spring processes your Java config classes, it will introspect them using reflection, to detect the bean methods. If any of the types mentioned in these methods' signatures or return types are not on the classpath, Spring will throw a ClassNotFoundException, and your ApplicationContext will not load. The one exception to this is generic types, which are erased by the Java compiler before Spring performs this introspection. There are therefore two ways to prevent ClassNotFoundExceptions being thrown for types that may legitimately not exist:

  • annotate the config class with @Conditional (or @ConditionalOnClass), and apply a condition that will prevent Spring from processing it in situations where the relevant class is known to be absent. For example, if a bean method returns a product-specific type like Jira's IssueService, annotate the config class with the JiraOnly condition mentioned above. Spring will evaluate this class-level condition, and if the running product is not Jira, skip the introspection step altogether, avoiding the ClassNotFoundException. Here's what that might look like:
1
2
       // Spring will skip this entire class if the running product is not Jira
       @Configuration
       @Conditional(JiraOnly.class)
       public class MyJiraOnlyBeans {
  
           @Bean
           public IssueService issueService() {
               return OsgiServices.importOsgiService(IssueService.class);
           }
       }
  • take advantage of the aforementioned type erasure by having the bean method return a Spring FactoryBean<T>, where T is the type that may or may not exist. Because the compiler will erase the reference to T, the absence of that class will not cause Spring to throw a ClassNotFoundException. Here's what this approach might look like:
1
2
       // Spring will unconditionally process this config class...
       @Configuration
       public class MyBeans {
  
           // ... but only create this bean if the running product is Jira
           @Bean
           @Conditional(JiraOnly.class)
           public FactoryBean<IssueService> issueServiceFactoryBean() {
               return OsgiServices.factoryBeanForOsgiService(IssueService.class);
           }
       }

Rate this page: