Spring @Autowiring с общими заводскими bean-компонентами

У меня есть набор классов со сложной схемой инициализации. По сути, я начинаю с интерфейса, который мне нужен, затем делаю кучу вызовов и заканчиваю объектом, реализующим этот интерфейс.

Чтобы справиться с этим, я создал фабричный класс, который при наличии интерфейса может создавать конечный объект. Я превратил эту фабрику в bean-компонент, а в XML я указал, что мои различные сервисные bean-компоненты создаются с помощью этого фабричного объекта с параметром интерфейса, который они будут реализовывать.

Это отлично работает, и я полностью получаю именно те бобы, которые мне нужны. К сожалению, я хотел бы получить к ним доступ из моих классов контроллера, которые обнаруживаются при сканировании компонентов. Я использую здесь @Autowired, и похоже, что Spring не знает, какой это тип объекта, а поскольку @Autowired работает по типу, я SOL.

Использование @Resource (name = "beanName") здесь будет работать отлично, однако кажется странным использовать @Resource для одних bean-компонентов и @Autowired для других.

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

Я, кстати, использую Spring 2.5.6, иначе я бы просто JavaConfig все это забыл.

Заводской класс:

<T extends Client> T buildService(Class<T> clientClass) {
  //Do lots of stuff with client class and return an object of clientClass.
}

контекст приложения:

<bean id="serviceFactoryBean" class="com.captainAwesomePants.FancyFactory" />
<bean id="userService" factory-bean="serviceFactoryBean" factory-method="buildService">
   <constructor-arg value="com.captain.services.UserServiceInterface" />
</bean>
<bean id="scoreService" factory-bean="serviceFactoryBean" factory-method="buildService">
   <constructor-arg value="com.captain.services.ScoreServiceInterface" />
</bean>  

мой контроллер:

public class HomepageController {

   //This doesn't work
   @Autowired @Qualifier("userService") UserServiceInterface userService;

   //This does
   @Resource(name="scoreService") ScoreServiceInterface scoreService;
}

person Brandon Yarbrough    schedule 30.01.2011    source источник


Ответы (3)


Я предлагаю вам пойти дальше по шаблону фабрики и реализуйте свои фабрики как классы Spring FactoryBean. Интерфейс FactoryBean имеет getObjectType() метод, который содержит вызовы для определения того, какой тип вернет фабрика. Это дает вашему автомобилю возможность вникнуть, если завод возвращает разумное значение.

person skaffman    schedule 31.01.2011
comment
Мне это нравится. Сначала я отказывался от FactoryBeans, но, честно говоря, кажется, что они будут делать именно то, что я хочу от них. - person Brandon Yarbrough; 31.01.2011
comment
@CaptainAwesomePants: я стараюсь делать свои реализации FactoryBean как можно более тонкими, делегируя их другому классу, который не использует Spring API. Вы могли бы сделать то же самое - просто делегировать свои полномочия существующему serviceFactoryBean. - person skaffman; 31.01.2011
comment
@skaffman: Итак, в этом случае должно быть столько FactoryBean реализаций, сколько вариантов возвращаемых типов. Будет ли Spring выполнять обнаружение правильного типа, если к factory UserServiceInterface buildUserService() { return buildService(UserServiceInterface.class); } и ScoreServiceInterface buildScoreService() { return buildService(ScoreServiceInterface.class); } будут добавлены два дополнительных метода (и контекст будет перенастроен для их использования)? - person dma_k; 29.11.2011

У меня была аналогичная проблема, но я хотел использовать одну фабрику для создания имитационных реализаций моих зависимостей с автоматическим подключением с помощью JMockit (среда тестирования, которую я должен использовать).

Не найдя удовлетворительного решения в переплетениях, я собрал простое решение, которое мне очень хорошо подходит.

В моем решении также используется Spring FactoryBean, но он использует только один фабричный bean-компонент для создания всех моих bean-компонентов (что, похоже, хотел сделать исходный запросчик).

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

Вот Java для моей фабрики фиктивных компонентов JMockit:

public class MockBeanFactory<C> implements FactoryBean<C> {

    private Class<C> mockBeanType;
    protected MockBeanFactory(){}

    protected  <C> C create(Class<C> mockClass) {
        return Mockit.newEmptyProxy(mockClass);
    }

    @Override
    public C getObject() throws Exception {
        return create(mockBeanType);
    }

    @Override
    public Class<C> getObjectType() {
        return mockBeanType;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public static class MetaFactory {
        public <C> MockBeanFactory<C> createFactory(Class<C> mockBeanType) {
            MockBeanFactory<C> factory = new MockBeanFactory<C>();
            factory.mockBeanType = mockBeanType;
            return factory;
        }
    }
}

А затем в XML-файле контекста Spring вы просто можете просто создать метафабрику, которая создает определенные фабрики типа bean-компонентов:

<bean id="metaFactory" class="com.stackoverflow.MockBeanFactory$MetaFactory"/>

<bean factory-bean="metaFactory" factory-method="createFactory">
    <constructor-arg name="mockBeanType" value="com.stackoverflow.YourService"/>
</bean>

Чтобы это работало для исходной ситуации спрашивающего, его можно настроить так, чтобы FactoryBeans превратился в оболочки / адаптер для serviceFactoryBean:

public class FancyFactoryAdapter<C> implements FactoryBean<C> {

    private Class<C> clientClass;
    private FancyFactory serviceFactoryBean;

    protected FancyFactoryAdapter(){}

    @Override
    public C getObject() throws Exception {
        return serviceFactoryBean.buildService(clientClass);
    }

    @Override
    public Class<C> getObjectType() {
        return clientClass;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public static class MetaFactory {

        @Autowired FancyFactory serviceFactoryBean;

        public <C> FancyFactoryAdapter<C> createFactory(Class<C> clientClass) {
            FancyFactoryAdapter<C> factory = new FancyFactoryAdapter<C>();
            factory.clientClass = clientClass;
            factory.serviceFactoryBean = serviceFactoryBean;
            return factory;
        }
    }
}

Затем в XML (обратите внимание, что идентификатор userServiceFactory и идентификатор компонента userService необходимы только для работы с аннотацией @Qualifier):

<bean id="metaFactory" class="com.stackoverflow.FancyFactoryAdapter$MetaFactory"/>

<bean id="userServiceFactory" factory-bean="metaFactory" factory-method="createFactory">
    <constructor-arg name="clientClass" value="com.captain.services.UserServiceInterface"/>
</bean>

<bean id="userService" factory-bean="userServiceFactory"/>

<bean id="scoreServiceFactory" factory-bean="metaFactory" factory-method="createFactory">
    <constructor-arg name="clientClass" value="com.captain.services.ScoreServiceInterface"/>
</bean>

<bean id="scoreService" factory-bean="scoreServiceFactory"/>

Вот и все, всего лишь один маленький Java-класс и немного конфигурации с шаблонной панелью, а также ваша фабрика пользовательских компонентов может создать все ваши bean-компоненты и успешно разрешить их Spring.

person rees    schedule 09.09.2012
comment
Я голосую за это в основном потому, что это Java, и поэтому объект FactoryFactory всегда правильный! - person Brandon Yarbrough; 10.09.2012

Вы должны добиться этого, используя:

<bean id="myCreatedObjectBean" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetClass">
        <value>com.mycompany.MyFactoryClass</value>
    </property>
    <property name="targetMethod">
        <value>myFactoryMethod</value>
    </property>
</bean>

Затем вы можете использовать @Resource или @Autowired + @Qualifier для непосредственной инъекции в ваш объект.

person IceBox13    schedule 28.11.2011