---
title: "Java Spring – Компоненти на Spring"
url: "https://romankryvolapov.com/bg/java-spring-components/"
description: "Настройка на application.properties, WebSocket, GraphQL, OAuth 2.0, Spring Security, Actuator, WebFlux и Batch с примери за код и конфигурация."
language: bg
published: 2024-07-30
updated: 2026-11-13
---
## Какви параметри може да има в 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):

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

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

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

Заявка за получаване на всички потребители

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

Мутация за създаване на нов потребител

```graphql
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.\
**Типична употреба:** Legacy проекти и прости 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:**\
Все още се използва в legacy проекти, но употребата му намалява.

**WebClient**:\
Все по-популярен избор за нови проекти, особено за тези, които изискват асинхронност и реактивност.

**FeignClient:**\
Често се използва в микроуслужни архитектури, интегрирани със Spring Cloud.

**Apache HttpClient и OkHttp:**\
Използват се за нисконивов контрол и в специфични задачи.

**Retrofit:**\
Популярен в Android разработката.

**HTTP4K и Ktor:**\
По-рядко срещани, но популярни сред Kotlin разработчиците и сред тези, които предпочитат функционален и модулен подход.

**RestTemplate:**

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

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

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

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

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

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

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

```groovy
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:**\
Нива на логване и промяната им.

**Примери за заявки:**\
Проверка на състоянието на приложението

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

Получаване на информация за приложението

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

Получаване на метрики

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

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

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

```csv
John,Doe
Jane,Doe
```

## Какво е AutoConfiguration в Spring

Автоконфигурацията (Auto-configuration) в Spring Boot е механизъм, който автоматично конфигурира вашето Spring приложение въз основа на зависимостите, намерени в classpath, и различни условия. Това е една от ключовите особености на Spring Boot, която позволява да се сведе до минимум количеството конфигурации, необходими за стартиране на приложението.

**Как работи автоконфигурацията:**\
Spring Boot анализира класовете в classpath на вашия проект и автоматично създава и настройва необходимите бийнове за вашето приложение. Този процес се управлява от анотацията @EnableAutoConfiguration, която обикновено е включена в анотацията @SpringBootApplication.

**Примери за работа на автоконфигурацията:**

**Конфигурация на база данни:**\
Ако във вашия classpath има H2, HSQLDB или друга поддържана база данни, Spring Boot автоматично ще създаде бийновете за DataSource, EntityManager и ще ги настрои.

**Конфигурация на уеб сървър:**\
Ако имате зависимости от spring-boot-starter-web, Spring Boot автоматично ще настрои уеб сървър (например Tomcat) и ще го свърже с вашето приложение.

**Основни файлове и класове на автоконфигурацията:**

**META-INF/spring.factories:**\
Този файл сочи към класовете на автоконфигурацията, които трябва да бъдат заредени. Намира се в JAR файловете с автоконфигурация.

**Класове на автоконфигурацията:**\
Класове, маркирани с анотацията @Configuration, които обикновено съдържат логиката за настройка на бийновете, основана на наличието или отсъствието на определени класове или бийнове в контекста на приложението.

**Определяне на автоконфигурация:**

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

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

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

**@ConditionalOnClass:**\
Автоконфигурацията се прилага, ако посоченият клас се намира в classpath.

**@ConditionalOnMissingBean:**\
Автоконфигурацията се прилага, ако бийнът липсва в контекста.

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

```java
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>) {
    runApplication<MyApplication>(*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
    }
}
```

**Стартиране на приложението с посочен профил:**\
Можете да посочите активния профил при стартиране на приложението чрез параметри на командния ред, променливи на средата или конфигурационен файл.

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

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

Чрез променливи на средата

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

## Какво е Spring WebFlux

Spring WebFlux е реактивен уеб фреймуърк, представен в Spring 5, който позволява създаването на асинхронни и неблокиращи уеб приложения. WebFlux може да работи върху различни реактивни двигатели, като Netty, Jetty или Tomcat.

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

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

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

Пример за заявка:

```http languages="HTTP, 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, за да създадете дъмп на паметта.\
Анализирайте броя на обектите и техните retained размери.

**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 приложения са

**Кеширани обекти, които не се премахват:**\
Уверете се, че кешовете са настроени правилно и обектите се премахват от кеша при необходимост.

**Неправилно използване на синглтони:**\
Обърнете внимание на използването на синглтони и проверете дали не държат референции към обекти, които трябва да бъдат събрани от garbage collector.

**Изтичане в сесиите:**\
Уверете се, че обектите на сесиите не се запазват по-дълго, отколкото е необходимо.

**Неосвободени ресурси:**\
Проверете дали всички външни ресурси (файлове, мрежови връзки и т.н.) се затварят коректно.

**Пример за използване на 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:**\
Този метод ще се изпълнява всяка минута.

```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 приложение.

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

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