Doorgaan naar hoofdcontent

Spring's conditional annotation with properties

Spring has a nice @Conditional annotation, to have the option to have beans be available in the context depending a specific condition (Of course, this can also be realized by using @Configuration objects, but that's a different post).

Ideally, we'd have the option to have a condition evaluate to true or false depending on a property. Sadly, Spring does not support that out of the box.
Googling and looking around gives a partial solution, but the complete one here, so we won't forget:

/**
 * Components annotated with ConditionalOnProperty will be registered in the spring context depending on the value of a
 * property defined in the propertiesBeanName properties Bean.
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
    /**
     * The name of the property. If not found, it will evaluate to false.
     */
    String value();
    /**
     * if the properties value should be true (default) or false
     */
    boolean on() default true;
    /**
     * Name of the bean containing the properties.
     */
    String propertiesBeanName();
}

/**
 * Condition that matches on the value of a property.
 *
 * @see ConditionalOnProperty
 */
class OnPropertyCondition implements ConfigurationCondition {
    private static final Logger LOG = LoggerFactory.getLogger(OnPropertyCondition.class);

    @Override
    public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
        final Map attributes = metadata.getAnnotationAttributes(ConditionalOnProperty.class.getName());
        final String propertyName = (String) attributes.get("value");
        final String propertiesBeanName = (String) attributes.get("propertiesBeanName");
        final boolean propertyDesiredValue = (boolean) attributes.get("on");

        // for some reason, we cannot use the environment here, hence we get the actual properties bean instead.
        Properties props = context.getBeanFactory().getBean(propertiesBeanName, Properties.class);
        final boolean propValue = parseBoolean(props.getProperty(propertyName, Boolean.toString(false)));
        LOG.info("Property '{}' resolved to {}, desired: {}", new Object[] { propertyName, propValue, "" + propertyDesiredValue });
        return propValue == propertyDesiredValue;
    }
    /**
     * Set the registration to REGISTER, else it is handled during the parsing of the configuration file
     * and we have no guarantee that the properties bean is loaded/exposed yet
     */
    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;
    }
}
This allows us to have a service or component be available or not upon startup Moreover, it allows us to define two beans *under the same name*, as in:
@ConditionalOnProperty(value="usenew", on=false, propertieBeanName="myprops")
@Service("service")
public class oldService implements ServiceFunction{
// some old implementaion of the service function.
}

@ConditionalOnProperty(value="usenew", on=true, propertieBeanName="myprops")
@Service("service")
public class newService implements ServiceFunction{
// some new implementaion of the service function.
}

Reacties

Populaire posts van deze blog

OSGI insights without sonar

So I was on a project without sonar. Oh my. Well, it was an OSGI project, so the problems couldn't be that bad, right? But how good were they (and what things were bad?) I found Stan4j , a code analysis tool for eclipse, which draws nice graphs and can handle osgi pretty well it seems. Now I can see that dependencies/bundle names aren't properly aligned (even though OSGI doesn't complain), etc.

JPA and transactions

So I was working with JPA and transactions. Consider the following: In bean 1, with implicit TX 1, managed entities are loaded/created,and returned in bean 2, with implicit TX 2, entities are modified in bean 3, with NO TX, bean 1 is called, and the results are passed to bean 2. and bean 4 is similar to bean 3, but with it's own transaction, TX3 What happens when bean 3 finishes: are the entities updated? What happens when bean 4 finishes, are the entities updated? The answer to this is simple; entities are managed through a persistance context. That context is tied to the transaction. So in bean 2, there is a difference. When called from bean 3, it runs in a different transaction then bean 1, and thus a different persistance context, and thus the entities are not managed 'by this transaction'.When called from bean 4, it all runs in the same transaction, TX3, and the results are persisted.