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.