blog.smart-java.nl
Ordina J-Technologies – Java Blog



Activering van je Spring beans configurabel maken

By: Michel.Schudel, 27 June 2008

Op een huidig project werken we aan een routeringscomponent waarin een aantal JMS listeners en endpoints hangen. Er komen berichten op de listeners binnen, die gerouteerd moeten worden naar een endpoint. Nu kan je voor ESB-achtige oplossingen beter terecht bij opensource producten als Mule of ServiceMix, maar daar wil ik het niet over hebben.

De listeners zijn geimplementeerd met Spring MessageListenerContainer types, en staan gewired in de Spring applicatie context:

  <bean id="myMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  ...properties..
</bean>

Het issue is nu dat de JMS listeners dienen in bepaalde omgevingen (zoals een aantal acceptatie en productie omgevingen) geactiveerd dienen te worden, en in andere omgevingen niet. Dit moet op zo’n manier kunnen dat het voor een beheer organisatie gemakkelijk is deze componenten aan en uit te zetten.

Je kan een Spring component natuurlijk uitcommentarieren in de applicationContext, maar je wilt een beheerclub eigenlijk niet opzadelen met zo’n taak, omdat deze taak vrij technisch is en behoorlijk foutgevoelig. Bovendien wil je niet dat beheer in je applicatie wiring wijzigingen gaat zitten maken. Import / configuratie van verschillende applicationContexts in verschillende omgevingen levert ook teveel overhead op.

Hoe maken we dit nu toch beheersbaar?

Een mogelijkheid zou zijn om het lazy-init attribuut van een Spring bean te wijzigen. Voor een bean als een MessageListenerContainer zou dit betekenen dat in het geval van lazy-init="true" de bean nooit wordt opgetuigd, aangezien er geen andere beans zijn die een referentie naar deze bean hebben.

  <bean id="myMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" lazy-init="true">
  ...properties..
</bean>

Echter levert dit nog steeds geen configurabel component voor beheer op, in het bovenstaande voorbeeld wordt de bean nooit gestart. Je zou geneigd zijn om te zeggen: los het met een externe property op:

  <bean id="myMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" lazy-init="${myListener.enabled}">
  ...properties..
</bean>

De PropertyPlaceholderConfigurer postprocessor kan dit echter niet aan, aangezien deze wel in kan grijpen na de constructie van het object maar niet ervoor. Er is ook geen mogelijkheid om via een eigen geschreven postprocessor het aanroepen van een init methode op een bean (wat in het geval van een MessageListenerContainer daadwerkelijk de JMS connnectie opzet) te onderdrukken.

Om toch het issue te kunnen oplossen heb ik een ConditionalInstantiationBean geschreven, die op basis van een beanName string property en een instantiate boolean een Spring bean uit de context ophaalt, waardoor deze geconstrueerd en geinitializeerd wordt:

package nl.project.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;

/**
 * A Spring bean-factory-aware bean that will onyl instantiate the bean with
 * name beanName if the property instantiate is set to true. Use this bean for
 * being able to control instantiation of beans depending on a true|false value
 * that is imported as a property from an external property file.
 *
 * It is not possible to do this with the "lazy-init" because you cannot switch
 * this value with an external property.
 *
 */
public class ConditionalInstantiationBean implements BeanFactoryAware,
        InitializingBean {

    /** Logger available to subclasses */
    protected final Log logger = LogFactory.getLog(getClass());

    private BeanFactory beanFactory;

    private String beanName;

    private boolean instantiate;

    /**
     * Will instantiate the target bean defined by beanName if instantiate is
     * set to true.
     */
    public void afterPropertiesSet() throws Exception {
        if (instantiate) {
            if (logger.isDebugEnabled()) {
                logger.debug("Instantiating bean with name '" + beanName + "'");
            }
            //keeping result is not necessary since we only want to instantiate
            // the bean.
            beanFactory.getBean(beanName);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Skipping instantiation of bean with name '"
                        + beanName + "'");
            }

        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
     */
    public void setBeanFactory(BeanFactory bf) throws BeansException {
        this.beanFactory = bf;
    }

    /**
     * @param beanName
     *            The beanName to set.
     */
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    /**
     * @param instantiate
     *            The instantiate to set.
     */
    public void setInstantiate(boolean instantiate) {
        this.instantiate = instantiate;
    }
}

En hieronder de Spring configuratie:

  <bean id="conditionalInstantiationBean" class="nl.project.util.ConditionalInstantiationBean">
    <property name="beanName" value="myMessageListenerContainer"/>
    <property name="instantiate" value="${listener.enabled}"/>
</bean>

Hierbij de volgende toelichting:

  • De property beanName is een String en geen referentie naar de listener. Een referentie zou immers betekenen dat de listener bean automatisch wordt opgetuigd!
  • De property instantiate kan nu met een PropertyPlaceholderConfigurer geexternaliseerd worden, bijvoorbeeld in een property file. In de property file komt dan een listener.enabled property die door een beheerclub aan-en uitgezet kan worden!
  • Het lazy-init attribuut van de listener dient wel op true te worden gezet, om automatische activering van de bean te voorkomen:
  •   <bean id="myMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer" lazy-init="true">
      ...properties..
    </bean>
    

Verbeteringen zouden nog zijn: testen of de target bean wel “lazy” geinitialiseerd wordt, aangezien anders de ConditionalInstantiationBean natuurlijk niets te doen heeft.
Ik kan me ook voorstellen dat iemand een mooie AOP oplossing voor dit issue heeft. Als dat zo is, hoor ik het graag!

6 reacties op “Activering van je Spring beans configurabel maken”

  1. Jan-Kees van Andel zegt:

    Hmm, ik dacht altijd dat die properties in Spring vrij krachtig waren, maar dat valt tegen… :(

    Wat ik weleens doe met configuratiebestanden waar je geen enkele mogelijkheid hebt om properties ofzo te gebruiken (zoals een web.xml), maar die je toch omgevings specifieke waarden wil laten bevatten , is gewoon Ant de properties erin laten zetten tijdens het builden. Dan moet je per omgeving wel opnieuw builden, maar het is minder foutgevoelig dan handwerk.

    Onder andere te gebruiken voor configuratie settings in web.xml, zoals de auth-method of om debug filters etc. aan en uit te zetten.

    http://ant.apache.org/manual/CoreTasks/filter.html

  2. admin zegt:

    JK, wie gebruikt er nog Ant, erg old-skool :-)

  3. Ron Thijssen zegt:

    @Stephan
    Met ant heb je ten minste alles nog in de hand :)

  4. Jan-Kees van Andel zegt:

    En die Ant task kun je gewoon in Maven hangen. Dan doe je de rest van de build met Maven. Dat was ik idd vergeten te melden.

  5. johan zegt:

    Of je zorgt ervoor dat Maven de placeholders in je resources met de juiste waarden vult. Maven heeft hiervoor een filter mechanisme zie:
    Zie: http://maven.apache.org/guides/getting-started/index.html#How_do_I_filter_resource_files

  6. Jan-Kees van Andel zegt:

    Die is idd nog mooier. Scheelt weer wat Ant classpath gekloot en zo.

Laat een reactie achter