Problem
In order to register custom converters we have to do the following:
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<!-- list of converters-->
<bean class="org.anotes.springexample.converter.PersonToClient" />
<bean class="org.anotes.springexample.converter.ClientToPerson" />
...
</list>
</property>
</bean>
We want to register the converters using only some custom annotation.
Solution
We have to follow the next steps:
Create the annotation
This annotation will be used to mark the classes that are converters
package org.anotes.spring.stereotype;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface TypeConverter {
}
Create the custom BeanDefinitionRegistryPostProcessor
We need to do the following tasks: 1. Add programmatically the bean "conversionService" to the applicationContext 2. Add all the custom converters (identified by the annotation TypeConverter) to the bean "conversionService" define in the previous step.
To complete these tasks we need to create a custom BeanDefinitionRegistryPostProcessor
public class ConverterRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanPostProcessor, ApplicationContextAware {
private final String CONVERSION_SERVICE_NAME = "conversionService";
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.registerBeanDefinition(CONVERSION_SERVICE_NAME, BeanDefinitionBuilder.rootBeanDefinition(ConversionServiceFactoryBean.class).getBeanDefinition());
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (CONVERSION_SERVICE_NAME.equals(beanName)) {
Map<String, Converter> beansOfType = appCtx.getBeansOfType(Converter.class);
ConversionServiceFactoryBean conversionfactoryBean = (ConversionServiceFactoryBean) bean;
Set converters = new HashSet(beansOfType.values());
conversionfactoryBean.setConverters(converters);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
ApplicationContext appCtx;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appCtx = applicationContext;
}
}
In the method: "postProcessBeanDefinitionRegistry" the "conversionService" is added to the context.
In the method: "postProcessBeanFactory" we collect all the beans that have the annotation "TypeConverter" and then add all of these to the conversion service.
Take into account that as the converters are beans you can autowired other beans in these converters.
Register the PostProcessor
We have to include in the application context the post processor as bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:component-scan base-package="org.anotes.springexample"/>
<bean class="org.anotes.spring.postprocessor.ConverterRegistryPostProcessor"/>
</beans>
Test
We have the following converter:
@TypeConverter
public class ClientToPerson implements Converter<Client,Person> {
@Override
public Person convert(Client source) {
Person person = new Person();
BeanUtils.copyProperties(source,person);
return person;
}
}
We test that the converter function properly with :
public static void main(String[] args) {
String configFiles = "classpath*:/app-context.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configFiles);
Client client = createClient();
ConversionService conversionService = (ConversionService) context.getBean("conversionService");
Person person = conversionService.convert(client, Person.class);
logger.info("Client:{}", client );
logger.info("Person:{}", person );
}
And we get the following:
springexample.Main Client:Client{name='Joseph', gender='M', address='St Main Square', subscriptionDate=Thu Mar 14 20:37:29 COT 2013} [INFO ]
springexample.Main Person:Person{name='Joseph', gender='M', address='St Main Square'} [INFO ]
With the above we see that all function correctly.