Spring Boot отлично подходит для разработки веб-приложений. Вы можете создать автономный HTTP-сервер, используя встроенный Tomcat, Jetty, Undertow или Netty. Большинство веб-приложений используют модуль spring-boot-starter-web для быстрого выполнения. Вы также можете компилировать реактивные веб-приложения с помощью модуля spring-boot-starter-webflux.

Если вы хотите создавать веб-приложения на основе сервлетов, то можете воспользоваться возможностями автоконфигурации Spring Boot для Spring MVC или Jersey.

Фреймворк "Spring Web MVC"

Фреймворк Spring Web MVC (часто упоминается как "Spring MVC") – это полноценный веб-фреймворк по схеме "модель-представление-контроллер". Spring MVC позволяет создавать специальные бины с аннотациями @Controller или @RestController для обработки входящих HTTP-запросов. Методы в контроллере отображаются на протокол HTTP с помощью аннотаций @RequestMapping.

В следующем коде показана типичная аннотация @RestController, которая работает с данными в формате JSON:

Java
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class MyRestController {
    private final UserRepository userRepository;
    private final CustomerRepository customerRepository;
    public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
        this.userRepository = userRepository;
        this.customerRepository = customerRepository;
    }
    @GetMapping("/{userId}")
    public User getUser(@PathVariable Long userId) {
        return this.userRepository.findById(userId).get();
    }
    @GetMapping("/{userId}/customers")
    public List<Customer> getUserCustomers(@PathVariable Long userId) {
        return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
    }
    @DeleteMapping("/{userId}")
    public void deleteUser(@PathVariable Long userId) {
        this.userRepository.deleteById(userId);
    }
}
Kotlin
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
    @GetMapping("/{userId}")
    fun getUser(@PathVariable userId: Long): User {
        return userRepository.findById(userId).get()
    }
    @GetMapping("/{userId}/customers")
    fun getUserCustomers(@PathVariable userId: Long): List<Customer> {
        return userRepository.findById(userId).map(customerRepository::findByUser).get()
    }
    @DeleteMapping("/{userId}")
    fun deleteUser(@PathVariable userId: Long) {
        userRepository.deleteById(userId)
    }
}

"WebMvc.fn", функциональный вариант, отделяет конфигурацию маршрутизации от фактической обработки запросов, как показано в следующем примере:

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
    @Bean
    public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }
}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.servlet.function.RequestPredicates.accept
import org.springframework.web.servlet.function.RouterFunction
import org.springframework.web.servlet.function.RouterFunctions
import org.springframework.web.servlet.function.ServerResponse
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
    @Bean
    fun routerFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
        return RouterFunctions.route()
            .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
            .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
            .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
            .build()
    }
    companion object {
        private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
    }
}
Java
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
@Component
public class MyUserHandler {
    public ServerResponse getUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }
    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }
    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }
}
Kotlin
import org.springframework.stereotype.Component
import org.springframework.web.servlet.function.ServerRequest
import org.springframework.web.servlet.function.ServerResponse
@Component
class MyUserHandler {
    fun getUser(request: ServerRequest?): ServerResponse {
        return ServerResponse.ok().build()
    }
    fun getUserCustomers(request: ServerRequest?): ServerResponse {
        return ServerResponse.ok().build()
    }
    fun deleteUser(request: ServerRequest?): ServerResponse {
        return ServerResponse.ok().build()
    }
}
Чтобы модулировать определение маршрутизатора, можно определять столько бинов RouterFunction, сколько пожелаете. Бины можно упорядочить, если требуется применять очередность выполнения.

Автоконфигурация Spring MVC

Spring Boot предусматривает автоконфигурацию для Spring MVC, которая отлично работает с большинством приложений.

Автоконфигурация добавляет следующие функции вдобавок к настройкам Spring по умолчанию:

  • Добавление бинов ContentNegotiatingViewResolver и BeanNameViewResolver.

  • Поддержка обработки статических ресурсов, включая поддержку WebJars.

  • Автоматическая регистрация бинов Converter, GenericConverter и Formatter.

  • Поддержка HttpMessageConverters.

  • Автоматическая регистрация MessageCodesResolver.

  • Поддержка статического index.html.

  • Автоматическое использование бина ConfigurableWebBindingInitializer.

Если необходимо сохранить эти настройки Spring Boot MVC и внести дополнительные настройки MVC (перехватчики, форматировщики, контроллеры представления и другие функции), то можете добавить свой собственный класс, помеченный аннотацией @Configuration, типа WebMvcConfigurer, но без аннотации @EnableWebMvc.

Если необходимо передать кастомные экземпляры RequestMappingHandlerMapping, RequestMappingHandlerAdapter или ExceptionHandlerExceptionResolver и при этом сохранить настройки Spring Boot MVC, то можно объявить бин типа WebMvcRegistrations и использовать его для передачи кастомных экземпляров этих компонентов.

Если необходимо полностью контролировать работу Spring MVC, то можно добавить свою собственную @Configuration, помеченную аннотацией @EnableWebMvc, или же добавить свою собственный класс DelegatingWebMvcConfiguration, помеченный аннотацией @Configuration, как описано в Javadoc для аннотации @EnableWebMvc.

Spring MVC использует ConversionService, отличный от того, который используется для преобразования значений из вашего файла application.properties или application.yaml. Это значит, что преобразователи Period, Duration и DataSize будут недоступны и что аннотации @DurationUnit и @DataSizeUnit будут проигнорированы.

Если необходимо персонализировать настройки службы ConversionService, используемой Spring MVC, то можно предусмотреть бин WebMvcConfigurer с методом addFormatters. Из этого метода можно регистрировать любой предпочтительный преобразователь или делегировать полномочия статическим методам, доступным для ApplicationConversionService.

HttpMessageConverters

Spring MVC использует интерфейс HttpMessageConverter для преобразования HTTP-запросов и ответов. Адекватные настройки по умолчанию предусмотрены "из коробки". Например, объекты могут быть автоматически преобразованы в JSON (с помощью библиотеки Jackson) или в XML (с помощью расширения Jackson XML, если оно доступно, или с помощью JAXB, если расширение Jackson XML недоступно). По умолчанию строки кодируются в UTF-8.

Если необходимо добавить или настроить преобразователи, то можно использовать класс HttpMessageConverters для Spring Boot, как показано в следующем листинге:

Java
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
        HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
        return new HttpMessageConverters(additional, another);
    }
}
Kotlin
import org.springframework.boot.autoconfigure.http.HttpMessageConverters
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.HttpMessageConverter
@Configuration(proxyBeanMethods = false)
class MyHttpMessageConvertersConfiguration {
    @Bean
    fun customConverters(): HttpMessageConverters {
        val additional: HttpMessageConverter<*> = AdditionalHttpMessageConverter()
        val another: HttpMessageConverter<*> = AnotherHttpMessageConverter()
        return HttpMessageConverters(additional, another)
    }
}

Любой бин HttpMessageConverter, присутствующий в контексте, добавляется в список преобразователей. Таким же образом можно переопределять преобразователи по умолчанию.

MessageCodesResolver

Spring MVC содержит стратегию генерации кодов ошибок для вывода сообщений об ошибках привязки: MessageCodesResolver. Если установлено свойство spring.mvc.message-codes-resolver-format PREFIX_ERROR_CODE или POSTFIX_ERROR_CODE, Spring Boot создаст его для вас.