Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converter auto-register itself to ConversionService [SPR-6415] #11081

Closed
spring-projects-issues opened this issue Nov 23, 2009 · 7 comments
Closed
Labels
status: declined A suggestion or change that we don't feel we should currently apply

Comments

@spring-projects-issues
Copy link
Collaborator

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

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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;
}

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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?

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

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<ConversionService> 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.

@spring-projects-issues spring-projects-issues added type: enhancement A general enhancement in: core Issues in core modules (aop, beans, core, context, expression) labels Jan 11, 2019
@bclozel
Copy link
Member

bclozel commented Sep 20, 2021

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.

@bclozel bclozel closed this as completed Sep 20, 2021
@bclozel bclozel added status: declined A suggestion or change that we don't feel we should currently apply and removed in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement labels Sep 20, 2021
@bclozel bclozel removed this from the General Backlog milestone Sep 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

2 participants