(Sottotitolo: per la serie “mi creo da solo il problema e me lo risolvo”…)

Descrizione del problema: ho un programma che tra le altre cose usa bean di tipo Pippo definiti in una libreria; ad un certo punto aggiungo una nuova libreria che definisce la classe SuperPippo, come decoratore di Pippo: quello che vorrei fare è applicare questo decoratore a tutti i bean Pippo della mia configurazione senza modificare il codice esistente del programma e senza dover configurare esplicitamente bean di tipo SuperPippo in aggiunta a quelli già presenti.

In altre parole: voglio che Spring applichi il design pattern Decorator, in maniera trasparente al software (cioè senza toccare il codice esistente).

Andando più in dettaglio, nella prima libreria è presente un’interfaccia chiamata Pippo (non è importante cosa faccia né quanti metodi contenga):

public interface Pippo {
  public boolean isGood(Object o);
}

La libreria (o l’applicativo) contiene anche una o più implementazioni di Pippo, di cui non ci interessa il funzionamento. Basti sapere che l’applicativo è configurato per istanziare dei bean di tipo Pippo; ad esempio (nel caso si usi Java Config, ma la situazione è analoga nel caso di configurazione XML o Groovy):

@Configuration
public class AppConf {
  @Bean
  public Pippo pippo1() {
    return new Pippo1Impl();
  }
}

La libreria di decorazione ha un decoratore:

public class SuperPippo implements Pippo {
  private final Pippo delegate;
  public SuperPippo(Pippo delegate) {
    this.delegate = delegate;
  }
  @Override
  public boolean isGood(Object o) {
    //...
    boolean rv = delegate.isGood(o);
    //...
    return rv ;
  }
}

Ecco la soluzione che ho applicato: creare un bean post-processor che si attivi alla creazione di un bean di tipo Pippo, dopo l’inizializzazione del bean fatta dal framework di Spring:

public class SuperPippoDecoratorBeanPostProcessor implements BeanPostProcessor {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName)
  throws BeansException {
    return bean;
  }
  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName)
  throws BeansException {
    if (bean instanceof Pippo && !(bean instanceof SuperPippo)) {
      SuperPippo decorator = new SuperPippo((Pippo) bean);
      return decorator;
    } else {
      return bean;
    }
  }
}

Per utilizzare il precedente BeanPostProcessor, è sufficiente che esso sia presente nella configurazione dell’applicazione:

@Configuration
public class SuperPippoDecoratorConfiguration {
  @Bean
  public BeanPostProcessor superPippoDecoratorBeanPostProcessor() {
    return new SuperPippoDecoratorBeanPostProcessor();
  }
}

Et voilà: con la sola aggiunta di due piccole classi, il gioco è fatto!

Ambiente usato per lo sviluppo: Java 6, Spring 4.1. Si veda la documentazione di Spring per approfondimenti.

Questa soluzione è abbastanza semplice e veloce, ma ovviamente manca di generalità: va scritto del codice per ogni decorazione di interesse.

Per quanto ne so, Spring non ha un meccanismo per specificare l’utilizzo automatico di decoratori.

Invece nelle specifiche CDI è presente l’annotazione @Decorator (descritta nel JSR-299): cercando il web, ho trovato solo un progetto di implementazione in Spring 3, che non sembra più portato avanti.

Annunci