FactoryBeans и конфигурация на основе аннотаций в Spring 3.0

Spring предоставляет интерфейс FactoryBean, позволяющий нетривиальную инициализацию bean-компонентов. Фреймворк предоставляет множество реализаций фабричных бинов, и - при использовании конфигурации Spring XML - фабричные бины просты в использовании.

Однако в Spring 3.0 я не могу найти удовлетворительный способ использования фабричных компонентов с конфигурацией на основе аннотаций (в девичестве JavaConfig).

Очевидно, я мог бы вручную создать экземпляр фабричного bean-компонента и сам установить все необходимые свойства, например:

@Configuration
public class AppConfig {

...

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

        return factory.getObject();
    }

Однако это не удалось бы, если бы FactoryBean реализовал какие-либо специфичные для Spring интерфейсы обратного вызова, например, InitializingBean, ApplicationContextAware, BeanClassLoaderAware или @PostConstruct. Мне также нужно проверить FactoryBean, выяснить, какие интерфейсы обратного вызова он реализует, а затем реализовать эту функцию самостоятельно, вызвав setApplicationContext, afterPropertiesSet() и т. Д.

Мне это кажется неудобным и задним числом: разработчикам приложений не нужно реализовывать обратные вызовы контейнера IOC.

Кто-нибудь знает лучшее решение для использования FactoryBeans из конфигураций Spring Annotation?


person Andrew Newdigate    schedule 04.04.2011    source источник


Ответы (6)


Насколько я понимаю, ваша проблема заключается в том, что вы хотите, чтобы результат sqlSessionFactory() был SqlSessionFactory (для использования в других методах), но вы должны вернуть SqlSessionFactoryBean из @Bean-аннотированного метода, чтобы запускать обратные вызовы Spring.

Это можно решить с помощью следующего обходного пути:

@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());
    }
}

Дело в том, что вызовы @Bean-аннотированных методов перехватываются аспектом, который выполняет инициализацию возвращаемых bean-компонентов (FactoryBean в вашем случае), так что вызов sqlSessionFactoryBean() в sqlSessionFactory() возвращает полностью инициализированный FactoryBean.

person axtavt    schedule 04.04.2011
comment
Но я не могу понять, где выгода от использования FactoryBean. Потому что в этом случае мы можем просто создать обычный bean-компонент с помощью Static factory, который вернет bean-компонент, и результат будет таким же. - person Mikhail; 19.03.2018

Я думаю, что это лучше всего решить, если вы полагаетесь на автоподключение. Если вы используете конфигурацию Java для bean-компонентов, это должно быть:

@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);
}

Таким образом, Spring вставит для нас экземпляр MyBean, возвращенный myFactory (). GetObject (), как это происходит с конфигурацией XML.

Это также должно работать, если вы используете @ Inject / @ Autowire в своих @ Component / @ Service и т. Д. Классах.

person tsachev    schedule 26.01.2012
comment
Будет ли экземпляр MyBean одним и тем же каждый раз, когда мы куда-то его внедряем, или для каждой инъекции фабрика будет создавать новый экземпляр? - person Konstantin Zyubin; 09.03.2017
comment
@KonstantinZyubin Это зависит от того, как вы реализуете isSingleton. - person tsachev; 24.05.2017

Spring JavaConfig имел ConfigurationSupport, у которого был метод getObject () для использования с FactoryBean.

Вы бы использовали его для расширения

@Configuration
public class MyConfig extends ConfigurationSupport {

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

Есть некоторая предыстория этой проблемы с jira.

В Spring 3.0 JavaConfig был перенесен в ядро ​​Spring, и было решено избавиться от ConfigurationSupport класс. Предлагаемый подход - теперь использовать шаблон строителя вместо фабрик.

Пример взят из нового SessionFactoryBuilder

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

Справочная информация здесь

person objects    schedule 20.02.2012

Вот что я делаю, и это работает:

@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);
}

Любой объявленный вами bean-компонент может быть передан в качестве аргумента любому другому методу @Bean (повторный вызов того же метода создаст новый экземпляр, который не обрабатывается Spring). Если вы объявляете FactoryBean, вы можете использовать тип компонента, который он создает, в качестве аргумента для другого метода @Bean, и он получит правильный экземпляр. Вы также можете использовать

@Autowired
private SqlSessionFactory sqlSessionFactory;

Куда угодно, и это тоже будет работать.

person Triqui    schedule 26.11.2015
comment
Хотели бы вы создавать каждый раз новый экземпляр сеанса SQl при запуске sqlSessionFactory? - person Mikhail; 19.03.2018

Почему вы не вводите Factory в свою конфигурацию приложений?

@Configuration
public class AppConfig {

    @Resource
    private SqlSessionFactoryBean factory;

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

Но пусть я не правильно понял ваш вопрос. Мне кажется, что вы пытаетесь сделать что-то странное - сделайте шаг назад и подумайте, что вам действительно нужно.

person Ralph    schedule 04.04.2011
comment
Ральф, спасибо за ваш ответ, но он не работает, поскольку FactoryBean (в этом примере SqlSessionFactoryBean, но это может быть что угодно, реализующее org.springframework.beans.factory.FactoryBean - например org.springframework.orm.hibernate3.LocalSessionFactoryBean) не находится под моим контролем ( или в моем источнике) и не аннотируется аннотациями @ Value / @ Resource / @ Autowired / @ Inject. Поэтому Spring не будет правильно внедрять свои зависимости, прежде чем я вызову .getObject(). - person Andrew Newdigate; 04.04.2011
comment
@Andrew Newdigate - сама фабрика является кандидатом для конфигурации XML, или, если вам не нравится XML, создайте ее с помощью другого аннотированного метода @Bean. (тогда вы можете использовать @Value) - person Ralph; 05.04.2011

Вот как я это делаю:

@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
}

Создается sessionFactoryBean, и с ним происходит соответствующий жизненный цикл после создания (afterPropertiesSet и т. Д.).

Обратите внимание, что я не ссылаюсь на sessionFactoryBean напрямую как на bean-компонент. Я автоматически подключаю sessionFactory к другим своим bean-компонентам.

person sourcedelica    schedule 04.04.2011