自定义的转换器在Spring Boot Webflux中未被调用。

huangapple go评论142阅读模式
英文:

Custom Converter with Spring Boot Webflux is not called

问题

使用版本为2.3.3的spring-boot-starter-webflux和Java 14,我正在尝试注册一个自定义转换器,将String输入转换为枚举。我遇到的问题是,在执行过程中,底层的Jackson库尝试将String转换为枚举,而不考虑我注册的自定义转换器。

以下是我使用的控制器示例:

@RestController
public class MyController {

    private final MyService myService;

    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }

    @PostMapping(value = "/subscriptions")
    public CreateSubscriptionOutput createSubscription(@RequestBody @Valid CreateSubscriptionInput input) throws Exception {
        return myService.createSubscription(input);
    }
}

输入定义如下:

public class CreateSubscriptionInput {

    private EventType eventType;

    public CreateSubscriptionInput() {
        this.eventType = EventType.A;
    }

    public CreateSubscriptionInput(EventType type) {
        this.eventType = type;
    }

    public EventType getEventType() {
        return this.eventType;
    }
}

public enum EventType implements SafeEnum {

    A(0L, "a"),
    B(1L, "b"),
   
    // 枚举定义...
}

public interface SafeEnum {

    long getId();

    String getName();
}

public final class Enums {

    public static <E extends Enum<E> & SafeEnum> E from(Class<E> clazz, long id) {
        // 枚举转换方法...
    }

    public static <E extends Enum<E> & SafeEnum> E from(Class<E> clazz, String name) {
        // 枚举转换方法...
    }
}

自定义转换器的定义和注册如下:

@Configuration
@EnableWebFlux
public class ApplicationConfiguration implements WebFluxConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new Converter<String, EventType>() {
            @Override
            public EventType convert(String source) {
                return EventType.from(source);
            }
        });
    }
}

目的是将"a"转换为EventType.A

当我调用以下方式的REST资源时:

curl --location --request POST 'http://localhost:8081/subscriptions' \
     --header 'Content-Type: application/json' \
     --data-raw '{
         "eventType": "a"
     }'

我得到一个400错误,错误信息如下:

JSON decoding error: Cannot deserialize value of type
my.app.EventType from String "a": not one of the values accepted for
Enum class: [A, B]

这让我认为转换器从未被调用。在调试中运行代码确认了这个假设。将根记录器设置为DEBUG似乎不会打印关于已注册转换器的信息。

我注册转换器的方式有问题吗?缺少了什么吗?

英文:

Using spring-boot-starter-webflux in version 2.3.3 with Java 14, I am trying to register a custom converter to convert a String input to an enum. The problem I have is that upon execution the underlying Jackson library tries to convert the String without considering the custom converter I have registered.

Here is the controller I use:

@RestController
public class MyController {

    private final MyService myService;


    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }

    @PostMapping(value = &quot;/subscriptions&quot;)
    public CreateSubscriptionOutput createSubscription(@RequestBody @Valid CreateSubscriptionInput input) throws Exception {
        return myService.createSubscription(input);
    }

}

The input definition is as follows:

public class CreateSubscriptionInput {

    private EventType eventType;

    public CreateSubscriptionInput() {
        this.eventType = EventType.A;
    }

    public CreateSubscriptionInput(EventType type) {
        this.eventType = type;
    }

    public EventType getEventType() {
        return this.eventType;
    }

}

public enum EventType implements SafeEnum {

    A(0L, &quot;a&quot;),
    B(1L, &quot;b&quot;),
   
    private final long id;
    private final String name;

    public static EventType from(long id) {
        return (EventType) Enums.from(EventType.class, id);
    }

    public static EventType from(String name) {
        return (EventType) Enums.from(EventType.class, name);
    }

    private EventType(long id, String name) {
        this.id = id;
        this.name = name;
    }

    public long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

}

public interface SafeEnum {

    long getId();

    String getName();

}

public final class Enums {

    public static &lt;E extends Enum&lt;E&gt; &amp; SafeEnum&gt; E from(Class&lt;E&gt; clazz, long id) {
        for (E e : EnumSet.allOf(clazz)) {
            if (e.getId() == id) {
                return e;
            }
        }

        throw new IllegalArgumentException(&quot;Unknown &quot; + clazz.getSimpleName() + &quot; id: &quot; + id);
    }

    public static &lt;E extends Enum&lt;E&gt; &amp; SafeEnum&gt; E from(Class&lt;E&gt; clazz, String name) {
        if (name == null) {
            return null;
        }

        for (E e : EnumSet.allOf(clazz)) {
            if (e.getName().equals(name)) {
                return e;
            }
        }

        throw new IllegalArgumentException(&quot;Unknown &quot; + clazz.getSimpleName() + &quot; name: &quot; + name);
    }

}

The custom converter is defined and registered as follows:

@Configuration
@EnableWebFlux
public class ApplicationConfiguration implements WebFluxConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new Converter&lt;String, EventType&gt;() {
            @Override
            public EventType convert(String source) {
                return EventType.from(source);
            }
        });
    }

The purpose is to convert "a" to EventType.A.

When I invoke the REST resource as follows:

curl --location --request POST &#39;http://localhost:8081/subscriptions&#39; \
--header &#39;Content-Type: application/json&#39; \
--data-raw &#39;{
&quot;eventType&quot;: &quot;a&quot;,
}&#39;

I get a 400 error with the following internal error:

> JSON decoding error: Cannot deserialize value of type
> my.app.EventType from String "a": not one of the values accepted for
> Enum class: [A, B]

This lets me think the converter is never called. Running the code in debug confirms this assumption. Setting the root logger in DEBUG seems not to print information about registered converters.

Is there something wrong with how I register the converter? what is missing?

答案1

得分: 3

以下是您要翻译的内容:

尚不清楚在带有 @RequestBody 注解的 DTO 中反序列化值时为何不使用已注册的转换器。官方文档似乎未描述这种情况。希望的解决方法是使用 @JsonCreator 注解:

public enum EventType implements SafeEnum {

    A(0L, "a"),
    B(1L, "b"),
   
    private final long id;
    private final String name;

    public static EventType from(long id) {
        return (EventType) Enums.from(EventType.class, id);
    }

    @JsonCreator
    public static EventType from(String name) {
        return (EventType) Enums.from(EventType.class, name);
    }

    private EventType(long id, String name) {
        this.id = id;
        this.name = name;
    }

    public long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

}

虽然这种方法不够灵活,而且需要为所有枚举重复操作,但至少它能按预期工作。

英文:

It is unclear why registered converters are not used when deserializing values in a DTO annotated with @RequestBody. The official documentation seems not to describe this case. Hopefully, a workaround is to use @JsonCreator annotation:

public enum EventType implements SafeEnum {

    A(0L, &quot;a&quot;),
    B(1L, &quot;b&quot;),
   
    private final long id;
    private final String name;

    public static EventType from(long id) {
        return (EventType) Enums.from(EventType.class, id);
    }

    @JsonCreator
    public static EventType from(String name) {
        return (EventType) Enums.from(EventType.class, name);
    }

    private EventType(long id, String name) {
        this.id = id;
        this.name = name;
    }

    public long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

}

This is less flexible and requires repetition for all enums but at least it has the merit to work as expected.

答案2

得分: 0

以下是翻译的内容:

只需将Converter用作@Component,无需WebFluxConfigurer:

@Component
public class CustomConverter implements Converter<String, EventType> {
    @Override
    public EventType convert(String source) {
        return EventType.from(source);
    }
}
英文:

Just use Converter as @Component without WebFluxConfigurer:

@Component
public class CustomConverter implements Converter&lt;String, EventType&gt; {
@Override
public EventType convert(String source) {
return EventType.from(source);
}
}

huangapple
  • 本文由 发表于 2020年8月21日 04:14:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/63512542.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定