Spring @Autowiring с использованием заводских компонентов - PullRequest
10 голосов
/ 31 января 2011

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

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

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

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

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

Кстати, я использую 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;
}

Ответы [ 3 ]

8 голосов
/ 31 января 2011

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

5 голосов
/ 10 сентября 2012

У меня была похожая проблема, но я хотел использовать одну фабрику для создания макетных реализаций моих автопроводных зависимостей с использованием 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 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-класс и небольшая часть конфигурации рабочей панели и ваша фабрика пользовательских компонентов могут создать все ваши компоненты и заставить Spring успешно их разрешить.

3 голосов
/ 28 ноября 2011

Вы сможете достичь этого, используя:

<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 для непосредственного внедрения в ваш объект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...