---
title: "Java Spring — Компоненти Spring"
url: "https://romankryvolapov.com/uk/java-spring-components/"
description: "Налаштування application.properties, WebSocket, GraphQL, OAuth 2.0, Spring Security, Actuator, WebFlux і Batch з прикладами коду та конфігурації."
language: uk
updated: 2026-01-21
---
## Які можуть бути параметри в application.properties

**Конфігурація бази даних:**

```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 консолі:**

```properties
# Увімкнення консолі H2
spring.h2.console.enabled=true
# Шлях доступу до консолі H2
spring.h2.console.path=/h2-console
```

**Конфігурація сервера:**

```properties
# Порт, на якому запускається сервер
server.port=8080
# Кодування за замовчуванням
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
```

**Конфігурація безпеки:**

```properties
# Шлях до сторінки логіна
spring.security.user.name=user
# Пароль для сторінки логіна
spring.security.user.password=secret
# Роль користувача
spring.security.user.roles=USER
```

**Конфігурація кешування:**

```properties
# Увімкнення кешування
spring.cache.type=simple
```

**Конфігурація логування:**

```properties
# Рівень логування для застосунку
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
```

**Конфігурація інтернаціоналізації:**

```properties
# Локаль за замовчуванням
spring.mvc.locale=ru_RU
# Увімкнення автоматичного визначення локалі
spring.mvc.locale-resolver=accept-header
```

**Конфігурація надсилання електронної пошти:**

```properties
# SMTP-сервер
spring.mail.host=smtp.example.com
# Порт SMTP
spring.mail.port=587
# Ім'я користувача для SMTP
spring.mail.username=myemail@example.com
# Пароль для SMTP
spring.mail.password=password
# Протокол
spring.mail.protocol=smtp
# Шлях до шаблонів листів
spring.mail.templates.path=classpath:/templates/
```

**Конфігурація параметрів застосунку:**

```properties
# Приклад користувацького параметра
myapp.custom-property=value
```

## Як використовувати WebSocket у Spring

Додайте залежності для Spring WebSocket у ваш build.gradle.kts (якщо ви використовуєте Kotlin DSL для Gradle):

```groovy
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-websocket")
    implementation("org.springframework.boot:spring-boot-starter-web")
}
```

Конфігурація WebSocket

```kotlin
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

```kotlin
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

Додавання залежностей

```kotlin
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.

```kotlin
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!
}
```

```kotlin
data class User(
    val id: Long,
    val name: String,
    val email: String
)
```

```kotlin
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**

```kotlin
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()
    }
}
```

```kotlin
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

```kotlin
query {
    getUser(id: 1) {
        id
        name
        email
    }
}
```

Запит на отримання всіх користувачів

```kotlin
query {
    getAllUsers {
        id
        name
        email
    }
}
```

Мутація для створення нового користувача

```kotlin
mutation {
    createUser(input: { name: "John Doe", email: "john.doe@example.com" }) {
        id
        name
        email
    }
}
```

## Як використовувати OAuth 2.0 у Spring

Налаштування OAuth 2.0 у Spring Boot дозволяє вашому застосунку безпечно взаємодіяти зі сторонніми сервісами, такими як Google, Facebook та іншими провайдерами OAuth. У цьому прикладі я покажу, як налаштувати Spring Boot застосунок для автентифікації користувачів через OAuth 2.0 з використанням провайдера Google.

```groovy
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'
}
```

```properties
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
```

```kotlin
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"
    }
}
```

```kotlin
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:**

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}
```

```yaml
rest:
  url: https://api.example.com/data
```

```kotlin
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()
    }
}
```

```kotlin
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)
    }
}
```

```kotlin
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:**

```kotlin
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
```

```yaml
webclient:
  base-url: https://api.example.com
```

```kotlin
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()
    }
}
```

```kotlin
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()
    }
}
```

```kotlin
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:**

```kotlin
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
```

```yaml
feign:
  client:
    config:
      externalApiClient:
        url: https://api.example.com
```

```kotlin
import org.springframework.cloud.openfeign.EnableFeignClients
import org.springframework.context.annotation.Configuration

@Configuration
@EnableFeignClients
class FeignConfig
```

```kotlin
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
}
```

```kotlin
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:**

```kotlin
dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.13'
}
```

```kotlin
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:**

```kotlin
dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.9.0'
}
```

```kotlin
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:**

```kotlin
dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}
```

```kotlin
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:**

```kotlin
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:**

```kotlin
dependencies {
    implementation "org.http4k:http4k-core:4.9.9.0"
    implementation "org.http4k:http4k-client-okhttp:4.9.9.0"
}
```

```kotlin
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:**

```kotlin
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"
}
```

```kotlin
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 — це хеш-функція, спеціально розроблена для хешування паролів. Вона забезпечує надійний захист від атак, таких як атаки за словником і атаки перебором.

```kotlin
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
}
```

```kotlin
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) — це бібліотека, яка надає прості способи для шифрування та дешифрування даних.

```kotlin
dependencies {
    implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4")
}
```

```properties
jasypt.encryptor.password=yourEncryptionPassword
```

```kotlin
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
    }
}
```

```kotlin
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)
    }
}
```

```kotlin
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)
    }
}
```

## Як налаштувати HTTPS у Spring

Використання SSL/TLS-сертифікатів для забезпечення безпечного з'єднання у Spring Boot застосунку включає налаштування HTTPS. Для цього потрібно створити або отримати SSL-сертифікат, налаштувати сервер і оновити конфігурацію Spring Boot. Нижче наведено покроковий приклад налаштування HTTPS із використанням самопідписаного сертифіката.

**Генерація самопідписаного сертифіката:**\
Для генерації самопідписаного сертифіката можна використовувати keytool, який входить до складу JDK.

```shellscript
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.

```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.

```kotlin
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

```kotlin
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

```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:**\
Рівні логування та їх зміна.

**Приклади запитів:**\
Перевірка стану застосунку

```text
curl http://localhost:8080/actuator/health
```

Отримання інформації про застосунок

```text
curl http://localhost:8080/actuator/info
```

Отримання метрик

```text
curl http://localhost:8080/actuator/metrics
```

**Захист кінцевих точок Actuator:**\
Для захисту кінцевих точок Actuator можна використовувати Spring Security. Це дозволяє обмежити доступ до адміністративних функцій лише авторизованим користувачам.

**Приклад конфігурації безпеки:**\
У цьому прикладі доступ до кінцевих точок Actuator обмежений користувачами з роллю ADMIN

```kotlin
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

```kotlin
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")
}
```

```java
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)
    }
}
```

```java
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:

```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

```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.

```kotlin
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()
)
```

```kotlin
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
)
```

```kotlin
import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository<UserEntity, Long> {
    fun findByUsername(username: String): UserEntity?
}
```

```kotlin
import org.springframework.data.jpa.repository.JpaRepository

interface RoleRepository : JpaRepository<RoleEntity, Long> {
    fun findByName(name: String): RoleEntity?
}
```

```kotlin
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
    }
}
```

```kotlin
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)
    }
}
```

```kotlin
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)
    }
}
```

```kotlin
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 для створення користувача

```kotlin
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 для обмеження доступу до методів контролерів або сервісів на основі ролей. Ось приклади їх використання.

```kotlin
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"
    }
}
```

```kotlin
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 зберігання користувачів — найпростіший спосіб для створення користувачів. Ви вже бачили приклад вище. Ось іще раз приклад для ясності:

```kotlin
@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. Приклад:

```kotlin
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")
}
```

```kotlin
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
    }
}
```

```properties
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:

```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.

```kotlin
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")
        }
    }
}
```

```kotlin
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 та їх обробки.

```kotlin
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")
}
```

```kotlin
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

```kotlin
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)
    }
}
```

```kotlin
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-файлу, їх обробки та запису в базу даних.

```kotlin
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")
}
```

```kotlin
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()
    }
}
```

```kotlin
data class Person(
    val firstName: String = "",
    val lastName: String = ""
)
```

CSV-файл (resources/people.csv)

```text
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 і зазвичай такі, що містять логіку налаштування бінів, засновану на присутності або відсутності певних класів чи бінів у контексті застосунку.

**Визначення автоконфігурації:**

```kotlin
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:**

```text
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfiguration.MyServiceAutoConfiguration
```

**Умови автоконфігурації:**\
Spring Boot надає безліч анотацій, які можна використовувати для керування тим, коли автоконфігурація має бути застосована:

**@ConditionalOnClass:**\
Автоконфігурація застосовується, якщо вказаний клас знаходиться в classpath.

**@ConditionalOnMissingBean:**\
Автоконфігурація застосовується, якщо бін відсутній у контексті.

**@ConditionalOnProperty:**\
Автоконфігурація застосовується, якщо певна властивість встановлена в application.properties або application.yml.

```kotlin
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.

```kotlin
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.

```properties
# application-dev.properties
app.base.url=https://dev.example.com
```

```properties
# application-test.properties
app.base.url=https://test.example.com
```

```properties
#application-prod.properties
app.base.url=https://prod.example.com
```

**Налаштування основного файлу конфігурації:**\
В основному файлі application.properties або application.yml можна визначити загальні налаштування та вказати активний профіль за замовчуванням (необов'язково).

```properties
# application.properties
spring.profiles.active=dev
```

**Читання конфігурації у вашому коді:**\
Використовуйте анотацію @Value або @ConfigurationProperties для отримання значення base URL із конфігураційних файлів.

```kotlin
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 для увімкнення або вимкнення бінів залежно від активного профілю.

```kotlin
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
    }
}
```

**Запуск застосунку із вказаним профілем:**\
Ви можете вказати активний профіль під час запуску застосунку через параметри командного рядка, змінні середовища або файл конфігурації.

Через параметри командного рядка

```text
$ java -jar myapp.jar --spring.profiles.active=prod
```

Через змінні середовища

```text
$ export SPRING_PROFILES_ACTIVE=prod
$ java -jar myapp.jar
```

## Що таке Spring WebFlux

Spring WebFlux — це реактивний веб-фреймворк, представлений у Spring 5, який дозволяє створювати асинхронні та неблокуючі веб-застосунки. WebFlux може працювати на різних реактивних рушіях, таких як Netty, Jetty або Tomcat.

```kotlin
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):**

```kotlin
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
)
```

```kotlin
import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository<User, Long>
```

```kotlin
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)
    }
}
```

```kotlin
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:**

```kotlin
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
)
```

```kotlin
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import reactor.core.publisher.Mono

interface UserRepository : ReactiveMongoRepository<User, String>
```

```kotlin
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)
    }
}
```

```kotlin
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 конфігурації

```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>
```

```kotlin
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

```kotlin
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!")
    }
}
```

```kotlin
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:**

```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>
```

```kotlin
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()}")
}
```

```kotlin
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()}")
}
```

**Приклад із використанням анотацій:**

```kotlin
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"
        }
    }
}
```

```kotlin
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
    }
}
```

```kotlin
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:**

```kotlin
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:**

```kotlin
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

```kotlin
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”.

Ще приклад:

```kotlin
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 надає потужні та гнучкі інструменти для валідації даних, включно з анотаціями, вбудованими валідаційними механізмами та підтримкою користувацьких валідаторів.

```kotlin
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")
}
```

```kotlin
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
)
```

```kotlin
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)
    }
}
```

**Обробка помилок валідації:**

```kotlin
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)
    }
}
```

Приклад запиту:

```json
POST /api/users/register
Content-Type: application/json
{
    "name": "",
    "email": "invalid-email",
    "password": "short"
}
```

Приклад відповіді:

```json
{
    "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, для моніторингу використання пам'яті.

```kotlin
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
}
```

```properties
management.endpoints.web.exposure.include=*
```

**Garbage Collection Logs:**\
Увімкніть логи збирача сміття у вашому застосунку, додавши такі параметри JVM:

```text
-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:**\
використовується для ланцюжка кількох маршрутів у функціональному стилі маршрутизації. Це дозволяє об'єднувати кілька маршрутів разом, щоб вони могли оброблятися однією конфігурацією маршрутизації.

```kotlin
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")
}
```

```kotlin
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!")
    }
}
```

```kotlin
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)
    }
}
```

або

```kotlin
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!")
    }
}
```

ще приклади:

```kotlin
data class User(
    val id: Long,
    val name: String,
    val email: String
)
```

```kotlin
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", "john@example.com"),
        User(2, "Jane Doe", "jane@example.com")
    )
    fun getAllUsers(): Flux<User> {
        return Flux.fromIterable(users)
    }
    fun getUserById(id: Long): Mono<User> {
        return Mono.justOrEmpty(users.find { it.id == id })
    }
}
```

```kotlin
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)
    }
}
```

```kotlin
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 в одному з конфігураційних класів або в основному класі застосунку.

```kotlin
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.annotation.EnableScheduling

@Configuration
@EnableScheduling
class SchedulerConfig
```

**Створіть сервіс для фонових завдань:**\
Створіть сервіс, у якому будуть знаходитися методи, що виконуються за розкладом. Ці методи потрібно анотувати @Scheduled.

```kotlin
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 хвилин, але лише після завершення попереднього запуску.

```kotlin
@Scheduled(fixedDelay = 5000)
fun taskWithFixedDelay() {
    println("Завдання з fixedDelay виконується: ${LocalDateTime.now()}")
}
```

**initialDelay:** \
Визначає затримку перед першим запуском методу, у мілісекундах. Наприклад, initialDelay = 10000 означає, що метод буде виконано через 10 секунд після запуску застосунку.

```kotlin
@Scheduled(initialDelay = 10000, fixedRate = 5000)
fun taskWithInitialDelay() {
    println("Завдання з initialDelay виконується: ${LocalDateTime.now()}")
}
```

**cron:** \
Дозволяє задати розклад у форматі cron. Наприклад, cron = "0 0 \* \* \* \*" запустить метод щогодини.

**Приклад використання cron:**\
Цей метод виконуватиметься щодня о 00:00.

```kotlin
@Scheduled(cron = "0 0/1 * 1/1 * ?")
fun taskWithCronExpression() {
    println("Завдання з cron виразом виконується щохвилини: ${LocalDateTime.now()}")
}
```

**Налаштування пулу потоків:**\
За замовчуванням завдання виконуються в одному потоці, що може бути обмеженням, якщо у вас кілька довгих завдань. Щоб використовувати пул потоків, можна налаштувати TaskScheduler.

```kotlin
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-застосунку.

```text
dependencies {
    implementation 'com.opencsv:opencsv:5.7.1'
}
```

```kotlin
data class Person(
    val id: Long,
    val name: String,
    val age: Int,
    val email: String
)
```

```kotlin
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()
    }
}
```

```kotlin
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-файлу

```text
id,name,age,email
1,John Doe,30,johndoe@example.com
2,Jane Smith,25,janesmith@example.com
```

[Copyright: Roman Kryvolapov](https://t.me/RomanKryvolapov)
