Статьи

Spring 4.1 и Java 8: java.util.Optional

Логотип-весна-ю
Начиная с Spring 4.1 Java 8 java.util.Optional , объект-контейнер, который может содержать или не содержать ненулевое значение, поддерживается с помощью @RequestParam , @RequestHeader и @MatrixVariable . При использовании Java 8 java.util.Optional вы убедитесь, что ваши параметры никогда не равны null .

Параметры запроса

В этом примере мы java.time.LocalDate как java.util.Optional используя @RequestParam :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@RestController
@RequestMapping("o")
public class SampleController {
 
    @RequestMapping(value = "r", produces = "text/plain")
    public String requestParamAsOptional(
            @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
            @RequestParam(value = "ld") Optional<LocalDate> localDate) {
 
        StringBuilder result = new StringBuilder("ld: ");
        localDate.ifPresent(value -> result.append(value.toString()));
        return result.toString();
    }
}

До весны 4.1 мы получали исключение, что не было найдено ни одного подходящего редактора или стратегии конвертации. С весны 4.1 это больше не проблема. Чтобы убедиться, что привязка работает правильно, мы можем создать простой интеграционный тест:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class SampleSomeControllerTest {
 
    @Autowired
    private WebApplicationContext wac;
    private MockMvc mockMvc;
 
    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
 
    // ...
 
}

В первом тесте мы проверим, правильно ли работает привязка и возвращается ли правильный результат:

1
2
3
4
5
@Test
public void bindsNonNullLocalDateAsRequestParam() throws Exception {
    mockMvc.perform(get("/o/r").param("ld", "2020-01-01"))
            .andExpect(content().string("ld: 2020-01-01"));
}

В следующем тесте мы не будем передавать параметр ld :

1
2
3
4
5
@Test
public void bindsNoLocalDateAsRequestParam() throws Exception {
    mockMvc.perform(get("/o/r"))
            .andExpect(content().string("ld: "));
}

Оба теста должны быть зелеными!

Заголовки запроса

Точно так же мы можем связать @RequestHeader с java.util.Optional :

1
2
3
4
5
6
7
8
9
@RequestMapping(value = "h", produces = "text/plain")
public String requestHeaderAsOptional(
        @RequestHeader(value = "Custom-Header") Optional<String> header) {
 
    StringBuilder result = new StringBuilder("Custom-Header: ");
    header.ifPresent(value -> result.append(value));
 
    return result.toString();
}

И тесты:

01
02
03
04
05
06
07
08
09
10
11
@Test
public void bindsNonNullCustomHeader() throws Exception {
    mockMvc.perform(get("/o/h").header("Custom-Header", "Value"))
            .andExpect(content().string("Custom-Header: Value"));
}
 
@Test
public void noCustomHeaderGiven() throws Exception {
    mockMvc.perform(get("/o/h").header("Custom-Header", ""))
            .andExpect(content().string("Custom-Header: "));
}

Матричные переменные

Представленная в Spring 3.2 аннотация @MatrixVariable указывает, что параметр метода должен быть связан с парой имя-значение в сегменте пути:

01
02
03
04
05
06
07
08
09
10
11
12
13
@RequestMapping(value = "m/{id}", produces = "text/plain")
public String execute(@PathVariable Integer id,
                      @MatrixVariable Optional<Integer> p,
                      @MatrixVariable Optional<Integer> q) {
 
    StringBuilder result = new StringBuilder();
    result.append("p: ");
    p.ifPresent(value -> result.append(value));
    result.append(", q: ");
    q.ifPresent(value -> result.append(value));
 
    return result.toString();
}

Вышеуказанный метод может быть вызван через /o/m/42;p=4;q=2 url. Давайте создадим тест для этого:

1
2
3
4
5
@Test
public void bindsNonNullMatrixVariables() throws Exception {
    mockMvc.perform(get("/o/m/42;p=4;q=2"))
            .andExpect(content().string("p: 4, q: 2"));
}

К сожалению, тест не @MatrixVariable , потому что поддержка аннотации @MatrixVariable по умолчанию отключена в Spring MVC . Чтобы включить его, нам нужно настроить конфигурацию и установить для свойства removeSemicolonContent RequestMappingHandlerMapping значение false . По умолчанию установлено значение true . Я сделал с WebMvcConfigurerAdapter как WebMvcConfigurerAdapter ниже:

1
2
3
4
5
6
7
8
9
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

И теперь все испытания проходят! Пожалуйста, найдите исходный код этой статьи здесь: https://github.com/kolorobot/spring41-samples