在Spring Boot中以编程方式注册Spring Converter

23

我希望以编程方式在Spring Boot项目中注册一个Spring Converter。 在过去的Spring项目中,我像这样在XML中完成了它...


<!-- Custom converters to allow automatic binding from Http requests parameters to objects -->
<!-- All converters are annotated w/@Component -->
<bean id="conversionService"
      class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <ref bean="stringToAssessmentConverter" />
        </list>
    </property>
</bean>

我正在尝试弄清楚如何在Spring Boot的SpringBootServletInitializer中进行操作。

更新: 通过将StringToAssessmentConverter作为参数传递给getConversionService,我已经取得了一些进展,但现在我正在获得一个"No default constructor found"错误,指向StringToAssessmentConverter类中的@Autowired构造函数。我不确定为什么Spring没有看到它。

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    ...

    @Bean(name="conversionService")
    public ConversionServiceFactoryBean getConversionService(StringToAssessmentConverter stringToAssessmentConverter) {
        ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();

        Set<Converter> converters = new HashSet<>();

        converters.add(stringToAssessmentConverter);

        bean.setConverters(converters);
        return bean;
    }
}  

这是Converter的代码...

 @Component
 public class StringToAssessmentConverter implements Converter<String, Assessment> {

     private AssessmentService assessmentService;

     @Autowired
     public StringToAssessmentConverter(AssessmentService assessmentService) {
         this.assessmentService = assessmentService;
     }

     public Assessment convert(String source) {
         Long id = Long.valueOf(source);
         try {
             return assessmentService.find(id);
         } catch (SecurityException ex) {
             return null;
         }
     }
 }

完整错误信息

Failed to execute goal org.springframework.boot:spring-boot-maven-
plugin:1.3.2.RELEASE:run (default-cli) on project yrdstick: An exception 
occurred while running. null: InvocationTargetException: Error creating 
bean with name
'org.springframework.boot.context.properties.ConfigurationPropertiesBindingPo
stProcessor': Invocation of init method failed; nested exception is 
org.springframework.beans.factory.UnsatisfiedDependencyException: Error 
creating bean with name 'conversionService' defined in 
me.jpolete.yrdstick.Application: Unsatisfied dependency expressed through 
constructor argument with index 0 of type 
[me.jpolete.yrdstick.websupport.StringToAssessmentConverter]: : Error 
creating bean with name 'stringToAssessmentConverter' defined in file 
[/yrdstick/target/classes/me/jpolete/yrdstick/websupport
/StringToAssessmentConverter.class]: Instantiation of bean failed; nested 
exception is org.springframework.beans.BeanInstantiationException: Failed 
to instantiate 
[me.jpolete.yrdstick.websupport.StringToAssessmentConverter]: No default 
constructor found; nested exception is java.lang.NoSuchMethodException: 
me.jpolete.yrdstick.websupport.StringToAssessmentConverter.<init>(); 
nested exception is 
org.springframework.beans.factory.BeanCreationException: Error creating 
bean with name 'stringToAssessmentConverter' defined in file [/yrdstick
/dev/yrdstick/target/classes/me/jpolete/yrdstick/websupport
/StringToAssessmentConverter.class]: Instantiation of bean failed; nested 
exception is org.springframework.beans.BeanInstantiationException: Failed 
to instantiate 
[me.jpolete.yrdstick.websupport.StringToAssessmentConverter]: No default 
constructor found; nested exception is java.lang.NoSuchMethodException: 
me.jpolete.yrdstick.websupport.StringToAssessmentConverter.<init>()

尝试像这样:converters.add(new StringToAssessmentConverter()); - Shaheer
1
@Shaheer 我无法使用new实例化它。StringToAssessmentConverter有它自己的Autowired依赖项。 - jpolete
7个回答

63
答案是,你只需要将你的转换器标记为@Component
这是我的转换器示例。
import org.springframework.core.convert.converter.Converter;
@Component
public class DateUtilToDateSQLConverter implements Converter<java.util.Date, Date> {

    @Override
    public Date convert(java.util.Date source) {
        return new Date(source.getTime());
    }
}

当Spring需要进行类型转换时,会调用相应的转换器。

我的Spring Boot版本:1.4.1


4
谢谢你提到这一点。不幸的是,在Spring文档中没有记录这一点。 - eav
@eav true,这是一些有用且简单易用的东西,需要进行文档化。 - deFreitas
4
自动配置在Spring的默认设置上添加了以下功能:自动注册转换器(Converter)、泛型转换器(GenericConverter)和格式化器(Formatter)Bean。 - cdalxndr
1
当我创建自己的WebMvcConfiguration(替换@EnableMvc)时,似乎失去了这个自动配置。对于那些遇到这个问题的人来说,这可能是有用的信息。 - Frans
3
如果需要将属性转换为特定对象,则将其注册为组件可能太晚了。我正在使用Spring-boot-2.3.11,并且遇到错误“找不到能够将类型[java.lang.String]转换为类型[org.raisercostin.jedio.WritableDirLocation]的转换器”。 - raisercostin
显示剩余3条评论

5
如果您不使用Spring Boot,并且没有在Web Mvc环境中执行带有@Component(和类似的构造型注释)的转换器的自动注册:
@Bean
ConversionService conversionService(){
    ConversionServiceFactoryBean factory = new ConversionServiceFactoryBean();
    Set<Converter<?, ?>> convSet = new HashSet<Converter<?, ?>>();
    convSet.add(new MyConverter()); // or reference bean convSet.add(myConverter());
    factory.setConverters(convSet);
    factory.afterPropertiesSet();
    return factory.getObject();
}

3
这里是我的解决方案:
一个TypeConverter注解:
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface TypeConverter {
}

转换器注册表:

@Configuration
public class ConverterConfiguration {

    @Autowired(required = false)
    @TypeConverter
    private Set<Converter<?, ?>> autoRegisteredConverters;

    @Autowired(required = false)
    @TypeConverter
    private Set<ConverterFactory<?, ?>> autoRegisteredConverterFactories;

    @Autowired
    private ConverterRegistry converterRegistry;

    @PostConstruct
    public void conversionService() {
        if (autoRegisteredConverters != null) {
            for (Converter<?, ?> converter : autoRegisteredConverters) {
                converterRegistry.addConverter(converter);
            }
        }
        if (autoRegisteredConverterFactories != null) {
            for (ConverterFactory<?, ?> converterFactory : autoRegisteredConverterFactories) {
                converterRegistry.addConverterFactory(converterFactory);
            }
        }
    }

}

然后对您的转换器进行注释:

@SuppressWarnings("rawtypes")
@TypeConverter
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

    @SuppressWarnings("unchecked")
    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnum(targetType);
    }

    private final class StringToEnum<T extends Enum> implements Converter<String, T> {

        private Class<T> enumType;

        public StringToEnum(Class<T> enumType) {
            this.enumType = enumType;
        }

        @SuppressWarnings("unchecked")
        public T convert(String source) {
            return (T) Enum.valueOf(this.enumType, source.trim().toUpperCase());
        }
    }
}

看起来很优雅,但是当我尝试的时候,出现了错误,可能是Spring版本的差异导致的?org.springframework.beans.factory.BeanCreationException: 在类路径资源[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]中定义的名为“entityManagerFactory”的bean创建异常:初始化方法调用失败;嵌套异常是java.lang.IllegalStateException:无法实例化AttributeConverter [void]。 - Espresso
从HibernateJpaAutoConfiguration来看,您可能正在使用Spring Boot。据我所知,Spring Boot已经支持开箱即用的自动注册转换器:https://dev59.com/YFsW5IYBdhLWcg3wZWjj 这个解决方案适用于裸Spring,而不是Spring Boot。 - narduk

0

试试这个:

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    @Bean
    public AssessmentService assessmentService(){
        return new AssessmentService();
    }

    @Bean
    public StringToAssessmentConverter stringToAssessmentConverter(){
        return new StringToAssessmentConverter(assessmentService());
    }

    @Bean(name="conversionService")
    public ConversionService getConversionService() {
        ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();    
        Set<Converter> converters = new HashSet<Converter>();

        //add the converter
        converters.add(stringToAssessmentConverter()); 

        bean.setConverters(converters);
        return bean.getObject();
    }

    // separate these class into its own java file if necessary
    // Assesment service
    class AssessmentService {}

    //converter
    class StringToAssessmentConverter implements Converter<String, Assessment> {

         private AssessmentService assessmentService;

         @Autowired
         public StringToAssessmentConverter(AssessmentService assessmentService) {
             this.assessmentService = assessmentService;
         }

         public Assessment convert(String source) {
             Long id = Long.valueOf(source);
             try {
                 return assessmentService.find(id);
             } catch (SecurityException ex) {
                 return null;
             }
         }

     }
}

或者如果您的StringToAssessmentConverter已经是一个Spring Bean:

@Autowired
@Bean(name="conversionService")
public ConversionService getConversionService(StringToAssessmentConverter stringToAssessmentConverter) {
    ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();    
    Set<Converter> converters = new HashSet<Converter>();

    //add the converter
    converters.add(stringToAssessmentConverter); 

    bean.setConverters(converters);
    return bean.getObject();
}

你正在调用stringToAssessmentConverter()方法,它在哪里? - jpolete
没关系,我明白了。那可能行得通,但我想利用Spring的组件和自动装配注解来代替手动连接对象。 - jpolete
请查看我的更新答案。基本上,将类注释为@Component与将方法注释为@Bean是相同的。 - KSTN
谢谢。请看我对原问题的更新。我正在按照您的建议进行操作。StringToAssessmentConverter是一个带有@Component注释的Spring Bean,但现在我遇到了一个问题:Failed to instantiate [me.jpolete.yrdstick.websupport.StringToAssessmentConverter]: No default constructor found; 我已经使用@Autowired注释标记了StringToAssessmentConverter构造函数。不确定为什么该构造函数没有被调用。 - jpolete

0
如果需要将属性转换为特定对象,则将其注册为组件可能太晚了,需要使用转换器。我正在使用spring-boot-2.3.11,并且遇到错误No converter found capable of converting from type [java.lang.String] to type [org.raisercostin.jedio.WritableDirLocation]
***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under 'revobet.feed.lsports.rest.dump-dir' to org.raisercostin.jedio.WritableDirLocation:

    Property: myapp.dump-dir
    Value: file://localhost/C:\Users\raiser/.myapp/cache
    Origin: class path resource [myapp.conf]:-1:1
    Reason: No converter found capable of converting from type [java.lang.String] to type [org.raisercostin.jedio.WritableDirLocation]

Action:

Update your application's configuration

解决方案

public class MyApp implements ApplicationRunner {

  public static void main(String[] args) {
    LocationsConverterConfig.init();
    SpringApplication.run(MyApp.class, args);
  }
}

和转换器

public class LocationsConverterConfig {
  public static void init() {
    ApplicationConversionService conversionService = (ApplicationConversionService) ApplicationConversionService
      .getSharedInstance();
    log.info("adding JacksonStringToObjectConverter to handle Locations serialization as soon as possible");
    conversionService.addConverter(new JacksonStringToObjectConverter());
  }

  //@Component
  public static class JacksonStringToObjectConverter implements ConditionalGenericConverter {

    public JacksonStringToObjectConverter() {
      log.info("adding {} to handle Locations serialization as soon as possible", JacksonStringToObjectConverter.class);
    }

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
      return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
    }

    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
      return true;
    }

    @Override
    @Nullable
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
      ...
    }
  }
}

0

对于Spring Boot,它看起来像是:

public class MvcConfiguration implements WebMvcConfigurer {

  @Override
  public void addFormatters(FormatterRegistry registry) {
    // do not replace with lambda as spring cannot determine source type <S> and target type <T>
    registry.addConverter(new Converter<String, Integer>() {
        @Override
        public Integer convert(String text) {
            if (text == null) {
                return null;
            }
            String trimmed = StringUtils.trimWhitespace(text);
            return trimmed.equals("null") ? null : Integer.valueOf(trimmed);
        }
    });
  }

0

在XML配置中注册自定义转换器时遇到了问题。需要将转换器ID添加到注释驱动程序中。

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="ru.javawebinar.topjava.util.StringToLocalDateConverter"/>
            </set>
        </property>
</bean>

<mvc:annotation-driven conversion-service="conversionService"/>

参考链接: Spring MVC。类型转换, Spring Core。类型转换

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接