O Spring possui um mecanismo de hooks que nos permitem manipular os beans sobre seu contexto quando estes são instanciados, também conhecido como BeanPostProcessor.
Esta interface define dois métodos:
- postProcessAfterInitialization – Intercepta um bean dentro do contexto da aplicação que já foi instanciado e suas propriedades já foram carregadas, porém, depois de chamadas a mêtodos customizados de inicialização, por exemplo.
- postProcessBeforeInitialization - Intercepta um bean dentro do contexto da aplicação que já foi instanciado e suas propriedades também já foram carregadas, porém, antes de chamadas a mêtodos customizados de inicialização, por exemplo.
Com este gancho podemos criar um utilitário que injete a dependência de uma classe do tipo org.apache.log4j.Logger nas classes que necessitem fazer algum uso do mecanismo de log, através de uma anotação ou por convenção mesmo.
Definição da anotação de Log:
/**
* Anotação marcadora de log.
*
* @author Erico Marineli
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Log {}
Apeser de ter uma anotação, vou utilizar também um mecanismo de convencão, o seja, caso o desenvolvedor chamar o atributo do tipo Logger de “log” ou “logger”, não será necessário o uso da anotação, o logger será adicionado automaticamente.
Definição classe que fará todo o trabalho para injeção:
/**
* PostProcessor para acrescentar no bean um Logger provido pelo LOG4J. O logger
* pode ser inserido via a anotação Log ou por convenção (logger ou log)
*
* @author Erico Marineli
*
*/
public class Log4jBeanPostProcessor implements BeanPostProcessor {
/**
* Realiza cache das instâncias de log criadas.
*/
private static Map<class <?>, Logger> cacheLoggers = new HashMap</class><class <?>, Logger>();
/**
* Nome de pacote para filtro. Util para restringir a busca pela anotação e convencões
* apenas em classes do projeto.
*/
private String packageFilter;
/**
* @see BeanPostProcessor#postProcessAfterInitialization(Object, String)
*/
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
/**
* @see BeanPostProcessor#postProcessBeforeInitialization(Object, String)
*/
@SuppressWarnings("unchecked")
public Object postProcessBeforeInitialization(final Object bean,
String beanName) throws BeansException {
Class clazz = bean.getClass();
for (Field field : clazz.getDeclaredFields()) {
/*
* Verifica se a classe possui a anotação requerida ou se segue
* algum padrão de nomenclatura. Deve ser também do tipo
* org.apache.log4j.Logger e estar no padrão do package (caso informado).
*/
if ((field.getAnnotation(Log.class) != null
|| field.getName().equalsIgnoreCase("logger")
|| field.getName().equalsIgnoreCase("log"))
&& field.getType().equals(Logger.class)
&& (getPackageFilter() == null || clazz.getPackage()
.getName().startsWith(getPackageFilter()))) {
field.setAccessible(true);
/* utiliza cache de log */
Logger logger = null;
if (cacheLoggers.containsKey(clazz)) {
logger = (Logger) cacheLoggers.get(clazz);
} else {
logger = Logger.getLogger(clazz);
cacheLoggers.put(clazz, logger);
}
/* acrescenta o log ao campo */
try {
field.set(bean, logger);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
return bean;
}
/**
* @return the packageFilter
*/
public String getPackageFilter() {
return packageFilter;
}
/**
* @param packageFilter the packageFilter to set
*/
public void setPackageFilter(String packageFilter) {
this.packageFilter = packageFilter;
}
}
Finalmente, definição do bean no arquivo de configuração do contexto da aplicaçao:
<!-- Log4j BeanPostProcessors --> <bean class="br.com.app.spring.logger.Log4jBeanPostProcessor" > <property name="packageFilter" value="br.com.app" /> </bean>
Note que podemos ou não passar um filtro de pacotes. Este filtro é interessante para que somente seja verificado classes dos nossos projetos, pois, o hook de BeanPostProcessor é acionado para cada classe que estiver sendo referenciada no contexto da aplicação do Spring.
Veja na prática como fica:
/**
* Classe de testes de injeção automática de logger.
*
* @author Erico Marineli
*
*/
@ContextConfiguration(locations = { "/applicationContextTest.xml" })
public class ResourcesTest extends AbstractJUnit4SpringContextTests {
/**
* Obtém o log através da anotação.
*/
@Log
private Logger logger;
/**
* Obtém o log através da CONVENÇÃO.
* Aqui, o atributo poderia se chamar logger também.
*/
private static Logger log;
@Test
public void testLogResources() {
Assert.assertNotNull(logger);
Assert.assertNotNull(log);
logger.debug("Logger ok");
log.debug("Log ok");
}
}
Tudo que fizemos é muito simples pois é feito dentro dos mecanismos do próprio Spring fornece. Diversas outras coisas podem ser feitas neste sentido, pois quanto mais transparente os mecanismo desta natureza para os desenvolvedores, melhor.