В этой статье:
➤ Какие могут быть параметры в application.properties
➤ Как использовать WebSocket в Spring
➤ Как использовать GraphQL в Spring
➤ Как использовать OAuth 2.0 в Spring
➤ Как настроить шифрование в spring
➤ Как в Spring настроить HTTPS
➤ Что такое Spring Boot Actuator
➤ Как настроить авторизацию при помощи Spring Security
➤ Что такое Spring Integration
➤ Что такое Spring Batch
➤ Что такое AutoConfiguration в Spring
➤ Как настроить разные профили сборки в Spring
➤ Что такое Spring WebFlux
➤ Что такое ApplicationContext в Spring
➤ Что такое BeanFactory
➤ Что такое Spring BOM
➤ Как настроить безопасность в WebFlux
➤ Как настроить валидацию данных в Spring
➤ Как определять утечки памяти в Spring
➤ Что такое RouterFunction и RequestPredicate
➤ Как Spring приложение может отправлять и принимать данные с других API
➤ Как работает RPC (Remote Procedure Calls)
➤ Как происходит тестирование в Spring
➤ Как в Spring сделать фоновую задачу по таймеру
➤ Как в Spring импортировать данные из CSV
➤ Какие могут быть параметры в application.properties
Конфигурация базы данных:
# URL подключения к базе данных spring.datasource.url=jdbc:h2:mem:testdb # Драйвер базы данных spring.datasource.driverClassName=org.h2.Driver # Имя пользователя базы данных spring.datasource.username=sa # Пароль базы данных spring.datasource.password=password # Платформа базы данных (используется Hibernate) spring.jpa.database-platform=org.hibernate.dialect.H2Dialect # Автоматическое создание и обновление схемы базы данных spring.jpa.hibernate.ddl-auto=update
Конфигурация H2 консоли:
# Включение консоли H2 spring.h2.console.enabled=true # Путь доступа к консоли H2 spring.h2.console.path=/h2-console
Конфигурация сервера:
# Порт, на котором запускается сервер server.port=8080 # Кодировка по умолчанию server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=true
Конфигурация безопасности:
# Путь к странице логина spring.security.user.name=user # Пароль для страницы логина spring.security.user.password=secret # Роль пользователя spring.security.user.roles=USER
Конфигурация кэширования:
# Включение кэширования spring.cache.type=simple
Конфигурация логирования:
# Уровень логирования для приложения logging.level.root=INFO logging.level.org.springframework.web=DEBUG # Путь к файлу логов logging.file.name=logs/spring-boot-application.log # Формат вывода логов logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
Конфигурация интернационализации:
# Локаль по умолчанию spring.mvc.locale=ru_RU # Включение автоматического определения локали spring.mvc.locale-resolver=accept-header
Конфигурация отправки электронной почты:
# SMTP-сервер spring.mail.host=smtp.example.com # Порт SMTP spring.mail.port=587 # Имя пользователя для SMTP [email protected] # Пароль для SMTP spring.mail.password=password # Протокол spring.mail.protocol=smtp # Путь к шаблонам писем spring.mail.templates.path=classpath:/templates/
Конфигурация параметров приложения:
# Пример пользовательского параметра myapp.custom-property=value
➤ Как использовать WebSocket в Spring
Добавьте зависимости для Spring WebSocket в ваш build.gradle.kts (если вы используете Kotlin DSL для Gradle):
dependencies { implementation("org.springframework.boot:spring-boot-starter-websocket") implementation("org.springframework.boot:spring-boot-starter-web") }
Конфигурация WebSocket
import org.springframework.context.annotation.Configuration import org.springframework.web.socket.config.annotation.EnableWebSocket import org.springframework.web.socket.config.annotation.WebSocketConfigurer import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry @Configuration @EnableWebSocket class WebSocketConfig(private val webSocketHandler: MyWebSocketHandler) : WebSocketConfigurer { override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { registry.addHandler(webSocketHandler, "/ws").setAllowedOrigins("*") } }
Реализация обработчика WebSocket
import org.springframework.stereotype.Component import org.springframework.web.socket.TextMessage import org.springframework.web.socket.WebSocketSession import org.springframework.web.socket.handler.TextWebSocketHandler @Component class MyWebSocketHandler : TextWebSocketHandler() { override fun handleTextMessage(session: WebSocketSession, message: TextMessage) { val payload = message.payload println("Received: $payload") val response = TextMessage("Server received: $payload") session.sendMessage(response) } }
➤ Как использовать GraphQL в Spring
Добавление зависимостей
dependencies { implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:11.1.0") implementation("com.graphql-java-kickstart:graphiql-spring-boot-starter:11.1.0") implementation("com.graphql-java-kickstart:altair-spring-boot-starter:11.1.0") implementation("com.graphql-java-kickstart:voyager-spring-boot-starter:11.1.0") implementation("com.graphql-java-kickstart:graphql-java-tools:11.1.0") }
Определение схемы GraphQL:
Создайте файл schema.graphqls в папке src/main/resources.
type Query { getUser(id: ID!): User getAllUsers: [User] } type Mutation { createUser(input: CreateUserInput): User } type User { id: ID! name: String! email: String! } input CreateUserInput { name: String! email: String! }
data class User( val id: Long, val name: String, val email: String )
import org.springframework.stereotype.Repository @Repository class UserRepository { private val users = mutableListOf<User>() private var idCounter = 1L fun getUserById(id: Long): User? { return users.find { it.id == id } } fun getAllUsers(): List<User> { return users } fun createUser(name: String, email: String): User { val user = User(id = idCounter++, name = name, email = email) users.add(user) return user } }
GraphQL Resolvers
import com.coxautodev.graphql.tools.GraphQLQueryResolver import org.springframework.stereotype.Component @Component class UserQueryResolver(private val userRepository: UserRepository) : GraphQLQueryResolver { fun getUser(id: Long): User? { return userRepository.getUserById(id) } fun getAllUsers(): List<User> { return userRepository.getAllUsers() } }
import com.coxautodev.graphql.tools.GraphQLMutationResolver import org.springframework.stereotype.Component data class CreateUserInput(val name: String, val email: String) @Component class UserMutationResolver(private val userRepository: UserRepository) : GraphQLMutationResolver { fun createUser(input: CreateUserInput): User { return userRepository.createUser(input.name, input.email) } }
Примеры запросов и мутаций
Запрос на получение пользователя по ID
query { getUser(id: 1) { id name email } }
Запрос на получение всех пользователей
query { getAllUsers { id name email } }
Мутация для создания нового пользователя
mutation { createUser(input: { name: "John Doe", email: "[email protected]" }) { id name email } }
➤ Как использовать OAuth 2.0 в Spring
Настройка OAuth 2.0 в Spring Boot позволяет вашему приложению безопасно взаимодействовать с сторонними сервисами, такими как Google, Facebook и другими провайдерами OAuth. В этом примере я покажу, как настроить Spring Boot приложение для аутентификации пользователей через OAuth 2.0 с использованием провайдера Google.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'com.fasterxml.jackson.module:jackson-module-kotlin' implementation 'org.jetbrains.kotlin:kotlin-reflect' implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
spring.security.oauth2.client.registration.google.client-id=YOUR_CLIENT_ID spring.security.oauth2.client.registration.google.client-secret=YOUR_CLIENT_SECRET spring.security.oauth2.client.registration.google.scope=profile,email spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/login/oauth2/code/{registrationId} spring.security.oauth2.client.registration.google.client-name=Google spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/auth spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo spring.security.oauth2.client.provider.google.user-name-attribute=sub
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class HomeController { @GetMapping("/") fun home(model: Model, @AuthenticationPrincipal principal: OidcUser?): String { if (principal != null) { model.addAttribute("name", principal.name) model.addAttribute("email", principal.email) } return "home" } @GetMapping("/login") fun login(): String { return "login" } }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests { requests -> requests.antMatchers("/", "/login").permitAll() .anyRequest().authenticated() }.oauth2Login { oauth2Login -> oauth2Login.loginPage("/login") .defaultSuccessURL("/") } return http.build() } }
➤ Как Spring приложение может отправлять и принимать данные с других API
Для этого могут использоваться RestTemplate, WebClient, FeignClient, Apache HttpClient, OkHttp, Retrofit, HTTP4K, Ktor
RestTemplate:
Природа: Синхронный.
Использование: Простой, блокирующий HTTP клиент.
Статус: В режиме поддержки; рекомендуется переходить на WebClient.
Типичное использование: Легаси проекты и простые HTTP вызовы.
Преимущества: Легкость использования, знаком многим разработчикам.
WebClient:
Природа: Асинхронный и неблокирующий.
Использование: Часть Spring WebFlux, поддерживает реактивное программирование.
Статус: Современный клиент для HTTP запросов.
Типичное использование: Высоконагруженные системы и приложения, требующие высокой производительности.
Преимущества: Поддержка асинхронных и синхронных запросов, потоков данных.
FeignClient:
Природа: Синхронный и асинхронный.
Использование: Высокоуровневый клиент для взаимодействия с внешними API.
Статус: Интегрируется с Spring Cloud, используется для микросервисов.
Типичное использование: Микросервисные архитектуры.
Преимущества: Автоматическое создание клиентов, высокая абстракция.
Apache HttpClient:
Природа: Синхронный и асинхронный.
Использование: Низкоуровневый HTTP клиент с множеством конфигураций.
Статус: Широко используемый, мощный и гибкий.
Типичное использование: Случаи, когда требуется низкоуровневый контроль над HTTP запросами.
Преимущества: Гибкость и мощные возможности настройки.
OkHttp:
Природа: Синхронный и асинхронный.
Использование: Современный HTTP клиент с простым API.
Статус: Часто используется в сочетании с Retrofit.
Типичное использование: Android-разработка и JVM-базированные приложения.
Преимущества: Простой и чистый API.
Retrofit:
Природа: Синхронный и асинхронный.
Использование: Высокоуровневый клиент для работы с REST API, интегрируется с OkHttp.
Статус: Популярный выбор для Android и микросервисных архитектур.
Типичное использование: Android и микросервисы.
Преимущества: Автоматическое создание клиентов, поддержка различных конвертеров.
HTTP4K:
Природа: Асинхронный.
Использование: Функциональная библиотека для создания HTTP клиентов и серверов.
Статус: Легковесная и гибкая библиотека.
Типичное использование: Проекты, требующие функционального программирования.
Преимущества: Функциональный подход, простота и гибкость.
Ktor:
Природа: Асинхронный.
Использование: Фреймворк от JetBrains для создания серверных и клиентских приложений на Kotlin.
Статус: Модульная и легко настраиваемая библиотека.
Типичное использование: Высокопроизводительные асинхронные приложения.
Преимущества: Отличная интеграция с Kotlin, высокая производительность.
Сравнительный анализ использования:
RestTemplate:
Все еще используется в легаси проектах, но его использование сокращается.
WebClient:
Все более популярный выбор для новых проектов, особенно тех, которые требуют асинхронности и реактивности.
FeignClient:
Часто используется в микросервисных архитектурах, интегрированных с Spring Cloud.
Apache HttpClient и OkHttp:
Используются для низкоуровневого контроля и в специфичных задачах.
Retrofit:
Популярен в Android-разработке.
HTTP4K и Ktor:
Менее распространены, но популярны среди Kotlin-разработчиков и тех, кто предпочитает функциональный и модульный подходы.
RestTemplate:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' }
rest: url: https://api.example.com/data
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.client.RestTemplate @Configuration class AppConfig { @Bean fun restTemplate(): RestTemplate { return RestTemplate() } }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate @Service class ApiService(@Autowired private val restTemplate: RestTemplate) { fun getDataFromExternalApi(url: String): String? { return restTemplate.getForObject(url, String::class.java) } }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpEntity import org.springframework.http.HttpMethod import org.springframework.http.ResponseEntity import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate data class RequestObject(val name: String, val value: String) data class ResponseObject(val id: Long, val status: String) @Service class ApiService(@Autowired private val restTemplate: RestTemplate) { fun sendAndReceiveObject(url: String, requestObject: RequestObject): ResponseObject? { val requestEntity = HttpEntity(requestObject) val responseEntity: ResponseEntity<ResponseObject> = restTemplate.exchange( url, HttpMethod.POST, requestEntity, ResponseObject::class.java ) return responseEntity.body } }
WebClient:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' }
webclient: base-url: https://api.example.com
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.reactive.function.client.WebClient @Configuration class WebClientConfig { @Bean fun webClient(): WebClient { return WebClient.builder().build() } }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.web.reactive.function.client.WebClient @Service class ApiService(@Autowired private val webClient: WebClient) { fun getDataFromExternalApi(url: String): String? { return webClient.get() .uri(url) .retrieve() .bodyToMono(String::class.java) .block() } }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.web.reactive.function.client.WebClient import reactor.core.publisher.Mono data class RequestObject(val name: String, val value: String) data class ResponseObject(val id: Long, val status: String) @Service class ApiService(@Autowired private val webClient: WebClient) { fun sendAndReceiveObject(url: String, requestObject: RequestObject): ResponseObject? { return webClient.post() .uri(url) .body(Mono.just(requestObject), RequestObject::class.java) .retrieve() .bodyToMono(ResponseObject::class.java) .block() } }
FeignClient:
dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' }
feign: client: config: externalApiClient: url: https://api.example.com
import org.springframework.cloud.openfeign.EnableFeignClients import org.springframework.context.annotation.Configuration @Configuration @EnableFeignClients class FeignConfig
import org.springframework.cloud.openfeign.FeignClient import org.springframework.web.bind.annotation.GetMapping @FeignClient(name = "externalApiClient", url = "https://api.external.com") interface ExternalApiClient { @GetMapping("/data") fun getData(): String }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @Service class ApiService(@Autowired private val externalApiClient: ExternalApiClient) { fun getDataFromExternalApi(): String { return externalApiClient.getData() } }
Apache HttpClient:
dependencies { implementation 'org.apache.httpcomponents:httpclient:4.5.13' }
import org.apache.http.client.methods.HttpGet import org.apache.http.impl.client.CloseableHttpClient import org.apache.http.impl.client.HttpClients import org.apache.http.util.EntityUtils fun sendHttpRequest(url: String): String? { val httpClient: CloseableHttpClient = HttpClients.createDefault() val httpGet = HttpGet(url) httpClient.execute(httpGet).use { response -> return EntityUtils.toString(response.entity) } }
OkHttp:
dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.0' }
import okhttp3.OkHttpClient import okhttp3.Request fun sendOkHttpRequest(url: String): String? { val client = OkHttpClient() val request = Request.Builder() .url(url) .build() client.newCall(request).execute().use { response -> return response.body?.string() } }
Retrofit:
dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' }
import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST data class RequestObject(val name: String, val value: String) data class ResponseObject(val id: Long, val status: String) interface ApiService { @POST("data") suspend fun sendData(@Body requestObject: RequestObject): ResponseObject @GET("data") suspend fun getData(): ResponseObject } val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .build() val apiService = retrofit.create(ApiService::class.java)
Spring WebClient with Reactor:
import org.springframework.web.reactive.function.client.WebClient import reactor.core.publisher.Mono val webClient = WebClient.create("https://api.example.com") fun sendAndReceiveReactiveObject(requestObject: RequestObject): Mono<ResponseObject> { return webClient.post() .uri("/data") .body(Mono.just(requestObject), RequestObject::class.java) .retrieve() .bodyToMono(ResponseObject::class.java) }
HTTP4K:
dependencies { implementation "org.http4k:http4k-core:4.9.9.0" implementation "org.http4k:http4k-client-okhttp:4.9.9.0" }
import org.http4k.client.OkHttp import org.http4k.core.Method import org.http4k.core.Request import org.http4k.core.Response import org.http4k.format.Gson.auto import org.http4k.lens.Body data class RequestObject(val name: String, val value: String) data class ResponseObject(val id: Long, val status: String) val client = OkHttp() fun sendHttp4kRequest(url: String, requestObject: RequestObject): ResponseObject? { val requestLens = Body.auto<RequestObject>().toLens() val responseLens = Body.auto<ResponseObject>().toLens() val request = Request(Method.POST, url) .with(requestLens of requestObject) val response: Response = client(request) return responseLens.extract(response) }
Ktor:
dependencies { implementation "io.ktor:ktor-client-core:1.5.2" implementation "io.ktor:ktor-client-cio:1.5.2" implementation "io.ktor:ktor-client-json:1.5.2" implementation "io.ktor:ktor-client-serialization:1.5.2" }
import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.client.features.json.* import io.ktor.client.features.json.serializer.* import kotlinx.serialization.Serializable @Serializable data class RequestObject(val name: String, val value: String) @Serializable data class ResponseObject(val id: Long, val status: String) val client = HttpClient(CIO) { install(JsonFeature) { serializer = KotlinxSerializer() } } suspend fun sendKtorRequest(url: String, requestObject: RequestObject): ResponseObject { return client.post(url) { body = requestObject } }
➤ Как настроить шифрование в spring
В Spring можно настроить шифрование данных с помощью различных библиотек и подходов. Одним из популярных способов является использование Spring Security Crypto для шифрования и дешифрования данных. В этом примере мы рассмотрим, как настроить шифрование паролей с помощью BCrypt, а также как шифровать данные с помощью Jasypt.
Шифрование паролей с помощью BCrypt:
BCrypt — это хэш-функция, специально разработанная для хеширования паролей. Она обеспечивает надежную защиту от атак, таких как атаки по словарю и атаки перебором.
dependencies { implementation("org.springframework.boot:spring-boot-starter-security") }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } @Bean fun userDetailsService(passwordEncoder: PasswordEncoder): UserDetailsService { val user = User.withUsername("user") .password(passwordEncoder.encode("password")) .roles("USER") .build() return InMemoryUserDetailsManager(user) } @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic() return http.build() } }
Шифрование данных с помощью Jasypt:
Jasypt (Java Simplified Encryption) — это библиотека, которая предоставляет простые способы для шифрования и дешифрования данных.
dependencies { implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4") }
jasypt.encryptor.password=yourEncryptionPassword
import org.jasypt.encryption.StringEncryptor import org.jasypt.encryption.pbe.StandardPBEStringEncryptor import org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.env.Environment import java.util.* @Configuration class JasyptConfig { @Bean(name = ["jasyptStringEncryptor"]) fun stringEncryptor(): StringEncryptor { val encryptor = StandardPBEStringEncryptor() encryptor.setPassword("yourEncryptionPassword") return encryptor } @Bean fun propertyPlaceholderConfigurer(environment: Environment): EncryptablePropertyPlaceholderConfigurer { val configurer = EncryptablePropertyPlaceholderConfigurer(stringEncryptor()) configurer.setLocation(environment) return configurer } }
import org.jasypt.util.text.AES256TextEncryptor import org.springframework.stereotype.Service @Service class DataService { private val textEncryptor = AES256TextEncryptor().apply { setPassword("yourEncryptionPassword") } fun encryptData(data: String): String { return textEncryptor.encrypt(data) } fun decryptData(encryptedData: String): String { return textEncryptor.decrypt(encryptedData) } }
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController class DataController(private val dataService: DataService) { @GetMapping("/encrypt") fun encrypt(@RequestParam data: String): String { return dataService.encryptData(data) } @GetMapping("/decrypt") fun decrypt(@RequestParam encryptedData: String): String { return dataService.decryptData(encryptedData) } }
➤ Как в Spring настроить HTTPS
Использование SSL/TLS-сертификатов для обеспечения безопасного соединения в Spring Boot приложении включает в себя настройку HTTPS. Для этого нужно создать или получить SSL-сертификат, настроить сервер и обновить конфигурацию Spring Boot. Ниже приведен пошаговый пример настройки HTTPS с использованием самозаверяющего сертификата.
Генерация самозаверяющего сертификата:
Для генерации самозаверяющего сертификата можно использовать keytool, который входит в состав JDK.
keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650
myalias:
псевдоним ключа.
keystore.p12:
имя файла хранилища ключей.
validity 3650:
срок действия сертификата (в днях).
При выполнении этой команды вас попросят ввести различные данные, такие как пароль для хранилища ключей, ваше имя, организация и т.д. После этого будет создан файл keystore.p12.
Настройка Spring Boot для использования SSL:
Добавьте настройки SSL в файл application.properties.
server.port=8443 server.ssl.key-store=classpath:keystore.p12 server.ssl.key-store-password=your_password server.ssl.key-store-type=PKCS12 server.ssl.key-alias=myalias
Перемещение файла keystore.p12 в ресурсную папку
Переместите файл keystore.p12 в папку src/main/resources вашего проекта, чтобы Spring Boot мог его найти.
Обновление конфигурации безопасности (если требуется)
Если ваше приложение использует Spring Security, вы можете настроить его для поддержки HTTPS.
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests { requests -> requests.anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login").permitAll() }.logout { logout -> logout.permitAll() }.requiresChannel { channel -> channel.anyRequest().requiresSecure() } return http.build() } }
➤ Что такое Spring Boot Actuator
Spring Boot Actuator — это мощный инструмент в экосистеме Spring Boot, который предоставляет готовые функции для мониторинга и управления работающими приложениями. Actuator предоставляет набор конечных точек (endpoints), которые можно использовать для получения информации о приложении, выполнения операций управления и мониторинга его состояния.
Основные возможности Spring Boot Actuator:
Мониторинг состояния:
Предоставляет информацию о состоянии приложения, таких как метрики производительности, информация о базе данных, данные о системе и т.д.
Управление приложением:
Позволяет выполнять административные операции, такие как перезапуск приложения, получение информации о конфигурации и т.д.
Интеграция с инструментами мониторинга:
Легко интегрируется с инструментами мониторинга и оповещения, такими как Prometheus, Grafana и другие.
Включение Spring Boot Actuator:
Для начала работы с Actuator, необходимо добавить зависимость в ваш проект.
Пример проекта с использованием Gradle
plugins { id("org.springframework.boot") version "2.6.2" id("io.spring.dependency-management") version "1.0.11.RELEASE" kotlin("jvm") version "1.6.0" kotlin("plugin.spring") version "1.6.0" } dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
Настройка Spring Boot Actuator:
По умолчанию некоторые конечные точки Actuator включены, но доступ к ним ограничен. Можно настроить, какие конечные точки будут доступны, а также уровень их безопасности.
application.properties
# Включение всех конечных точек Actuator management.endpoints.web.exposure.include=* # Настройка пути к конечным точкам Actuator management.endpoints.web.base-path=/actuator
Основные конечные точки Actuator:
/actuator/health:
Проверка состояния приложения.
/actuator/info:
Общая информация о приложении.
/actuator/metrics:
Метрики производительности приложения.
/actuator/env:
Информация о текущей конфигурации окружения.
/actuator/beans:
Список всех бинов Spring в контексте приложения.
/actuator/threaddump:
Снимок текущих потоков.
/actuator/loggers:
Уровни логирования и их изменение.
Примеры запросов:
Проверка состояния приложения
curl http://localhost:8080/actuator/health
Получение информации о приложении
curl http://localhost:8080/actuator/info
Получение метрик
curl http://localhost:8080/actuator/metrics
Защита конечных точек Actuator:
Для защиты конечных точек Actuator можно использовать Spring Security. Это позволяет ограничить доступ к административным функциям только авторизованным пользователям.
Пример конфигурации безопасности:
В этом примере доступ к конечным точкам Actuator ограничен пользователями с ролью ADMIN
import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter @Configuration @EnableWebSecurity class SecurityConfig : WebSecurityConfigurerAdapter() { override fun configure(http: HttpSecurity) { http.authorizeRequests() .antMatchers("/actuator/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .httpBasic() } }
➤ Как настроить авторизацию при помощи Spring Security
dependencies { implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeHttpRequests { authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form .loginPage("/login") .permitAll() }.logout { logout -> logout.permitAll() } return http.build() } @Bean fun userDetailsService(): UserDetailsService { val user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() val admin = User.withDefaultPasswordEncoder() .username("admin") .password("admin") .roles("ADMIN") .build() return InMemoryUserDetailsManager(user, admin) } }
import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class HomeController { @GetMapping("/") fun home(model: Model): String { model.addAttribute("message", "Welcome to the Home Page!") return "home" } @GetMapping("/login") fun login(): String { return "login" } }
src/main/resources/templates/home.html:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Home</title> </head> <body> <h1 th:text="${message}">Welcome to the Home Page!</h1> <a href="/logout">Logout</a> </body> </html>
src/main/resources/templates/login.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Login</title> </head> <body> <h1>Login</h1> <form th:action="@{/login}" method="post"> <div> <label>Username:</label> <input type="text" name="username"/> </div> <div> <label>Password:</label> <input type="password" name="password"/> </div> <div> <button type="submit">Login</button> </div> </form> </body> </html>
Пользователь:
Для создания пользовательского класса, наследующего UserDetails, нужно создать класс, который будет реализовывать интерфейс UserDetails. Этот класс будет использоваться для представления пользователя в системе безопасности Spring.
import javax.persistence.* @Entity data class UserEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0, val username: String, val password: String, val enabled: Boolean = true, @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "users_roles", joinColumns = [JoinColumn(name = "user_id")], inverseJoinColumns = [JoinColumn(name = "role_id")] ) val roles: Set<RoleEntity> = HashSet() )
import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id @Entity data class RoleEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0, val name: String )
import org.springframework.data.jpa.repository.JpaRepository interface UserRepository : JpaRepository<UserEntity, Long> { fun findByUsername(username: String): UserEntity? }
import org.springframework.data.jpa.repository.JpaRepository interface RoleRepository : JpaRepository<RoleEntity, Long> { fun findByName(name: String): RoleEntity? }
import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.userdetails.UserDetails class CustomUserDetails(private val user: UserEntity) : UserDetails { override fun getAuthorities(): Collection<GrantedAuthority> { return user.roles.map { SimpleGrantedAuthority(it.name) } } override fun getPassword(): String { return user.password } override fun getUsername(): String { return user.username } override fun isAccountNonExpired(): Boolean { return true } override fun isAccountNonLocked(): Boolean { return true } override fun isCredentialsNonExpired(): Boolean { return true } override fun isEnabled(): Boolean { return user.enabled } }
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.stereotype.Service @Service class CustomUserDetailsService(private val userRepository: UserRepository) : UserDetailsService { override fun loadUserByUsername(username: String): UserDetails { val user = userRepository.findByUsername(username) ?: throw UsernameNotFoundException("User not found with username: $username") return CustomUserDetails(user) } }
import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service @Transactional class UserService( private val userRepository: UserRepository, private val roleRepository: RoleRepository ) { fun createUser(username: String, password: String, roles: Set<String>): UserEntity { val roleEntities = roles.map { roleRepository.findByName(it) ?: RoleEntity(name = it) }.toSet() val user = UserEntity(username = username, password = password, roles = roleEntities) return userRepository.save(user) } }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig( private val customUserDetailsService: CustomUserDetailsService ) { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests { authz -> authz.antMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login") .permitAll() }.logout { logout -> logout.permitAll() } return http.build() } @Bean fun userDetailsService(): UserDetailsService { return customUserDetailsService } @Bean fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } }
Использование UserService для создания пользователя
import org.springframework.boot.CommandLineRunner import org.springframework.stereotype.Component @Component class DataInitializer(private val userService: UserService) : CommandLineRunner { override fun run(vararg args: String?) { userService.createUser("user", passwordEncoder().encode("password"), setOf("ROLE_USER")) userService.createUser("admin", passwordEncoder().encode("admin"), setOf("ROLE_ADMIN")) } fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } }
В Spring Security можно использовать аннотации @PreAuthorize и @Secured для ограничения доступа к методам контроллеров или сервисов на основе ролей. Вот примеры их использования.
import org.springframework.security.access.prepost.PreAuthorize import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class UserController { @PreAuthorize("hasRole('ROLE_USER')") @GetMapping("/user") fun userPage(model: Model): String { model.addAttribute("message", "Welcome, User!") return "user" } @PreAuthorize("hasRole('ROLE_ADMIN')") @GetMapping("/admin") fun adminPage(model: Model): String { model.addAttribute("message", "Welcome, Admin!") return "admin" } }
import org.springframework.security.access.annotation.Secured import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class AdminController { @Secured("ROLE_USER") @GetMapping("/user-secured") fun userPageSecured(model: Model): String { model.addAttribute("message", "Welcome, User! (Secured)") return "user-secured" } @Secured("ROLE_ADMIN") @GetMapping("/admin-secured") fun adminPageSecured(model: Model): String { model.addAttribute("message", "Welcome, Admin! (Secured)") return "admin-secured" } }
In-Memory Storage:
In-Memory хранение пользователей — самый простой способ для создания пользователей. Вы уже видели пример выше. Вот еще раз пример для ясности:
@Bean fun userDetailsService(): UserDetailsService { val user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() val admin = User.withDefaultPasswordEncoder() .username("admin") .password("admin") .roles("ADMIN") .build() return InMemoryUserDetailsManager(user, admin) }
JDBC Storage:
Для использования JDBC хранилища, вам потребуется настроить базу данных и использовать JdbcUserDetailsManager. Пример:
dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") runtimeOnly("com.h2database:h2") // Или другой драйвер базы данных testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.User import org.springframework.security.provisioning.JdbcUserDetailsManager import org.springframework.security.provisioning.UserDetailsManager import org.springframework.security.web.SecurityFilterChain import javax.sql.DataSource @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeHttpRequests { authz -> authz.requestMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login") .permitAll() } .logout { logout -> logout.permitAll() } return http.build() } @Bean fun userDetailsService(dataSource: DataSource): UserDetailsManager { val userDetailsManager = JdbcUserDetailsManager(dataSource) // Добавление пользователей, если их нет в базе данных if (!userDetailsManager.userExists("user")) { val user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() userDetailsManager.createUser(user) } if (!userDetailsManager.userExists("admin")) { val admin = User.withDefaultPasswordEncoder() .username("admin") .password("admin") .roles("ADMIN") .build() userDetailsManager.createUser(admin) } return userDetailsManager } }
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.h2.console.enabled=true
Скрипты для создания таблиц:
Spring Security предоставляет готовые SQL-скрипты для создания таблиц пользователей и ролей. Добавьте их в src/main/resources/schema.sql:
CREATE TABLE users ( username VARCHAR(50) NOT NULL PRIMARY KEY, password VARCHAR(500) NOT NULL, enabled BOOLEAN NOT NULL ); CREATE TABLE authorities ( username VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL, FOREIGN KEY (username) REFERENCES users (username) ); CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);
Custom UserDetailsService:
Если вам нужно кастомное хранилище, создайте собственную реализацию UserDetailsService.
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.stereotype.Service @Service class CustomUserDetailsService : UserDetailsService { override fun loadUserByUsername(username: String): UserDetails { // Замените этот код на собственное хранилище (например, запрос к базе данных) if (username == "user") { return User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() } else { throw UsernameNotFoundException("User not found") } } }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig( val customUserDetailsService: CustomUserDetailsService ) { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeHttpRequests { authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login") .permitAll() }.logout { logout -> logout.permitAll() } return http.build() } @Bean fun userDetailsService(): UserDetailsService { return customUserDetailsService } }
➤ Что такое Spring Integration
Spring Integration — это модуль в экосистеме Spring, предназначенный для создания корпоративных интеграционных решений. Он предоставляет поддержку интеграционных шаблонов (Enterprise Integration Patterns, EIP), которые помогают разработчикам проектировать и реализовывать интеграцию различных систем и компонентов в приложении. Spring Integration позволяет легко связывать и координировать взаимодействие между различными системами и компонентами, обеспечивая масштабируемость, надежность и простоту поддержки.
Основные возможности Spring Integration:
Поддержка интеграционных шаблонов (EIP):
Включает в себя поддержку популярных шаблонов интеграции, таких как маршрутизация сообщений, преобразование сообщений, фильтрация сообщений и агрегация сообщений.
Соединение с различными системами:
Обеспечивает подключение к различным источникам данных и внешним системам, таким как базы данных, очереди сообщений (JMS, RabbitMQ), веб-службы (REST, SOAP), файлы и другие.
Модульная архитектура:
Предоставляет модульную архитектуру, позволяющую легко добавлять и конфигурировать компоненты интеграции.
Расширяемость:
Позволяет разработчикам легко расширять функциональность путем добавления своих собственных компонентов интеграции.
Основные компоненты Spring Integration:
Message (Сообщение):
Основная структура данных, которая передается между компонентами. Сообщение состоит из полезной нагрузки (payload) и заголовков (headers).
Channel (Канал):
Транспортный механизм, через который передаются сообщения. Каналы могут быть точка-точка (point-to-point) или публикация-подписка (publish-subscribe).
Endpoint (Конечная точка):
Компоненты, которые отправляют или получают сообщения. Конечные точки включают преобразователи сообщений (message transformers), маршрутизаторы (routers), фильтры (filters) и другие обработчики сообщений.
Gateway (Шлюз):
Интерфейсы, которые обеспечивают интеграцию с внешними системами и протоколами. Шлюзы могут быть использованы для отправки и получения сообщений из/в веб-службы, очереди сообщений и других систем.
Пример использования Spring Integration:
Рассмотрим пример, где мы используем Spring Integration для чтения сообщений из очереди JMS и их обработки.
dependencies { implementation("org.springframework.boot:spring-boot-starter-integration") implementation("org.springframework.boot:spring-boot-starter-activemq") implementation("org.springframework.integration:spring-integration-jms") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.integration.annotation.ServiceActivator import org.springframework.integration.channel.DirectChannel import org.springframework.integration.config.EnableIntegration import org.springframework.integration.core.MessageHandler import org.springframework.integration.jms.dsl.Jms import org.springframework.jms.annotation.EnableJms import org.springframework.jms.core.JmsTemplate import javax.jms.ConnectionFactory @Configuration @EnableIntegration @EnableJms class IntegrationConfig { @Bean fun inputChannel() = DirectChannel() @Bean fun jmsInboundAdapter(connectionFactory: ConnectionFactory) = Jms.inboundAdapter(connectionFactory) .destination("inputQueue") .channel(inputChannel()) .get() @Bean @ServiceActivator(inputChannel = "inputChannel") fun messageHandler(): MessageHandler { return MessageHandler { message -> println("Received message: ${message.payload}") } } }
Отправка сообщений в очередь JMS
import org.springframework.jms.core.JmsTemplate import org.springframework.stereotype.Component @Component class JmsProducer(private val jmsTemplate: JmsTemplate) { fun sendMessage(destination: String, message: String) { jmsTemplate.convertAndSend(destination, message) } }
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.context.annotation.Bean import javax.annotation.PostConstruct @SpringBootApplication class DemoApplication(private val jmsProducer: JmsProducer) { @PostConstruct fun init() { jmsProducer.sendMessage("inputQueue", "Hello, Spring Integration!") } } fun main(args: Array<String>) { runApplication<DemoApplication>(*args) }
➤ Что такое Spring Batch
Spring Batch — это модуль в экосистеме Spring, предназначенный для создания масштабируемых, надежных и многопоточечных пакетных приложений. Он предоставляет мощный фреймворк для обработки больших объемов данных в пакетном режиме, включая чтение, обработку и запись данных.
Основные возможности Spring Batch:
Чтение, обработка и запись данных:
Поддерживает различные источники данных для чтения, такие как базы данных, файлы, очереди сообщений и т.д.
Позволяет выполнять сложные операции обработки данных.
Поддерживает запись данных в различные источники.
Модульная архитектура:
Пакетная работа (Job) состоит из шагов (Step), каждый из которых может быть конфигурирован независимо.
Каждый шаг включает в себя три основные фазы: чтение, обработка и запись (Read, Process, Write).
Управление транзакциями и отказами:
Поддерживает управление транзакциями, откатами и повторными попытками для обеспечения надежности и целостности данных.
Планирование и мониторинг:
Интеграция с различными планировщиками задач (Quartz, Spring Scheduling и др.).
Поддержка мониторинга и аудита выполнения пакетных задач.
Поддержка параллелизма и многопоточности:
Обеспечивает возможность выполнения шагов и задач в многопоточном режиме для повышения производительности.
Основные компоненты Spring Batch:
Job (Работа):
Пакетная задача, состоящая из одного или нескольких шагов (Step). Определяет, какие шаги должны быть выполнены и в каком порядке.
Step (Шаг):
Единица выполнения внутри работы. Состоит из этапов чтения, обработки и записи данных. Каждый шаг может иметь свою собственную логику и конфигурацию.
ItemReader (Читатель элементов):
Компонент, который отвечает за чтение данных из источника. Примеры включают FlatFileItemReader для чтения данных из файла и JdbcCursorItemReader для чтения данных из базы данных.
ItemProcessor (Процессор элементов):
Компонент, который отвечает за обработку данных. Может выполнять любые преобразования или фильтрацию данных.
ItemWriter (Записыватель элементов):
Компонент, который отвечает за запись данных в целевой источник. Примеры включают FlatFileItemWriter для записи данных в файл и JdbcBatchItemWriter для записи данных в базу данных.
Пример использования Spring Batch:
Рассмотрим пример использования Spring Batch для чтения данных из CSV-файла, их обработки и записи в базу данных.
dependencies { implementation("org.springframework.boot:spring-boot-starter-batch") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") runtimeOnly("com.h2database:h2") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.batch.core.Job import org.springframework.batch.core.Step import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing import org.springframework.batch.core.configuration.annotation.JobBuilderFactory import org.springframework.batch.core.configuration.annotation.StepBuilderFactory import org.springframework.batch.core.launch.support.RunIdIncrementer import org.springframework.batch.item.ItemProcessor import org.springframework.batch.item.ItemReader import org.springframework.batch.item.ItemWriter import org.springframework.batch.item.file.FlatFileItemReader import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper import org.springframework.batch.item.file.mapping.DefaultLineMapper import org.springframework.batch.item.file.mapping.DelimitedLineTokenizer import org.springframework.batch.item.file.transform.LineTokenizer import org.springframework.batch.item.file.transform.Range import org.springframework.batch.item.support.ListItemReader import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.io.ClassPathResource import javax.sql.DataSource @Configuration @EnableBatchProcessing class BatchConfig( private val jobBuilderFactory: JobBuilderFactory, private val stepBuilderFactory: StepBuilderFactory, private val dataSource: DataSource ) { @Bean fun personReader(): ItemReader<Person> { val reader = FlatFileItemReader<Person>() reader.setResource(ClassPathResource("people.csv")) reader.setLineMapper(DefaultLineMapper<Person>().apply { setLineTokenizer(DelimitedLineTokenizer().apply { setNames("firstName", "lastName") }) setFieldSetMapper(BeanWrapperFieldSetMapper<Person>().apply { setTargetType(Person::class.java) }) }) return reader } @Bean fun personProcessor(): ItemProcessor<Person, Person> { return ItemProcessor { person -> person.copy( firstName = person.firstName.toUpperCase(), lastName = person.lastName.toUpperCase() ) } } @Bean fun personWriter(): ItemWriter<Person> { return ItemWriter { items -> items.forEach { println("Writing person: $it") } } } @Bean fun importUserJob(): Job { return jobBuilderFactory.get("importUserJob") .incrementer(RunIdIncrementer()) .flow(step1()) .end() .build() } @Bean fun step1(): Step { return stepBuilderFactory.get("step1") .chunk<Person, Person>(10) .reader(personReader()) .processor(personProcessor()) .writer(personWriter()) .build() } }
data class Person( val firstName: String = "", val lastName: String = "" )
CSV-файл (resources/people.csv)
John,Doe Jane,Doe
➤ Что такое AutoConfiguration в Spring
Автоконфигурация (Auto-configuration) в Spring Boot — это механизм, который автоматически конфигурирует ваше Spring-приложение на основе зависимостей, найденных в classpath, и различных условий. Это одна из ключевых особенностей Spring Boot, позволяющая минимизировать количество конфигураций, необходимых для запуска приложения.
Как работает автоконфигурация:
Spring Boot анализирует классы в classpath вашего проекта и автоматически создает и настраивает необходимые бины для вашего приложения. Этот процесс управляется аннотацией @EnableAutoConfiguration, которая обычно включена в аннотацию @SpringBootApplication.
Примеры работы автоконфигурации:
Конфигурация базы данных:
Если в вашем classpath есть H2, HSQLDB или другая поддерживаемая база данных, Spring Boot автоматически создаст бины для DataSource, EntityManager и настроит их.
Конфигурация веб-сервера:
Если у вас есть зависимости от spring-boot-starter-web, Spring Boot автоматически настроит веб-сервер (например, Tomcat) и свяжет его с вашим приложением.
Основные файлы и классы автоконфигурации:
META-INF/spring.factories:
Этот файл указывает на классы автоконфигурации, которые должны быть загружены. Он содержится в JAR-файлах с автоконфигурацией.
Классы автоконфигурации:
Классы, помеченные аннотацией @Configuration и обычно содержащие логику настройки бинов, основанную на присутствии или отсутствии определенных классов или бинов в контексте приложения.
Определение автоконфигурации:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @Configuration public class MyServiceAutoConfiguration { @Bean @ConditionalOnMissingBean public MyService myService() { return new MyService(); } }
Регистрация автоконфигурации в META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.autoconfiguration.MyServiceAutoConfiguration
Условия автоконфигурации:
Spring Boot предоставляет множество аннотаций, которые можно использовать для управления тем, когда автоконфигурация должна быть применена:
@ConditionalOnClass:
Автоконфигурация применяется, если указанный класс находится в classpath.
@ConditionalOnMissingBean:
Автоконфигурация применяется, если бин отсутствует в контексте.
@ConditionalOnProperty:
Автоконфигурация применяется, если определенное свойство установлено в application.properties или application.yml.
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @Configuration @ConditionalOnClass(MyService.class) public class MyServiceAutoConfiguration { @Bean @ConditionalOnMissingBean public MyService myService() { return new MyService(); } }
Отключение автоконфигурации:
Иногда может потребоваться отключить определенные автоконфигурации. Это можно сделать с помощью аннотации @SpringBootApplication или @EnableAutoConfiguration.
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration import org.springframework.boot.runApplication @SpringBootApplication(exclude = [DataSourceAutoConfiguration::class]) class MyApplication fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args) }
➤ Как настроить разные профили сборки в Spring
В Spring Boot можно использовать профили для управления различными конфигурациями вашего приложения, включая разные базовые URL. Профили позволяют вам создавать несколько конфигураций для разных сред (например, разработка, тестирование, продакшен) и переключаться между ними, не изменяя основной код приложения.
Шаги для создания нескольких вариантов сборки с разным base URL:
Определение профилей в application.properties или application.yml
Создайте файлы конфигурации для каждого профиля. Например, создадим три профиля: dev, test и prod, каждый из которых будет иметь свой базовый URL.
# application-dev.properties app.base.url=https://dev.example.com
# application-test.properties app.base.url=https://test.example.com
#application-prod.properties app.base.url=https://prod.example.com
Настройка основного файла конфигурации:
В основном файле application.properties или application.yml можно определить общие настройки и указать активный профиль по умолчанию (необязательно).
# application.properties spring.profiles.active=dev
Чтение конфигурации в вашем коде:
Используйте аннотацию @Value или @ConfigurationProperties для получения значения base URL из конфигурационных файлов.
import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Configuration @Configuration class AppConfig { @Value("\${app.base.url}") lateinit var baseUrl: String }
Использование профилей в коде:
Вы можете также использовать аннотацию @Profile для включения или отключения бинов в зависимости от активного профиля.
import org.springframework.context.annotation.Profile import org.springframework.stereotype.Service interface ExampleService { fun getBaseUrl(): String } @Service @Profile("dev") class DevExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl } } @Service @Profile("test") class TestExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl } } @Service @Profile("prod") class ProdExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl } }
Запуск приложения с указанным профилем:
Вы можете указать активный профиль при запуске приложения через параметры командной строки, переменные среды или файл конфигурации.
Через параметры командной строки
$ java -jar myapp.jar --spring.profiles.active=prod
Через переменные среды
$ export SPRING_PROFILES_ACTIVE=prod $ java -jar myapp.jar
➤ Что такое Spring WebFlux
Spring WebFlux — это реактивный веб-фреймворк, представленный в Spring 5, который позволяет создавать асинхронные и неблокирующие веб-приложения. WebFlux может работать на различных реактивных движках, таких как Netty, Jetty или Tomcat.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive' }
Примеры реактивного и не реактивного кода для Spring WebFlux и Spring Web (обычный Spring MVC) с использованием Kotlin. Будет показано, как создавать REST API для простого CRUD-приложения, которое работает с пользователями (User).
Пример не реактивного кода для Spring Web (Spring MVC):
import javax.persistence.Entity import javax.persistence.Id import javax.persistence.GeneratedValue import javax.persistence.GenerationType @Entity data class User( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, val email: String, val username: String )
import org.springframework.data.jpa.repository.JpaRepository interface UserRepository : JpaRepository<User, Long>
import org.springframework.stereotype.Service @Service class UserService(private val userRepository: UserRepository) { fun getAllUsers(): List<User> { return userRepository.findAll() } fun getUserById(id: Long): User? { return userRepository.findById(id).orElse(null) } fun createUser(user: User): User { return userRepository.save(user) } fun updateUser(id: Long, user: User): User? { return userRepository.findById(id).map { val updatedUser = it.copy(email = user.email, username = user.username) userRepository.save(updatedUser) }.orElse(null) } fun deleteUser(id: Long) { userRepository.deleteById(id) } }
import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/users") class UserController(private val userService: UserService) { @GetMapping fun getAllUsers(): List<User> { return userService.getAllUsers() } @GetMapping("/{id}") fun getUserById(@PathVariable id: Long): User? { return userService.getUserById(id) } @PostMapping fun createUser(@RequestBody user: User): User { return userService.createUser(user) } @PutMapping("/{id}") fun updateUser(@PathVariable id: Long, @RequestBody user: User): User? { return userService.updateUser(id, user) } @DeleteMapping("/{id}") fun deleteUser(@PathVariable id: Long) { userService.deleteUser(id) } }
Пример реактивного кода для Spring WebFlux:
import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document @Document(collection = "users") data class User( @Id val id: String? = null, val email: String, val username: String )
import org.springframework.data.mongodb.repository.ReactiveMongoRepository import reactor.core.publisher.Mono interface UserRepository : ReactiveMongoRepository<User, String>
import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Mono @Service class UserService(private val userRepository: UserRepository) { fun getAllUsers(): Flux<User> { return userRepository.findAll() } fun getUserById(id: String): Mono<User> { return userRepository.findById(id) } fun createUser(user: User): Mono<User> { return userRepository.save(user) } fun updateUser(id: String, user: User): Mono<User> { return userRepository.findById(id) .flatMap { val updatedUser = it.copy(email = user.email, username = user.username) userRepository.save(updatedUser) } } fun deleteUser(id: String): Mono<Void> { return userRepository.deleteById(id) } }
import org.springframework.web.bind.annotation.* import reactor.core.publisher.Flux import reactor.core.publisher.Mono @RestController @RequestMapping("/users") class UserController(private val userService: UserService) { @GetMapping fun getAllUsers(): Flux<User> { return userService.getAllUsers() } @GetMapping("/{id}") fun getUserById(@PathVariable id: String): Mono<User> { return userService.getUserById(id) } @PostMapping fun createUser(@RequestBody user: User): Mono<User> { return userService.createUser(user) } @PutMapping("/{id}") fun updateUser(@PathVariable id: String, @RequestBody user: User): Mono<User> { return userService.updateUser(id, user) } @DeleteMapping("/{id}") fun deleteUser(@PathVariable id: String): Mono<Void> { return userService.deleteUser(id) } }
Основные отличия заключаются в том, что реактивный подход использует типы Mono и Flux для работы с асинхронными потоками данных, что позволяет улучшить производительность и масштабируемость приложения, особенно при работе с большим количеством одновременных запросов.
➤ Что такое ApplicationContext в Spring
ApplicationContext это центральный интерфейс для конфигурирования приложения и получения доступов к его компонентам (бинам). Он является расширением интерфейса BeanFactory, который обеспечивает базовую функциональность контейнера инверсии управления (IoC). Основное отличие ApplicationContext от BeanFactory заключается в дополнительных возможностях, таких как:
Поддержка различных типов событий.
Возможность загрузки сообщений из файлов локализации.
Поддержка декларативного управления транзакциями.
Пример создания ApplicationContext:
Создание ApplicationContext из XML конфигурации
<!-- beans.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="exampleBean" class="com.example.ExampleBean"> <property name="message" value="Hello, Spring!" /> </bean> </beans>
import org.springframework.context.ApplicationContext import org.springframework.context.support.ClassPathXmlApplicationContext fun main() { val context: ApplicationContext = ClassPathXmlApplicationContext("beans.xml") val exampleBean: ExampleBean = context.getBean("exampleBean", ExampleBean::class.java) println(exampleBean.message) }
Создание ApplicationContext
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class AppConfig { @Bean fun exampleBean(): ExampleBean { return ExampleBean("Hello, Spring with Kotlin DSL!") } }
import org.springframework.context.ApplicationContext import org.springframework.context.annotation.AnnotationConfigApplicationContext fun main() { val context: ApplicationContext = AnnotationConfigApplicationContext(AppConfig::class.java) val exampleBean: ExampleBean = context.getBean(ExampleBean::class.java) println(exampleBean.message) }
➤ Что такое BeanFactory
BeanFactory является центральным интерфейсом в Spring для управления объектами, которые составляют ваше приложение. Хотя BeanFactory является фундаментальным интерфейсом для контейнера Spring, чаще используется его более функциональный потомок — ApplicationContext. Однако, BeanFactory может быть полезен в некоторых ситуациях, например, когда вам нужно минимальное использование памяти или более тонкий контроль над инициализацией.
Основные методы BeanFactory:
getBean:
Возвращает экземпляр бина по его имени или типу.
containsBean:
Проверяет, существует ли бин с указанным именем.
isSingleton:
Проверяет, является ли бин с указанным именем синглтоном.
isPrototype:
Проверяет, является ли бин с указанным именем прототипом.
getType:
Возвращает тип бина по его имени.
getAliases:
Возвращает все псевдонимы для бина с указанным именем.
Пример использования BeanFactory с XML:
<!-- beans.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myBean" class="com.example.MyBean"> <property name="name" value="Bean Name"/> </bean> </beans>
import org.springframework.beans.factory.BeanFactory import org.springframework.beans.factory.xml.XmlBeanFactory import org.springframework.core.io.ClassPathResource class MyBean { var name: String? = null } fun main() { // Загрузка конфигурационного файла Spring val resource = ClassPathResource("beans.xml") val beanFactory: BeanFactory = XmlBeanFactory(resource) // Получение бина по имени val myBean = beanFactory.getBean("myBean") as MyBean println("Bean Name: ${myBean.name}") // Проверка, существует ли бин val containsBean = beanFactory.containsBean("myBean") println("Contains 'myBean': $containsBean") // Проверка, является ли бин синглтоном val isSingleton = beanFactory.isSingleton("myBean") println("Is 'myBean' Singleton: $isSingleton") // Получение типа бина val beanType = beanFactory.getType("myBean") println("Bean Type: $beanType") // Получение всех псевдонимов для бина val aliases = beanFactory.getAliases("myBean") println("Bean Aliases: ${aliases.joinToString()}") }
import org.springframework.context.ApplicationContext import org.springframework.context.support.ClassPathXmlApplicationContext fun main() { // Загрузка конфигурационного файла Spring val context: ApplicationContext = ClassPathXmlApplicationContext("beans.xml") // Получение бина по имени val myBean = context.getBean("myBean") as MyBean println("Bean Name: ${myBean.name}") // Проверка, существует ли бин val containsBean = context.containsBean("myBean") println("Contains 'myBean': $containsBean") // Проверка, является ли бин синглтоном val isSingleton = context.isSingleton("myBean") println("Is 'myBean' Singleton: $isSingleton") // Получение типа бина val beanType = context.getType("myBean") println("Bean Type: $beanType") // Получение всех псевдонимов для бина val aliases = context.getAliases("myBean") println("Bean Aliases: ${aliases.joinToString()}") }
Пример с использованием аннотаций:
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class AppConfig { @Bean fun myBean(): MyBean { return MyBean().apply { name = "Bean Name" } } }
import org.springframework.beans.factory.BeanFactory import org.springframework.stereotype.Service @Service class TestClass( private val beanFactory: BeanFactory ) { fun start() { val myBean = beanFactory.getBean("myBean") as GreetingHandler } }
import org.springframework.context.annotation.AnnotationConfigApplicationContext fun main() { // Создание контекста на основе конфигурационного класса val context = AnnotationConfigApplicationContext(AppConfig::class.java) // Получение BeanFactory из контекста val beanFactory = context.beanFactory // Получение бина по имени val myBean = beanFactory.getBean("myBean") as MyBean println("Bean Name: ${myBean.name}") // Проверка, существует ли бин val containsBean = beanFactory.containsBean("myBean") println("Contains 'myBean': $containsBean") // Проверка, является ли бин синглтоном val isSingleton = beanFactory.isSingleton("myBean") println("Is 'myBean' Singleton: $isSingleton") // Получение типа бина val beanType = beanFactory.getType("myBean") println("Bean Type: $beanType") // Получение всех псевдонимов для бина val aliases = beanFactory.getAliases("myBean") println("Bean Aliases: ${aliases.joinToString()}") }
➤ Что такое Spring BOM
Spring BOM (Bill Of Materials) — это механизм управления зависимостями, предоставляемый Spring для упрощения управления версиями различных артефактов (библиотек) в экосистеме Spring. BOM используется для того, чтобы гарантировать совместимость и согласованность версий всех зависимостей, используемых в проекте. Используя BOM, вы можете указать одну версию BOM, и все связанные библиотеки будут автоматически использовать соответствующие версии, указанные в BOM.
Пример с использованием Spring BOM:
plugins { id 'org.springframework.boot' version '2.6.7' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } dependencyManagement { imports { mavenBom "org.springframework.boot:spring-boot-dependencies:2.6.7" } } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
Пример без использования Spring BOM:
plugins { id 'org.springframework.boot' version '2.6.7' id 'java' } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:2.6.7' implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.6.7' implementation 'org.springframework.boot:spring-boot-starter-security:2.6.7' runtimeOnly 'com.h2database:h2:1.4.200' testImplementation 'org.springframework.boot:spring-boot-starter-test:2.6.7' }
➤ Как настроить безопасность в WebFlux
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.web.server.ServerHttpSecurity import org.springframework.security.core.userdetails.MapReactiveUserDetailsService import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.access.prepost.PreAuthorize import org.springframework.stereotype.Service import reactor.core.publisher.Mono @Configuration @EnableWebFluxSecurity @EnableReactiveMethodSecurity class SecurityConfig { @Bean fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { return http.authorizeExchange { exchanges -> exchanges.pathMatchers("/public/**") .permitAll() .anyExchange() .authenticated() } .httpBasic().and() .formLogin().and() .csrf().disable() .build() } @Bean fun userDetailsService(): MapReactiveUserDetailsService { val user: UserDetails = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() val admin: UserDetails = User.withDefaultPasswordEncoder() .username("admin") .password("password") .roles("ADMIN") .build() return MapReactiveUserDetailsService(user, admin) } } @Service class SecuredService { @PreAuthorize("hasRole('ADMIN')") fun adminMethod(): Mono<String> { return Mono.just("Admin access granted") } @PreAuthorize("hasRole('USER')") fun userMethod(): Mono<String> { return Mono.just("User access granted") } }
Используется аннотация @EnableWebFluxSecurity для включения безопасности WebFlux.
Добавлена аннотация @EnableReactiveMethodSecurity для включения безопасности на уровне методов.
Конфигурационный класс SecurityConfig определяет два бина: securityWebFilterChain и userDetailsService.
Метод securityWebFilterChain настраивает правила безопасности, разрешая доступ к публичным URL (например, /public/**) всем пользователям, и требуя аутентификации для всех остальных запросов. Также включены базовая и форма аутентификации, а CSRF защита отключена.
Метод userDetailsService создает два пользователя с ролями “USER” и “ADMIN” с использованием простого метода шифрования паролей.
Класс SecuredService содержит методы, защищенные аннотациями @PreAuthorize, которые ограничивают доступ к методам в зависимости от ролей пользователей. Метод adminMethod доступен только пользователям с ролью “ADMIN”, а метод userMethod — пользователям с ролью “USER”.
Еще пример:
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.HttpStatus import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.web.server.ServerHttpSecurity import org.springframework.security.crypto.password.NoOpPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.server.SecurityWebFilterChain import reactor.core.publisher.Mono @Configuration @EnableWebFluxSecurity @EnableReactiveMethodSecurity class WebSecurityConfig( private val authenticationManager: AuthenticationManager, private val securityContextRepository: SecurityContextRepository ) { @Bean fun passwordEncoder(): PasswordEncoder { return NoOpPasswordEncoder.getInstance() } @Bean fun securityWebFilterChain(httpSecurity: ServerHttpSecurity): SecurityWebFilterChain { return httpSecurity.exceptionHandling() .authenticationEntryPoint { swe, _ -> Mono.fromRunnable { swe.response.statusCode = HttpStatus.UNAUTHORIZED } } .accessDeniedHandler { swe, _ -> Mono.fromRunnable { swe.response.statusCode = HttpStatus.FORBIDDEN } } .and() .csrf().disable() .formLogin().disable() .httpBasic().disable() .authenticationManager(authenticationManager) .securityContextRepository(securityContextRepository) .authorizeExchange() .pathMatchers("/", "/login", "/favicon.ico").permitAll() .pathMatchers("/controller").hasRole("ADMIN") .anyExchange().authenticated() .and() .build() } }
➤ Как настроить валидацию данных в Spring
Spring Framework предоставляет мощные и гибкие инструменты для валидации данных, включая аннотации, встроенные валидационные механизмы и поддержку пользовательских валидаторов.
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import javax.validation.constraints.Email import javax.validation.constraints.NotBlank import javax.validation.constraints.Size data class User( @field:NotBlank(message = "Name is mandatory") val name: String, @field:Email(message = "Email should be valid") @field:NotBlank(message = "Email is mandatory") val email: String, @field:Size(min = 8, message = "Password should be at least 8 characters") val password: String )
import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import javax.validation.Valid @RestController @RequestMapping("/api/users") class UserController { @PostMapping("/register") fun registerUser(@Valid @RequestBody user: User): ResponseEntity<String> { // В реальном приложении здесь будет логика регистрации пользователя return ResponseEntity("User registered successfully", HttpStatus.OK) } }
Обработка ошибок валидации:
import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ControllerAdvice import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.ResponseStatus @ControllerAdvice class ValidationHandler { @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException::class) fun handleValidationExceptions(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, String>> { val errors: MutableMap<String, String> = HashMap() ex.bindingResult.fieldErrors.forEach { error -> errors[error.field] = error.defaultMessage ?: "Invalid value" } return ResponseEntity(errors, HttpStatus.BAD_REQUEST) } }
Пример запроса:
POST /api/users/register Content-Type: application/json { "name": "", "email": "invalid-email", "password": "short" }
Пример ответа:
{ "name": "Name is mandatory", "email": "Email should be valid", "password": "Password should be at least 8 characters" }
➤ Как определять утечки памяти в Spring
Утечки памяти могут быть серьезной проблемой для любых приложений, включая Spring-приложения. Выявление и устранение утечек памяти требует систематического подхода, использования инструментов для профилирования и мониторинга, а также знания типичных причин утечек памяти в Java-приложениях.
Основные шаги для выявления утечек памяти:
Мониторинг памяти в реальном времени
Использование инструментов для профилирования
Анализ дампов памяти
Использование логов и метрик
Поиск типичных проблемных мест
Мониторинг памяти в реальном времени:
Для мониторинга использования памяти в реальном времени можно использовать такие инструменты, как JVisualVM, JConsole или внешние APM (Application Performance Monitoring) решения, такие как New Relic, Dynatrace или Prometheus.
Использование инструментов для профилирования:
Профилировщики помогают выявлять утечки памяти, показывая, какие объекты занимают много памяти и как они связаны друг с другом.
JVisualVM:
Запустите JVisualVM, который поставляется с JDK.
Подключитесь к работающему JVM-процессу.
Перейдите на вкладку Memory и нажмите Heap Dump для создания дампа памяти.
Анализируйте количество объектов и их удерживаемые размеры.
YourKit:
YourKit Java Profiler предоставляет мощные возможности для анализа памяти, включая сбор дампов, анализ утечек и профилирование выполнения.
Запустите YourKit и подключитесь к JVM.
Создайте дамп памяти и проанализируйте его с помощью встроенных инструментов.
Анализ дампов памяти:
Анализ дампов памяти помогает понять, какие объекты не освобождаются из памяти.
Eclipse Memory Analyzer (MAT):
Используйте Eclipse MAT для анализа дампов памяти.
Откройте дамп памяти и используйте инструменты, такие как Leak Suspects Report и Histogram для выявления проблем.
Использование логов и метрик:
Логи и метрики могут помочь в выявлении проблем с памятью.
Spring Boot Actuator:
Используйте метрики, такие как jvm.memory.used, для мониторинга использования памяти.
dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") }
management.endpoints.web.exposure.include=*
Garbage Collection Logs:
Включите логи сборщика мусора в вашем приложении, добавив следующие параметры JVM:
-Xlog:gc*:file=gc.log:time
Поиск типичных проблемных мест:
Некоторые распространенные причины утечек памяти в Spring-приложениях включают
Неудаляемые кэшированные объекты:
Убедитесь, что кэши настроены правильно и объекты удаляются из кэша по мере необходимости.
Неправильное использование синглтонов:
Обратите внимание на использование синглтонов и проверьте, что они не удерживают ссылки на объекты, которые должны быть собраны сборщиком мусора.
Утечки в сессиях:
Убедитесь, что объекты сессий не сохраняются дольше, чем это необходимо.
Неосвобождаемые ресурсы:
Проверьте, что все внешние ресурсы (файлы, сетевые соединения и т.д.) закрываются корректно.
Пример использования JVisualVM для выявления утечек памяти:
Запуск Spring Boot приложения:
Запустите ваше Spring Boot приложение.
Подключение JVisualVM:
Откройте JVisualVM из JDK.
Подключитесь к вашему JVM процессу, который выполняет Spring Boot приложение.
Сбор данных о памяти:
Перейдите на вкладку Monitor для наблюдения за общим использованием памяти.
Перейдите на вкладку Sampler и начните профилирование памяти.
Создание и анализ дампа памяти:
Перейдите на вкладку Heap Dump и создайте дамп памяти.
Проанализируйте дамп памяти на предмет большого количества объектов одного типа или объектов, которые не должны быть в памяти.
➤ Что такое RouterFunction и RequestPredicate в Spring WebFlux
RouterFunction это концепция в Spring WebFlux, которая предоставляет функциональный способ определения маршрутов для обработки HTTP-запросов.
Вместо аннотаций, таких как @RequestMapping, используется функциональный подход для конфигурирования маршрутов. Это позволяет создавать маршруты более декларативно и гибко.
Основные особенности RouterFunction:
Функциональный стиль:
Использует функциональные интерфейсы для определения маршрутов и обработчиков запросов.
Декларативность:
Маршруты и обработчики определяются в коде, что позволяет легче управлять маршрутизацией.
Асинхронность:
Полностью поддерживает асинхронное и неблокирующее программирование, что делает его подходящим для высоконагруженных приложений.
RequestPredicate:
интерфейс в Spring WebFlux, который используется для проверки соответствия входящего HTTP-запроса определенным условиям. Он играет ключевую роль в функциональном программировании маршрутизации в WebFlux, помогая определить, какие обработчики должны обрабатывать конкретные запросы на основе различных критериев, таких как путь, HTTP-метод, заголовки и параметры запроса.
Основные особенности RequestPredicate:
Проверка условий запроса:
Позволяет проверять соответствие запросов определенным условиям, таким как пути, методы, заголовки и параметры запроса.
Сочетание условий:
Можно комбинировать несколько условий с помощью методов and(), or() и negate() для создания сложных предикатов.
Использование в маршрутизации:
Используется вместе с RouterFunction для определения маршрутов в функциональном стиле.
andRoute:
используется для цепочки нескольких маршрутов в функциональном стиле маршрутизации. Это позволяет объединять несколько маршрутов вместе, чтобы они могли обрабатываться одной конфигурацией маршрутизации.
dependencies { implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.ServerResponse import reactor.core.publisher.Mono @Component class GreetingHandler { fun hello(request: ServerRequest): Mono<ServerResponse> { return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .bodyValue("Hello, Spring WebFlux!") } fun index(request: ServerRequest): Mono<ServerResponse> { return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .bodyValue("Welcome to the home page!") } }
import com.example.handler.GreetingHandler import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.MediaType import org.springframework.web.reactive.function.server.RequestPredicate import org.springframework.web.reactive.function.server.RequestPredicates.* import org.springframework.web.reactive.function.server.RouterFunction import org.springframework.web.reactive.function.server.RouterFunctions.route import org.springframework.web.reactive.function.server.ServerResponse @Configuration class GreetingRouter { @Bean fun route(greetingHandler: GreetingHandler): RouterFunction<ServerResponse> { val helloRoute: RequestPredicate = GET("/hello") .and(accept(MediaType.TEXT_PLAIN)) return route(helloRoute, greetingHandler::hello) .andRoute(GET("/"), greetingHandler::index) } }
или
import org.springframework.http.MediaType import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import reactor.core.publisher.Mono @RestController @RequestMapping(produces = [MediaType.TEXT_PLAIN_VALUE]) class GreetingHandler { @GetMapping("/hello") fun hello(): Mono<String> { return Mono.just("Hello, Spring WebFlux!") } @GetMapping("/") fun index(): Mono<String> { return Mono.just("Welcome to the home page!") } }
еще примеры:
data class User( val id: Long, val name: String, val email: String )
import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Mono @Service class UserService { private val users = listOf( User(1, "John Doe", "[email protected]"), User(2, "Jane Doe", "[email protected]") ) fun getAllUsers(): Flux<User> { return Flux.fromIterable(users) } fun getUserById(id: Long): Mono<User> { return Mono.justOrEmpty(users.find { it.id == id }) } }
import org.springframework.stereotype.Component import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.ServerResponse import reactor.core.publisher.Mono @Component class UserHandler(private val userService: UserService) { fun getAllUsers(request: ServerRequest): Mono<ServerResponse> { return ServerResponse.ok().body(userService.getAllUsers(), User::class.java) } fun getUserById(request: ServerRequest): Mono<ServerResponse> { val userId = request.pathVariable("id").toLong() return userService.getUserById(userId) .flatMap { user -> ServerResponse.ok().bodyValue(user) } .switchIfEmpty(ServerResponse.notFound().build()) } fun getUsersByHeader(request: ServerRequest): Mono<ServerResponse> { val headerValue = request.headers().firstHeader("X-USER-ROLE") ?: return ServerResponse.badRequest().build() return ServerResponse.ok().body(userService.getUsersByRole(headerValue), User::class.java) } }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.reactive.function.server.RequestPredicates.* import org.springframework.web.reactive.function.server.RouterFunction import org.springframework.web.reactive.function.server.RouterFunctions.route import org.springframework.web.reactive.function.server.ServerResponse @Configuration class RouterConfig { @Bean fun userRoutes(userHandler: UserHandler): RouterFunction<ServerResponse> { return route() .GET("/users", userHandler::getAllUsers) .GET("/users/{id}", userHandler::getUserById) .GET("/users/role", headers("X-USER-ROLE"), userHandler::getUsersByHeader) .build() } }
RequestPredicate:
Интерфейс, определяющий условие, которому должен соответствовать запрос. Используется для создания условий маршрутизации.
Примеры предикатов:
GET(“/users”):
Проверяет, что запрос использует метод GET и путь “/users”.
GET(“/users/{id}”):
Проверяет, что запрос использует метод GET и путь “/users/{id}”.
headers(“X-USER-ROLE”):
Проверяет, что запрос содержит заголовок “X-USER-ROLE”.
Комбинирование предикатов:
Предикаты можно комбинировать с помощью методов and(), or() и negate(). Например, можно создать сложное условие, проверяющее и путь, и наличие заголовка, и метод HTTP.
➤ Как в Spring сделать фоновую задачу по таймеру
Для выполнения фоновых задач по таймеру в Spring можно использовать аннотацию @Scheduled, которая позволяет вам планировать выполнение методов с заданной периодичностью.
Включение поддержки задач по расписанию:
Для начала нужно включить поддержку аннотации @Scheduled в вашем приложении. Это делается с помощью аннотации @EnableScheduling в одном из конфигурационных классов или в основном классе приложения.
import org.springframework.context.annotation.Configuration import org.springframework.scheduling.annotation.EnableScheduling @Configuration @EnableScheduling class SchedulerConfig
Создайте сервис для фоновых задач:
Создайте сервис, в котором будут находиться методы, выполняющиеся по расписанию. Эти методы нужно аннотировать @Scheduled.
import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import java.time.LocalDateTime @Service class ScheduledTasks { @Scheduled(fixedRate = 5000) fun reportCurrentTime() { println("Текущее время: ${LocalDateTime.now()}") } }
Параметры аннотации @Scheduled:
fixedRate:
Определяет фиксированный интервал между запусками метода, в миллисекундах. Например, fixedRate = 300000 означает, что метод будет выполняться каждые 5 минут (300000 миллисекунд).
fixedDelay:
Определяет интервал между завершением последнего запуска метода и началом следующего. Например, fixedDelay = 300000 также запустит метод каждые 5 минут, но только после завершения предыдущего запуска.
@Scheduled(fixedDelay = 5000) fun taskWithFixedDelay() { println("Задача с fixedDelay выполняется: ${LocalDateTime.now()}") }
initialDelay:
Определяет задержку перед первым запуском метода, в миллисекундах. Например, initialDelay = 10000 означает, что метод будет выполнен через 10 секунд после запуска приложения.
@Scheduled(initialDelay = 10000, fixedRate = 5000) fun taskWithInitialDelay() { println("Задача с initialDelay выполняется: ${LocalDateTime.now()}") }
cron:
Позволяет задать расписание в формате cron. Например, cron = «0 0 * * * *» запустит метод каждый час.
Пример использования cron:
Этот метод будет выполняться ежедневно в 00:00.
@Scheduled(cron = "0 0/1 * 1/1 * ?") fun taskWithCronExpression() { println("Задача с cron выражением выполняется каждую минуту: ${LocalDateTime.now()}") }
Настройка пула потоков:
По умолчанию, задачи выполняются в одном потоке, что может быть ограничением, если у вас несколько долгих задач. Чтобы использовать пул потоков, можно настроить TaskScheduler.
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler @Configuration class SchedulerConfig { @Bean fun taskScheduler(): ThreadPoolTaskScheduler { val scheduler = ThreadPoolTaskScheduler() scheduler.poolSize = 10 scheduler.setThreadNamePrefix("scheduled-task-pool-") return scheduler } }
Теперь задачи будут выполняться в отдельном пуле потоков, что позволяет запускать несколько задач параллельно.
➤ Как в Spring импортировать данные из CSV
Для импорта CSV-файлов в Spring можно использовать различные библиотеки, такие как OpenCSV или Apache Commons CSV. Вот пример использования OpenCSV для импорта данных из CSV-файла в Spring-приложении.
dependencies { implementation 'com.opencsv:opencsv:5.7.1' }
data class Person( val id: Long, val name: String, val age: Int, val email: String )
import com.opencsv.bean.CsvToBeanBuilder import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile import java.io.InputStreamReader @Service class CsvService { fun importCsv(file: MultipartFile): List<Person> { val reader = InputStreamReader(file.inputStream) val csvToBean = CsvToBeanBuilder<Person>(reader) .withType(Person::class.java) .withIgnoreLeadingWhiteSpace(true) .build() return csvToBean.parse() } }
import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile @RestController class CsvController(private val csvService: CsvService) { @PostMapping("/import") fun importCsv(@RequestParam("file") file: MultipartFile): ResponseEntity<List<Person>> { return try { val people = csvService.importCsv(file) ResponseEntity(people, HttpStatus.OK) } catch (e: Exception) { ResponseEntity(HttpStatus.BAD_REQUEST) } } }
Теперь вы можете протестировать ваш API, отправив POST-запрос на /import с загруженным CSV-файлом.
Пример CSV-файла
id,name,age,email 1,John Doe,30,[email protected] 2,Jane Smith,25,[email protected]