web-dev-qa-db-fra.com

FactoryBeans et la configuration basée sur les annotations dans Spring 3.0

Spring fournit l'interface FactoryBean pour permettre une initialisation non triviale des beans. Le framework fournit de nombreuses implémentations de beans d'usine et - lorsque vous utilisez la configuration XML de Spring - les beans d'usine sont faciles à utiliser.

Cependant, dans Spring 3.0, je ne trouve pas de manière satisfaisante d'utiliser les beans d'usine avec la configuration basée sur les annotations (née JavaConfig).

De toute évidence, je pouvais instancier manuellement le bean d'usine et définir moi-même toutes les propriétés requises, comme ceci:

@Configuration
public class AppConfig {

...

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource());
        factory.setAnotherProperty(anotherProperty());

        return factory.getObject();
    }

Cependant, cela échouerait si FactoryBean implémentait des interfaces de rappel spécifiques à Spring, comme InitializingBean, ApplicationContextAware, BeanClassLoaderAware ou @PostConstruct par exemple. Je dois également inspecter le FactoryBean, savoir quelles interfaces de rappel il implémente, puis implémenter cette fonctionnalité moi-même en appelant setApplicationContext, afterPropertiesSet() etc.

Cela me semble gênant et direct: les développeurs d'applications ne devraient pas avoir à implémenter les rappels du conteneur IOC.

Quelqu'un connaît-il une meilleure solution pour utiliser les configurations FactoryBeans de Spring Annotation?

24
Andrew Newdigate

Pour autant que je sache, votre problème est ce que vous voulez qu'un résultat de sqlSessionFactory() soit un SqlSessionFactory (pour une utilisation dans d'autres méthodes), mais vous devez renvoyer SqlSessionFactoryBean à partir de une méthode @Bean - annotée afin de déclencher des rappels Spring.

Il peut être résolu avec la solution de contournement suivante:

@Configuration 
public class AppConfig { 
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactoryBean sqlSessionFactoryBean() { ... }

    // FactoryBean is hidden behind this method
    public SqlSessionFactory sqlSessionFactory() {
        try {
            return sqlSessionFactoryBean().getObject();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    @Bean
    public AnotherBean anotherBean() {
        return new AnotherBean(sqlSessionFactory());
    }
}

Le fait est que les appels à @Bean - les méthodes annotées sont interceptés par un aspect qui effectue l'initialisation des beans retournés (FactoryBean dans votre cas), de sorte que l'appel à sqlSessionFactoryBean() dans sqlSessionFactory() renvoie un FactoryBean complètement initialisé.

21
axtavt

Je pense que cela est mieux résolu lorsque vous comptez sur le câblage automatique. Si vous utilisez Java pour les beans, cela voudrait:

@Bean
MyFactoryBean myFactory()
{ 
    // this is a spring FactoryBean for MyBean
    // i.e. something that implements FactoryBean<MyBean>
    return new MyFactoryBean();
}

@Bean
MyOtherBean myOther(final MyBean myBean)
{
    return new MyOtherBean(myBean);
}

Donc Spring nous injectera l'instance MyBean retournée par myFactory (). GetObject () comme elle le fait avec la configuration XML.

Cela devrait également fonctionner si vous utilisez @ Inject/@ Autowire dans vos classes @ Component/@ Service, etc.

22
tsachev

Spring JavaConfig avait une classe ConfigurationSupport qui avait une méthode getObject () à utiliser avec FactoryBean.

Vous l'utiliseriez en étendant

@Configuration
public class MyConfig extends ConfigurationSupport {

    @Bean
    public MyBean getMyBean() {
       MyFactoryBean factory = new MyFactoryBean();
       return (MyBean) getObject(factory);
    }
}

Il y a un peu d'histoire dans ce problème jira

Avec Spring 3.0, JavaConfig a été déplacé dans le noyau Spring et il a été décidé de se débarrasser de la classe ConfigurationSupport . L'approche suggérée consiste à utiliser maintenant le modèle de constructeur au lieu des usines.

Un exemple tiré du nouveau SessionFactoryBuilder

@Configuration
public class DataConfig {
    @Bean
    public SessionFactory sessionFactory() {
        return new SessionFactoryBean()
           .setDataSource(dataSource())
           .setMappingLocations("classpath:com/myco/*.hbm.xml"})
           .buildSessionFactory();
    }
}

Quelques informations ici

5
objects

C'est ce que je fais et ça marche:

@Bean
@ConfigurationProperties("dataSource")
public DataSource dataSource() { // Automatically configured from a properties file
    return new BasicDataSource();
}

@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource); // Invoking dataSource() would get a new instance which won't be initialized
    factory.setAnotherProperty(anotherProperty());
    return factory;
}


@Bean
public AnotherBean anotherBean(SqlSessionFactory sqlSessionFactory) { // This method receives the SqlSessionFactory created by the factory above
    return new AnotherBean(sqlSessionFactory);
}

Tout bean que vous avez déclaré peut être passé en argument à toute autre méthode @Bean (invoquer à nouveau la même méthode créera une nouvelle instance qui ne sera pas traitée par spring). Si vous déclarez un FactoryBean, vous pouvez utiliser le type de bean qu'il crée comme argument pour une autre méthode @Bean, et il recevra la bonne instance. Vous pouvez également utiliser

@Autowired
private SqlSessionFactory sqlSessionFactory;

N'importe où et cela fonctionnera aussi.

2
Triqui

Voici comment je le fais:

@Bean
def sessionFactoryBean: AnnotationSessionFactoryBean = {
  val sfb = new AnnotationSessionFactoryBean

  sfb.setDataSource(dataSource)
  sfb.setPackagesToScan(Array("com.foo.domain"))

  // Other configuration of session factory bean
  // ...

  return sfb
}

@Bean
def sessionFactory: SessionFactory = {
   return sessionFactoryBean.getObject
}

La sessionFactoryBean est créée et les éléments de cycle de vie post-création appropriés lui arrivent (afterPropertiesSet, etc.).

Notez que je ne référence pas directement le sessionFactoryBean en tant que bean. J'ai auto-câblé la sessionFactory dans mes autres beans.

0
sourcedelica

Pourquoi n'injectez-vous pas Factory dans votre AppConfiguration?

@Configuration
public class AppConfig {

    @Resource
    private SqlSessionFactoryBean factory;

    @Bean 
    public SqlSessionFactory sqlSessionFactory() throws Exception {
       return factory.getObjectfactory();    
    }    
}

Mais puis-je ne pas comprendre votre question correctement. Parce qu'il me semble que vous essayez quelque chose d'étrange - reculez et repensez ce dont vous avez vraiment besoin.

0
Ralph