How to Autowired in ConversionService in springboot
Asked Answered
V

3

16

Trying to access the ConversionControl in model in springboot, no luck.

@Component
public class CityHelperService  {

    @Autowired
    ConversionService conversionService;// = ConversionServiceFactory.registerConverters();

    public City toEntity(CityDTO dto){
        City entity = conversionService.convert(dto, City.class);
        return entity;
    }

    public CityDTO toDTO(City entity){
        CityDTO dto = conversionService.convert(entity, CityDTO.class);
        return dto;
    }
}

It shows the following error:

Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.lumiin.mytalk.model.CityModel com.lumiin.mytalk.controllers.CityController.cityModel;
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'cityModel' defined in file : Unsatisfied dependency expressed through constructor argument with index 1 of type [com.lumiin.mytalk.dao.CityHelperService]: : Error creating bean with name 'cityHelperService': Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: org.springframework.core.convert.ConversionService com.lumiin.mytalk.dao.CityHelperService.conversionService;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.core.convert.ConversionService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)};
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cityHelperService': Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: org.springframework.core.convert.ConversionService com.lumiin.mytalk.dao.CityHelperService.conversionService;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.core.convert.ConversionService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Vaginectomy answered 4/5, 2015 at 20:44 Comment(0)
M
9

Apparently there is no ConversionService bean available, judging by the last nested exception:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.core.convert.ConversionService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.

A look into the Spring documentation reveals, that you should declare a ConversionService bean. In the XML configuration it would look like this:

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

And since you're using Spring Boot, I assume you are creating the context programatically, so you should create a method annotated with @Bean, which returns a ConverstionService, like this (explained here):

@Bean(name="conversionService")
public ConversionService getConversionService() {
    ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
    bean.setConverters(...); //add converters
    bean.afterPropertiesSet();
    return bean.getObject();
}
Mauney answered 4/5, 2015 at 21:0 Comment(4)
thanks for your comments..but what is bean.setConverters(...); it shows an error - org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type com.lumiin.mytalk.dto.CityDTO to type com.lumiin.mytalk.model.CityVaginectomy
You probably didn't specify a converter - you have to create a class, which implements the interface Converter<City, CityDTO>, and another one the other way around, then register both in the ConversionServiceFactoryBean. Please look at this tutorial: javabeat.net/introduction-to-spring-converters-and-formattersMauney
In the Javaconfig, You don't even need to return a ConverstionService from the ConversionServiceFactoryBean like the example above, just return a ConversionServiceFactoryBean bean and spring will do the rest for youEsau
I wouldn't recommend this: it causes the default ApplicationConversionService to be replaced on your application context which means you could lose some useful converters that it provides. (This happens in AbstractApplicationContext.finishBeanFactoryInitialization.) Rather add your converters to the conversion services on Environment and BeanFactory; I'll show an example that worked for me in an answer.Vespucci
M
4

Not totally agreed with the accepted answers, because there would be a default ConverstionService named mvcConversionService so you would get duplicate bean exception. Instead addConverter to FormatterRegistry, here is the link for the part answer:

Java Config equivalent for conversionService / FormattingConversionServiceFactoryBean

Also you would need (in some cases) to define to at least an empty Component for ConversionService, something like below:

@Component @Primary
public class MyConversionService extends DefaultConversionService implements ConversionService {
    // an empty ConversionService to initiate call to register converters
}

This is to force spring container to initiate a call to:

class WebMvcConfigurerAdapter {
    ...

    public void addFormatters(FormatterRegistry registry) {
         //registry.addConverter(...);
    }
}
Manicotti answered 17/11, 2017 at 8:26 Comment(1)
the mvcConversionService will only be present if you have org.springframework.boot:spring-boot-starter-web dependency in your projectBullroarer
V
0

Existing answers didn't work for me:

  • Customizing via WebMvcConfigurerAdapter.addFormatters (or simply annotating the converter with @Component) only works in the WebMvc context and I want my custom converter to be available everywhere, including @Value injections on any bean.
  • Defining a ConversionService bean (via ConversionServiceFactoryBean @Bean or @Component) causes Spring Boot to replace the default ApplicationConversionService on the SpringApplication bean factory with the custom bean you've defined, which will probably be based on DefaultConversionService (in AbstractApplicationContext.finishBeanFactoryInitialization). The problem is that Spring Boot adds some handy converters such as StringToDurationConverter to the standard set in DefaultConversionService, so by replacing it you lose those conversions. This may not be an issue for you if you don't use them, but it means that solution won't work for everyone.

I created the following @Configuration class which did the trick for me. It basically adds custom converters to the ConversionService instance used by Environment (which is then passed on to BeanFactory). This maintains as much backwards compatibility as possible while still adding your custom converter into the conversion services in use.

@Configuration
public class ConversionServiceConfiguration {

    @Autowired
    private ConfigurableEnvironment environment;

    @PostConstruct
    public void addCustomConverters() {
        ConfigurableConversionService conversionService = environment.getConversionService();
        conversionService.addConverter(new MyCustomConverter());
    }
}

Obviously you can autowire a list of custom converters into this configuration class and loop over them to add them to the conversion service instead of the hard-coded way of doing it above, if you want the process to be more automatic.

To make sure this configuration class gets run before any beans are instantiated that might require the converter to have been added to the ConversionService, add it as a primary source in your spring application's run() call:

@SpringBootApplication
public class MySpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(new Class<?>[] { MySpringBootApplication.class, ConversionServiceConfiguration.class }, args);
    }
}

If you don't do this, it might work, or not, depending on the order in which your classes end up in the Spring Boot JAR, which determines the order in which they are scanned. (I found this out the hard way: it worked when compiling locally with an Oracle JDK, but not on our CI server which was using a Azul Zulu JDK.)

Note that for this to work in @WebMvcTests, I had to also combine this configuration class along with my Spring Boot application class into a @ContextConfiguration:

@WebMvcTest(controllers = MyController.class)
@ContextConfiguration(classes = { MySpringBootApplication.class, ConversionServiceConfiguration.class })
@TestPropertySource(properties = { /* ... properties to inject into beans, possibly using your custom converter ... */ })
class MyControllerTest {
   // ...
}
Vespucci answered 28/6, 2022 at 6:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.