Using Enums as Request Parameters in Spring

Springboot において Enum を Request Parameter に用いる方法

en: Using Enums as Request Parameters in Spring

テストした当時の環境は:

openjdk version "17.0.8.1" 2023-08-24
OpenJDK Runtime Environment (build 17.0.8.1+1-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 17.0.8.1+1-Ubuntu-0ubuntu122.04, mixed mode, sharing)
Springframework.boot が 'org.springframework.boot' version '3.1.5'

である。

大体の部分においては、Using Enums as Request Parameters in Spring に準じれば良いが、2点注意が必要であった。

1点目は、@Component を使った方が良さそうという点である。
2点目は、Enum が値をもつ場合の扱いについてである。

@Component について

Spring's @RequestParam with Enum の回答
https://stackoverflow.com/questions/39774427/springs-requestparam-with-enum/53266048#53266048
に、

If you are using Spring Boot, this is the reason that you should not use WebMvcConfigurationSupport.

というものがあった。WebMvcConfigurationSupport を用いる必要がないのであれば使わなくて良い…のだと解釈した。

@Configurationpublic class WebConfig implements WebMvcConfigurer であるクラスを用いることなく、もともとの Converter に対して @Component を加えるだけで十分動作した。

値をもつ Enum に対する工夫について

Enum が

public enum Modes {
    ALPHA, BETA;
}

ではなく

public enum Modes {
    HIGH("高"), MIDDLE("中"), LOW("低");

    public final String label;

    private Priority(String label) {
        this.label = label;
    }
}

のように値をもつ(正しい表現ではないかもしれない)場合において若干工夫が必要だったので書き残しておく。

元の例では:

public class StringToEnumConverter implements Converter<String, Modes> {
    @Override
    public Modes convert(String source) {
        return Modes.valueOf(source.toUpperCase());
    }
}

のようにしていた。

ここでは、Priority を以下のように定義する:

import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public enum Priority {
    HIGH("高"), MIDDLE("中"), LOW("低");

    public static final Map<String, Priority> PRIORITY_LABEL_MAP = Stream.of(Priority.values()).collect(Collectors.toMap(t -> t.label, t -> t));

    public final String label;

    private Priority(String label) {
        this.label = label;
    }

    public static Optional<Priority> findByLabel(String label) {
        return Optional.ofNullable(PRIORITY_LABEL_MAP.get(label));
    }
}

Priority は label を持つ。URL では、「高」や「中」という値を query string として受け取る。つまり、Controller は、「高」や「中」の形で URL に現れる Query Parameter を RequestParam として受け取りたいため、Converter はそれを考慮する必要がある。

例えば、以下のようにしてあげると良い。

import java.util.Optional;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import com.example.demo.enums.Priority;

@Component
public class LabelToPriorityConverter implements Converter<String, Optional<Priority>> {

    @Override
    public Optional<Priority> convert(String label) {
        return Priority.findByLabel(label);
    }
}

つまり、org.springframework.core.convert.converter.Converter を implement したクラスが、@org.springframework.stereotype.Component アノテーションされていれば良い。

Controller は以下のようになっていれば良い(一部抜粋)

@GetMapping
    public String getWithEnum(
            @RequestParam(value = "priority") Optional<Priority> priority) {
...
    }

動作するソースコードを
https://github.com/takotakot/spring-test/blob/enum_param
に置いておくことにした。

Priority.java
LabelToPriorityConverter.java
EnumController.java

サンプルのテストも EnumControllerTest.java に置いてある。

良くコード素片が動く形でおかれていないため、初心者には分からない…ということが多いので、動作するコード一式にしておいた。

備考

もう少し DRY (Don't Repeat Yourself) の原則に従って、複数の Enum についても変換して受け取るできる方法については、Spring MVCのRestControllerのRequestParamで任意のEnumをコードなどの別の値で受け取る方法が良さそうである。

参考

Java

Posted by tako