«1. Обзор

В контейнере Spring есть два вида бобов: обычные бобы и фабричные бобы. Spring использует первое напрямую, тогда как второе может создавать объекты самостоятельно, которыми управляет фреймворк.

И, проще говоря, мы можем построить фабричный компонент, реализовав интерфейс org.springframework.beans.factory.FactoryBean.

2. Основы фабричных компонентов

2.1. Реализация FactoryBean

Давайте сначала рассмотрим интерфейс FactoryBean:

public interface FactoryBean {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

Давайте обсудим три метода:

    getObject() — возвращает объект, созданный фабрикой, и это объект, который будет может использоваться контейнером Spring. getObjectType() — возвращает тип объекта, который создает этот FactoryBean. isSingleton() — указывает, является ли объект, созданный этим FactoryBean, одноэлементным

Теперь давайте реализуем пример FactoryBean. Мы реализуем ToolFactory, который производит объекты типа Tool:

public class Tool {

    private int id;

    // standard constructors, getters and setters
}

Сама ToolFactory:

public class ToolFactory implements FactoryBean<Tool> {

    private int factoryId;
    private int toolId;

    @Override
    public Tool getObject() throws Exception {
        return new Tool(toolId);
    }

    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }

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

    // standard setters and getters
}

Как мы видим, ToolFactory — это FactoryBean, который может создавать объекты Tool.

2.2. Использование FactoryBean с конфигурацией на основе XML

Давайте теперь посмотрим, как использовать нашу ToolFactory.

Мы начнем создавать инструмент с конфигурацией на основе XML – factorybean-spring-ctx.xml:

<beans ...>

    <bean id="tool" class="com.baeldung.factorybean.ToolFactory">
        <property name="factoryId" value="9090"/>
        <property name="toolId" value="1"/>
    </bean>
</beans>

Затем мы можем проверить правильность внедрения объекта Tool:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:factorybean-spring-ctx.xml" })
public class FactoryBeanXmlConfigTest {
    @Autowired
    private Tool tool;

    @Test
    public void testConstructWorkerByXml() {
        assertThat(tool.getId(), equalTo(1));
    }
}

~~ ~ Результат теста показывает, что нам удалось внедрить объект инструмента, созданный ToolFactory, со свойствами, которые мы настроили в factorybean-spring-ctx.xml.

Результат теста также показывает, что контейнер Spring использует объект, созданный FactoryBean, вместо себя для внедрения зависимостей.

Хотя контейнер Spring использует возвращаемое значение метода getObject() FactoryBean в качестве компонента, вы также можете использовать сам FactoryBean.

Чтобы получить доступ к FactoryBean, вам просто нужно добавить «\u0026» перед именем компонента.

Давайте попробуем получить фабричный компонент и его свойство factoryId:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:factorybean-spring-ctx.xml" })
public class FactoryBeanXmlConfigTest {

    @Resource(name = "&tool")
    private ToolFactory toolFactory;

    @Test
    public void testConstructWorkerByXml() {
        assertThat(toolFactory.getFactoryId(), equalTo(9090));
    }
}

2.3. Использование FactoryBean с конфигурацией на основе Java

Использование FactoryBean с конфигурацией на основе Java немного отличается от конфигурации на основе XML, вы должны явно вызвать метод getObject() FactoryBean.

Преобразуем пример из предыдущего подраздела в пример конфигурации на основе Java:

@Configuration
public class FactoryBeanAppConfig {
 
    @Bean(name = "tool")
    public ToolFactory toolFactory() {
        ToolFactory factory = new ToolFactory();
        factory.setFactoryId(7070);
        factory.setToolId(2);
        return factory;
    }

    @Bean
    public Tool tool() throws Exception {
        return toolFactory().getObject();
    }
}

Затем мы проверяем правильность внедрения объекта Tool:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = FactoryBeanAppConfig.class)
public class FactoryBeanJavaConfigTest {

    @Autowired
    private Tool tool;
 
    @Resource(name = "&tool")
    private ToolFactory toolFactory;

    @Test
    public void testConstructWorkerByJava() {
        assertThat(tool.getId(), equalTo(2));
        assertThat(toolFactory.getFactoryId(), equalTo(7070));
    }
}

Результат теста показывает аналогичный эффект. как предыдущий тест конфигурации на основе XML.

3. Способы инициализации

Иногда вам нужно выполнить некоторые операции после установки FactoryBean, но до вызова метода getObject(), например, проверка свойств.

Вы можете добиться этого, реализовав интерфейс InitializingBean или используя аннотацию @PostConstruct.

Более подробная информация об использовании этих двух решений представлена ​​в другой статье: Руководство по запуску логики при запуске в Spring.

4. AbstractFactoryBean

Spring предоставляет AbstractFactoryBean как простой суперкласс шаблона для реализаций FactoryBean. С помощью этого базового класса мы теперь можем более удобно реализовать фабричный компонент, который создает объект-одиночку или объект-прототип.

Давайте реализуем SingleToolFactory и NonSingleToolFactory, чтобы показать, как использовать AbstractFactoryBean как для одноэлементного, так и для прототипного типа:

public class SingleToolFactory extends AbstractFactoryBean<Tool> {

    private int factoryId;
    private int toolId;

    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }

    @Override
    protected Tool createInstance() throws Exception {
        return new Tool(toolId);
    }

    // standard setters and getters
}

А теперь не одиночная реализация:

public class NonSingleToolFactory extends AbstractFactoryBean<Tool> {

    private int factoryId;
    private int toolId;

    public NonSingleToolFactory() {
        setSingleton(false);
    }

    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }

    @Override
    protected Tool createInstance() throws Exception {
        return new Tool(toolId);
    }

    // standard setters and getters
}

Кроме того, конфигурация XML для этих фабричных компонентов :

<beans ...>

    <bean id="singleTool" class="com.baeldung.factorybean.SingleToolFactory">
        <property name="factoryId" value="3001"/>
        <property name="toolId" value="1"/>
    </bean>

    <bean id="nonSingleTool" class="com.baeldung.factorybean.NonSingleToolFactory">
        <property name="factoryId" value="3002"/>
        <property name="toolId" value="2"/>
    </bean>
</beans>

Теперь мы можем проверить, внедряются ли свойства объектов Worker так, как мы ожидаем:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:factorybean-abstract-spring-ctx.xml" })
public class AbstractFactoryBeanTest {

    @Resource(name = "singleTool")
    private Tool tool1;
 
    @Resource(name = "singleTool")
    private Tool tool2;
 
    @Resource(name = "nonSingleTool")
    private Tool tool3;
 
    @Resource(name = "nonSingleTool")
    private Tool tool4;

    @Test
    public void testSingleToolFactory() {
        assertThat(tool1.getId(), equalTo(1));
        assertTrue(tool1 == tool2);
    }

    @Test
    public void testNonSingleToolFactory() {
        assertThat(tool3.getId(), equalTo(2));
        assertThat(tool4.getId(), equalTo(2));
        assertTrue(tool3 != tool4);
    }
}

Как видно из тестов, SingleToolFactory создает объект-одиночку, а NonSingleToolFactory создает объект-прототип.

Обратите внимание, что нет необходимости устанавливать свойство singleton в SingleToolFactory, потому что в AbstractFactory значение свойства singleton по умолчанию равно true.

5. Заключение

Использование FactoryBean может быть хорошей практикой для инкапсуляции сложной логики построения или упрощения настройки объектов с широкими возможностями настройки в Spring.

«Итак, в этой статье мы представили основы того, как реализовать наш FactoryBean, как использовать его как в конфигурации на основе XML, так и в конфигурации на основе Java, а также некоторые другие различные аспекты FactoryBean, такие как инициализация FactoryBean и AbstractFactoryBean.

Как всегда, полный исходный код находится на GitHub.