Liu, Yinwei David opened SPR-6415 and commented

Hi, Currently, we can only register a CustomConverter by configuring it in Spring configuration file in Spring 3.0.0.RC2. However, we want the ability which allows all Converters to register themselves into the global ConversionService by default.

In Spring 3.0.RC2, we can use ConversionServiceFactoryBean to create a ConversionService in Spring config. If people have a lot of Converter in the infrastructure libraries, they need to register all customized converters by themself, so we think it would be good if Converter itself can register itself.


Affects: 3.0 RC1, 3.0 RC2

4 votes, 8 watchers

Comment From: spring-projects-issues

Keith Donald commented

Related to this: some converters require a callback into the ConversionService. Some sort of conversion service aware concept would simplify the config of such converters.

Comment From: spring-projects-issues

Lance Arlaus commented

I ran into both these issues on my current project where I'm using the conversion service. Here's the solution I came up with.

  1. Converter Registration - Created a bean post processor that registers any converter or converter factory found in the context with the global conversion service
  2. Conversion Service Initialization - Created another bean post processor that retrieves all converters and converter factories to force initialization (and registration via the previous bean post processor)
  3. Conversion Service Injection - Autowired the conversion service into converters that need it. No need for a separate aware interface (though the first draft design had one)

Notes * This solution can likely be improved, but it's in the spirit of preserving a separation of concerns. The first version of this design had the conversion service retrieving converters from the app context - probably not the responsibility of a conversion service. * All appropriate converters need to be registered with the conversion service before the service can be considered initialized, hence the conversion service bean post processor * A registration callback on the converter would come in handy, especially since the conversion service is currently quite sensitive to how converters are registered (I find myself registering converters with various combinations of source and target types to get things to work since the reflection logic used by the conversion service isn't quite baked yet) * Why doesn't the ConverterRegistry interface support the extended converter registration methods?

Code

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        final GenericConversionService conversionService = (GenericConversionService) ((AbstractApplicationContext)getApplicationContext()).getBeanFactory().getConversionService();

        if (bean instanceof ConverterFactory<?,?>) {
            log.debug("adding converter factory {} to conversion service", beanName);
            conversionService.addConverterFactory((ConverterFactory<?, ?>) bean);
        } else if (bean instanceof Converter<?,?>) {
            log.debug("adding converter {} to conversion service", beanName);
            conversionService.addConverter((Converter<?, ?>) bean);
        }
        return bean;
    }

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
          if (bean instanceof ConversionService) {
          log.info("conversion service detected - retrieving all converters and converter factories to force initialization");
                    final Map<String, Converter> converters = getApplicationContext().getBeansOfType(Converter.class);
          if (log.isTraceEnabled()) {
               log.trace("found {} converters: {}", converters.size(), converters.keySet());
          }
                    final Map<String, ConverterFactory> converterFactories = getApplicationContext().getBeansOfType(ConverterFactory.class);
          if (log.isTraceEnabled()) {
               log.trace("found {} converter factories: {}", converterFactories.size(), converterFactories.keySet());
          }
               }
          return bean;
}

Comment From: spring-projects-issues

Mark Kralj-Taylor commented

Could a JAR META-INF entry be used to identify converters?

This would allow infrastructure converters to be picked up in a zero touch way from Jars on the classpath.

Spring could even use the same mechanism for its default converters. Or if auto registration from JAR META-INF scanning is seen as 'too-much magic', then it could be turned on by a tag in config, in the same way that component scanning is.

Comment From: spring-projects-issues

Liu, Yinwei David commented

Hi, We implement our Converter auto-registration by scanning JAR META-INF/services/spring.convert.Converters.
Auto Register Converters: All Converters will be registered to ConversionService by placing a text file at META-INF/services/spring.convert.Converters containing a list of fully qualified class name of each Converter on its own line. The CustomizedConversionServiceFactoryBean will return a ConversionService which contains all Converters registered in this way.

META-INF/services/spring.convert.Converters sample file: spring.convert.TestConverterA spring.convert.TestConverterB

Is it possible that Spring can provide a similar solution for Converters auto registration?

Comment From: spring-projects-issues

André Wolf commented

I found a very simple solution which seems to work fine for me. I just created a common super class AutoRegisteredConverter which registers itself in a @PostConstruct method. The ConversionService gets injected by Spring.

public abstract class AutoRegisteredConverter<S, T> implements Converter<S, T> {

    private ConversionService conversionService;

    public ConversionService getConversionService() {
        return conversionService;
    }

    @Inject
    public void setConversionService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    @SuppressWarnings("unused") // the IoC container will call @PostConstruct methods
    @PostConstruct
    private void register() {
        if (conversionService instanceof GenericConversionService) {
            ((GenericConversionService) conversionService).addConverter(this);
        }
    }
}

For me it's a preferable solution, because it does not need a BeanPostProcessor (which are problematic for me in Unit Tests when using SpringJUnitClassRunner). Another advantage is, that each custom Converter has access to the ConversionService which is perfect for converting transitive object references.

Comment From: spring-projects-issues

Kosta Krauth commented

A variation on Andre's approach, registering all the converters once the context has been loaded does the trick too. Didn't encounter any issues with the SpringJUnitClassRunner. With this approach, the Converters can be implemented in the standard way without any additional classes. \ \

@Component
public class SpringContextListener implements ApplicationListener<ContextRefreshedEvent> {
  @Autowired private Set<Converter<?, ?>> converters;
  @Autowired private ConversionService conversionService;

  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    GenericConversionService gcs = (GenericConversionService) conversionService;
    for(Converter<?, ?> converter : converters) {
      gcs.addConverter(converter);
    }
  }
}

The approach of adding a component with the FactoryBean\ interface that registers all Converters on itself works as well, as long as you don't autowire the ConversionService into Converters themselves. That scenario quickly descends into circular dependency hell.

Comment From: bclozel

This approach requires more fine-grained knowledge of the application. I think this has been covered by Spring Boot for configuration properties and web data binding.

This looks like a general auto-configuration feature rather than a Spring Framework concern. I'm closing this issue as a result.