Implementando as Interfaces BeanFactory e FactoryBean em um Container IOC Personalizado

Contexto e Evolução do Container

Após estabelecer a base para parsing de configurações XML e validação de schemas, o próximo passo no desenvolvimento de um container de Inversão de Controle (IOC) é padronizar a obtenção de beans. Até o momento, a recuperação de instâncias estava acoplada à implementação concreta. Para seguir as convenções do ecossistema Spring, é necessário extrair interfaces padrão que permitam flexibilidade e extensão.

Refatoração para BeanFactory

O primeiro requisito é definir um contrato claro para a obtenção de objetos gerenciados. A interface BeanFactory atua como este contrato, abstraindo a lógica de criação e recuperação de instâncias.

A definição da interface foca em dois métodos principais: um genérico para recuperação tipada e outro para recuperação genérica:

public interface BeanFactory {
    <T> T getBean(String identifier, Class<T> type);
    Object getBean(String name);
}

A implementação concreta, agora denominada MinimalContainerV8, deve adaptar sua estrutura para cumprir este contrato. A lógica interna de instanciação e injeção de propriedades permanece similar, mas é encapsulada dentro dos métodos da interface.

import org.apache.commons.beanutils.BeanUtils;
import java.util.Map;

public abstract class MinimalContainerV8 implements BeanFactory {
    private final BeanDefinitionRegistry definitionStore = new SimpleBeanDefinitionRegistry();
    private ResourceLoader loader = new DefaultResourceLoader();

    private void populateProperties(Object instance, BeanDefinition definition) {
        try {
            MutablePropertyValues values = definition.getProperties();
            Map<String, String> props = values.getProperties();
            for (String key : props.keySet()) {
                BeanUtils.setProperty(instance, key, props.get(key));
            }
        } catch (Exception ex) {
            throw new RuntimeException("Falha na injeção de propriedades", ex);
        }
    }

    private Object instantiateBean(BeanDefinition definition) {
        try {
            String className = definition.getBeanClass();
            Object instance = Class.forName(className).getDeclaredConstructor().newInstance();
            populateProperties(instance, definition);
            return instance;
        } catch (Exception ex) {
            throw new RuntimeException("Erro na instanciação do bean", ex);
        }
    }

    @Override
    public <T> T getBean(String identifier, Class<T> type) {
        BeanDefinition definition = definitionStore.getBeanDefinition(identifier);
        return (T) instantiateBean(definition);
    }

    @Override
    public Object getBean(String name) {
        BeanDefinition definition = definitionStore.getBeanDefinition(name);
        return instantiateBean(definition);
    }

    public void setResourceLoader(ResourceLoader loader) {
        this.loader = loader;
    }
}

Introdução ao FactoryBean

Em cenários reais, a instanciação direta via reflexão nem sempre é suficiente. Alguns objetos requerem lógica complexa de construção que não cabe dentro do container padrão. Para resolver isso, introduce-se a interface FactoryBean.

Um FactoryBean é um bean especial que, em vez de ser retornado diretamente, é usado pelo container para produzir outro objeto. Além disso, deve haver um mecanismo para obter a própria instância do factory, caso necessário. Por convenção, utiliza-se o prefixo & no identificador para indicar que se deseja o factory em si, e não o objeto produzido por ele.

A interface é definida da seguinte forma:

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

Adaptação do Container para Suportar FactoryBean

A classe do container precisa ser atualizada para a versão 9 (MinimalContainerV9). As principais mudanças envolvem a resolução do nome do bean e a decisão de retornar o objeto fabricado ou o factory.

O método resolveBeanIdentifier remove o prefixo & para localizar a definição correta no registro. O método resolveBeanObject verifica se a instância criada implementa FactoryBean e decide o que retornar com base no nome original solicitado.

public abstract class MinimalContainerV9 implements BeanFactory {
    private final BeanDefinitionRegistry definitionStore = new SimpleBeanDefinitionRegistry();
    private ResourceLoader loader = new DefaultResourceLoader();
    private static final String FACTORY_PREFIX = "&";

    private boolean isFactoryReference(String name) {
        return name != null && name.startsWith(FACTORY_PREFIX);
    }

    private String resolveBeanIdentifier(String name) {
        if (!isFactoryReference(name)) {
            return name;
        }
        String cleanName = name;
        while (cleanName.startsWith(FACTORY_PREFIX)) {
            cleanName = cleanName.substring(FACTORY_PREFIX.length());
        }
        return cleanName;
    }

    private Object resolveBeanObject(Object instance, String originalName, String cleanName, BeanDefinition def) {
        if (isFactoryReference(originalName)) {
            if (!(instance instanceof FactoryBean)) {
                throw new RuntimeException("Bean " + cleanName + " não é um FactoryBean");
            }
            return instance;
        }
        if (instance instanceof FactoryBean) {
            try {
                return ((FactoryBean<?>) instance).getObject();
            } catch (Exception e) {
                throw new RuntimeException("Erro ao obter objeto do FactoryBean", e);
            }
        }
        return instance;
    }

    // Métodos de instanciação e injeção similares à versão anterior...
    // ... (instantiateBean e populateProperties mantidos)

    @Override
    public <T> T getBean(String name, Class<T> type) {
        return (T) doGetBean(name);
    }

    @Override
    public Object getBean(String name) {
        return doGetBean(name);
    }

    protected Object doGetBean(String name) {
        String cleanName = resolveBeanIdentifier(name);
        BeanDefinition def = definitionStore.getBeanDefinition(cleanName);
        Object instance = instantiateBean(def);
        return resolveBeanObject(instance, name, cleanName, def);
    }
}

Implementação e Verificação

Para validar a funcionalidade, cria-se uma implementação concreta chamada ExtendedUserFactory. Este factory configura um objeto User com valores específicos que seriam difíceis de definir apenas via XML.

public class ExtendedUserFactory implements FactoryBean<User> {
    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public User getObject() {
        User user = new User();
        user.setAge(100);
        user.setName("Usuario Especial");
        return user;
    }
}

A configuração XML deve registrar tanto um bean padrão quanto o factory:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="usuarioPadrao" class="com.example.bean.User">
        <property name="name" value="Usuario Comum"/>
        <property name="age" value="25"/>
    </bean>
    <bean id="criadorUsuario" class="com.example.bean.ExtendedUserFactory"/>
</beans>

O teste unitário confirma o comportamento esperado. Ao solicitar criadorUsuario, o container retorna o objeto User criado pelo factory. Ao solicitar &criadorUsuario, o container retorna a instância do próprio ExtendedUserFactory.

@Test
public void testFactoryBeanImplementation() {
    System.out.println("### Teste Versão 9: FactoryBean ###");
    MinimalContainerV9 container = new MinimalContainerV9Xml("classpath:v9/config.xml");
    
    User commonUser = container.getBean("usuarioPadrao", User.class);
    System.out.println("Bean Padrão: " + commonUser);
    
    Object specialUser = container.getBean("criadorUsuario");
    System.out.println("Objeto do Factory: " + specialUser);
    
    Object factoryInstance = container.getBean("&criadorUsuario");
    System.out.println("Instância do Factory: " + factoryInstance);
}

A execução dos testes demonstra que a distinção entre o bean fabricado e o factory funcionam corretamente, permitindo lógica de criação customizada dentro do container IOC.

Tags: Spring-Framework inversion-of-control bean-factory factory-bean java-backend

Publicado em 8-1 11:49