Перейти к содержимому

Вопросы и ответы — Java / Spring Developer — Компоненты Spring

➤ Какие могут быть параметры в application.properties
➤ Как использовать WebSocket в Spring
➤ Как использовать GraphQL в Spring
➤ Как использовать OAuth 2.0 в Spring
➤ Как настроить шифрование в spring
➤ Как в Spring настроить HTTPS
➤ Что такое Spring Boot Actuator
➤ Как настроить авторизацию при помощи Spring Security
➤ Что такое Spring Integration
➤ Что такое Spring Batch
➤ Что такое AutoConfiguration в Spring
➤ Как настроить разные профили сборки в Spring
➤ Что такое Spring WebFlux
➤ Что такое ApplicationContext в Spring
➤ Что такое BeanFactory
➤ Что такое Spring BOM
➤ Как настроить безопасность в WebFlux
➤ Как настроить валидацию данных в Spring
➤ Как определять утечки памяти в Spring
➤ Что такое RouterFunction и RequestPredicate
➤ Как Spring приложение может отправлять и принимать данные с других API
➤ Как работает RPC (Remote Procedure Calls)
➤ Как происходит тестирование в Spring
➤ Как в Spring сделать фоновую задачу по таймеру
➤ Как в Spring импортировать данные из CSV

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

# URL подключения к базе данных
spring.datasource.url=jdbc:h2:mem:testdb
# Драйвер базы данных
spring.datasource.driverClassName=org.h2.Driver
# Имя пользователя базы данных
spring.datasource.username=sa
# Пароль базы данных
spring.datasource.password=password
# Платформа базы данных (используется Hibernate)
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# Автоматическое создание и обновление схемы базы данных
spring.jpa.hibernate.ddl-auto=update

Конфигурация H2 консоли:

# Включение консоли H2
spring.h2.console.enabled=true
# Путь доступа к консоли H2
spring.h2.console.path=/h2-console

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

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

Конфигурация безопасности:

# Путь к странице логина
spring.security.user.name=user
# Пароль для страницы логина
spring.security.user.password=secret
# Роль пользователя
spring.security.user.roles=USER

Конфигурация кэширования:

# Включение кэширования
spring.cache.type=simple

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

# Уровень логирования для приложения
logging.level.root=INFO
logging.level.org.springframework.web=DEBUG
# Путь к файлу логов
logging.file.name=logs/spring-boot-application.log
# Формат вывода логов
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} - %msg%n

Конфигурация интернационализации:

# Локаль по умолчанию
spring.mvc.locale=ru_RU
# Включение автоматического определения локали
spring.mvc.locale-resolver=accept-header

Конфигурация отправки электронной почты:

# SMTP-сервер
spring.mail.host=smtp.example.com
# Порт SMTP
spring.mail.port=587
# Имя пользователя для SMTP
[email protected]
# Пароль для SMTP
spring.mail.password=password
# Протокол
spring.mail.protocol=smtp
# Путь к шаблонам писем
spring.mail.templates.path=classpath:/templates/

Конфигурация параметров приложения:

# Пример пользовательского параметра
myapp.custom-property=value

Добавьте зависимости для Spring WebSocket в ваш build.gradle.kts (если вы используете Kotlin DSL для Gradle):

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

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

import org.springframework.context.annotation.Configuration
import org.springframework.web.socket.config.annotation.EnableWebSocket
import org.springframework.web.socket.config.annotation.WebSocketConfigurer
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry

@Configuration
@EnableWebSocket
class WebSocketConfig(private val webSocketHandler: MyWebSocketHandler) : WebSocketConfigurer {
    override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
        registry.addHandler(webSocketHandler, "/ws").setAllowedOrigins("*")
    }
}

Реализация обработчика WebSocket

import org.springframework.stereotype.Component
import org.springframework.web.socket.TextMessage
import org.springframework.web.socket.WebSocketSession
import org.springframework.web.socket.handler.TextWebSocketHandler

@Component
class MyWebSocketHandler : TextWebSocketHandler() {
    override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
        val payload = message.payload
        println("Received: $payload")
        val response = TextMessage("Server received: $payload")
        session.sendMessage(response)
    }
}

Добавление зависимостей

dependencies {
    implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:11.1.0")
    implementation("com.graphql-java-kickstart:graphiql-spring-boot-starter:11.1.0")
    implementation("com.graphql-java-kickstart:altair-spring-boot-starter:11.1.0")
    implementation("com.graphql-java-kickstart:voyager-spring-boot-starter:11.1.0")
    implementation("com.graphql-java-kickstart:graphql-java-tools:11.1.0")
}

Определение схемы GraphQL:
Создайте файл schema.graphqls в папке src/main/resources.

type Query {
    getUser(id: ID!): User
    getAllUsers: [User]
}

type Mutation {
    createUser(input: CreateUserInput): User
}

type User {
    id: ID!
    name: String!
    email: String!
}

input CreateUserInput {
    name: String!
    email: String!
}
data class User(
    val id: Long,
    val name: String,
    val email: String
)
import org.springframework.stereotype.Repository

@Repository
class UserRepository {
    private val users = mutableListOf<User>()
    private var idCounter = 1L
    fun getUserById(id: Long): User? {
        return users.find { it.id == id }
    }
    fun getAllUsers(): List<User> {
        return users
    }
    fun createUser(name: String, email: String): User {
        val user = User(id = idCounter++, name = name, email = email)
        users.add(user)
        return user
    }
}

GraphQL Resolvers

import com.coxautodev.graphql.tools.GraphQLQueryResolver
import org.springframework.stereotype.Component

@Component
class UserQueryResolver(private val userRepository: UserRepository) : GraphQLQueryResolver {
    fun getUser(id: Long): User? {
        return userRepository.getUserById(id)
    }
    fun getAllUsers(): List<User> {
        return userRepository.getAllUsers()
    }
}
import com.coxautodev.graphql.tools.GraphQLMutationResolver
import org.springframework.stereotype.Component

data class CreateUserInput(val name: String, val email: String)

@Component
class UserMutationResolver(private val userRepository: UserRepository) : GraphQLMutationResolver {
    fun createUser(input: CreateUserInput): User {
        return userRepository.createUser(input.name, input.email)
    }
}

Примеры запросов и мутаций

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

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

Запрос на получение всех пользователей

query {
    getAllUsers {
        id
        name
        email
    }
}

Мутация для создания нового пользователя

mutation {
    createUser(input: { name: "John Doe", email: "[email protected]" }) {
        id
        name
        email
    }
}

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

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
    implementation 'org.jetbrains.kotlin:kotlin-reflect'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
spring.security.oauth2.client.registration.google.client-id=YOUR_CLIENT_ID
spring.security.oauth2.client.registration.google.client-secret=YOUR_CLIENT_SECRET
spring.security.oauth2.client.registration.google.scope=profile,email
spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.google.client-name=Google
spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/auth
spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token
spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo
spring.security.oauth2.client.provider.google.user-name-attribute=sub
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class HomeController {
    @GetMapping("/")
    fun home(model: Model, @AuthenticationPrincipal principal: OidcUser?): String {
        if (principal != null) {
            model.addAttribute("name", principal.name)
            model.addAttribute("email", principal.email)
        }
        return "home"
    }
    @GetMapping("/login")
    fun login(): String {
        return "login"
    }
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests { requests ->
                requests.antMatchers("/", "/login").permitAll()
                    .anyRequest().authenticated()
            }.oauth2Login { oauth2Login ->
                oauth2Login.loginPage("/login")
                    .defaultSuccessURL("/")
            }
        return http.build()
    }
}

Для этого могут использоваться RestTemplate, WebClient, FeignClient, Apache HttpClient, OkHttp, Retrofit, HTTP4K, Ktor

RestTemplate:
Природа: Синхронный.
Использование: Простой, блокирующий HTTP клиент.
Статус: В режиме поддержки; рекомендуется переходить на WebClient.
Типичное использование: Легаси проекты и простые HTTP вызовы.
Преимущества: Легкость использования, знаком многим разработчикам.

WebClient:
Природа: Асинхронный и неблокирующий.
Использование: Часть Spring WebFlux, поддерживает реактивное программирование.
Статус: Современный клиент для HTTP запросов.
Типичное использование: Высоконагруженные системы и приложения, требующие высокой производительности.
Преимущества: Поддержка асинхронных и синхронных запросов, потоков данных.

FeignClient:
Природа: Синхронный и асинхронный.
Использование: Высокоуровневый клиент для взаимодействия с внешними API.
Статус: Интегрируется с Spring Cloud, используется для микросервисов.
Типичное использование: Микросервисные архитектуры.
Преимущества: Автоматическое создание клиентов, высокая абстракция.

Apache HttpClient:
Природа: Синхронный и асинхронный.
Использование: Низкоуровневый HTTP клиент с множеством конфигураций.
Статус: Широко используемый, мощный и гибкий.
Типичное использование: Случаи, когда требуется низкоуровневый контроль над HTTP запросами.
Преимущества: Гибкость и мощные возможности настройки.

OkHttp:
Природа: Синхронный и асинхронный.
Использование: Современный HTTP клиент с простым API.
Статус: Часто используется в сочетании с Retrofit.
Типичное использование: Android-разработка и JVM-базированные приложения.
Преимущества: Простой и чистый API.

Retrofit:
Природа: Синхронный и асинхронный.
Использование: Высокоуровневый клиент для работы с REST API, интегрируется с OkHttp.
Статус: Популярный выбор для Android и микросервисных архитектур.
Типичное использование: Android и микросервисы.
Преимущества: Автоматическое создание клиентов, поддержка различных конвертеров.

HTTP4K:
Природа: Асинхронный.
Использование: Функциональная библиотека для создания HTTP клиентов и серверов.
Статус: Легковесная и гибкая библиотека.
Типичное использование: Проекты, требующие функционального программирования.
Преимущества: Функциональный подход, простота и гибкость.

Ktor:
Природа: Асинхронный.
Использование: Фреймворк от JetBrains для создания серверных и клиентских приложений на Kotlin.
Статус: Модульная и легко настраиваемая библиотека.
Типичное использование: Высокопроизводительные асинхронные приложения.
Преимущества: Отличная интеграция с Kotlin, высокая производительность.

Сравнительный анализ использования:

RestTemplate:
Все еще используется в легаси проектах, но его использование сокращается.

WebClient:
Все более популярный выбор для новых проектов, особенно тех, которые требуют асинхронности и реактивности.

FeignClient:
Часто используется в микросервисных архитектурах, интегрированных с Spring Cloud.

Apache HttpClient и OkHttp:
Используются для низкоуровневого контроля и в специфичных задачах.

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

HTTP4K и Ktor:
Менее распространены, но популярны среди Kotlin-разработчиков и тех, кто предпочитает функциональный и модульный подходы.

RestTemplate:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}
rest:
  url: https://api.example.com/data
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.client.RestTemplate

@Configuration
class AppConfig {
    @Bean
    fun restTemplate(): RestTemplate {
        return RestTemplate()
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class ApiService(@Autowired private val restTemplate: RestTemplate) {
    fun getDataFromExternalApi(url: String): String? {
        return restTemplate.getForObject(url, String::class.java)
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpEntity
import org.springframework.http.HttpMethod
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

data class RequestObject(val name: String, val value: String)
data class ResponseObject(val id: Long, val status: String)

@Service
class ApiService(@Autowired private val restTemplate: RestTemplate) {
    fun sendAndReceiveObject(url: String, requestObject: RequestObject): ResponseObject? {
        val requestEntity = HttpEntity(requestObject)
        val responseEntity: ResponseEntity<ResponseObject> = restTemplate.exchange(
            url,
            HttpMethod.POST,
            requestEntity,
            ResponseObject::class.java
        )
        return responseEntity.body
    }
}

WebClient:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
webclient:
  base-url: https://api.example.com
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.client.WebClient

@Configuration
class WebClientConfig {
    @Bean
    fun webClient(): WebClient {
        return WebClient.builder().build()
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient

@Service
class ApiService(@Autowired private val webClient: WebClient) {
    fun getDataFromExternalApi(url: String): String? {
        return webClient.get()
            .uri(url)
            .retrieve()
            .bodyToMono(String::class.java)
            .block()
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

data class RequestObject(val name: String, val value: String)
data class ResponseObject(val id: Long, val status: String)

@Service
class ApiService(@Autowired private val webClient: WebClient) {
    fun sendAndReceiveObject(url: String, requestObject: RequestObject): ResponseObject? {
        return webClient.post()
            .uri(url)
            .body(Mono.just(requestObject), RequestObject::class.java)
            .retrieve()
            .bodyToMono(ResponseObject::class.java)
            .block()
    }
}

FeignClient:

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
feign:
  client:
    config:
      externalApiClient:
        url: https://api.example.com
import org.springframework.cloud.openfeign.EnableFeignClients
import org.springframework.context.annotation.Configuration

@Configuration
@EnableFeignClients
class FeignConfig
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.GetMapping

@FeignClient(name = "externalApiClient", url = "https://api.external.com")
interface ExternalApiClient {
    @GetMapping("/data")
    fun getData(): String
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class ApiService(@Autowired private val externalApiClient: ExternalApiClient) {
    fun getDataFromExternalApi(): String {
        return externalApiClient.getData()
    }
}

Apache HttpClient:

dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.13'
}
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils

fun sendHttpRequest(url: String): String? {
    val httpClient: CloseableHttpClient = HttpClients.createDefault()
    val httpGet = HttpGet(url)
    httpClient.execute(httpGet).use { response ->
        return EntityUtils.toString(response.entity)
    }
}

OkHttp:

dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.9.0'
}
import okhttp3.OkHttpClient
import okhttp3.Request

fun sendOkHttpRequest(url: String): String? {
    val client = OkHttpClient()
    val request = Request.Builder()
        .url(url)
        .build()
    client.newCall(request).execute().use { response ->
        return response.body?.string()
    }
}

Retrofit:

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST

data class RequestObject(val name: String, val value: String)
data class ResponseObject(val id: Long, val status: String)

interface ApiService {
    @POST("data")
    suspend fun sendData(@Body requestObject: RequestObject): ResponseObject
    @GET("data")
    suspend fun getData(): ResponseObject
}

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val apiService = retrofit.create(ApiService::class.java)

Spring WebClient with Reactor:

import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

val webClient = WebClient.create("https://api.example.com")

fun sendAndReceiveReactiveObject(requestObject: RequestObject): Mono<ResponseObject> {
    return webClient.post()
        .uri("/data")
        .body(Mono.just(requestObject), RequestObject::class.java)
        .retrieve()
        .bodyToMono(ResponseObject::class.java)
}

HTTP4K:

dependencies {
    implementation "org.http4k:http4k-core:4.9.9.0"
    implementation "org.http4k:http4k-client-okhttp:4.9.9.0"
}
import org.http4k.client.OkHttp
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.format.Gson.auto
import org.http4k.lens.Body

data class RequestObject(val name: String, val value: String)
data class ResponseObject(val id: Long, val status: String)

val client = OkHttp()

fun sendHttp4kRequest(url: String, requestObject: RequestObject): ResponseObject? {
    val requestLens = Body.auto<RequestObject>().toLens()
    val responseLens = Body.auto<ResponseObject>().toLens()
    val request = Request(Method.POST, url)
        .with(requestLens of requestObject)
    val response: Response = client(request)
    return responseLens.extract(response)
}

Ktor:

dependencies {
    implementation "io.ktor:ktor-client-core:1.5.2"
    implementation "io.ktor:ktor-client-cio:1.5.2"
    implementation "io.ktor:ktor-client-json:1.5.2"
    implementation "io.ktor:ktor-client-serialization:1.5.2"
}
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import kotlinx.serialization.Serializable

@Serializable
data class RequestObject(val name: String, val value: String)

@Serializable
data class ResponseObject(val id: Long, val status: String)

val client = HttpClient(CIO) {
    install(JsonFeature) {
        serializer = KotlinxSerializer()
    }
}

suspend fun sendKtorRequest(url: String, requestObject: RequestObject): ResponseObject {
    return client.post(url) {
        body = requestObject
    }
}

В Spring можно настроить шифрование данных с помощью различных библиотек и подходов. Одним из популярных способов является использование Spring Security Crypto для шифрования и дешифрования данных. В этом примере мы рассмотрим, как настроить шифрование паролей с помощью BCrypt, а также как шифровать данные с помощью Jasypt.

Шифрование паролей с помощью BCrypt:
BCrypt — это хэш-функция, специально разработанная для хеширования паролей. Она обеспечивает надежную защиту от атак, таких как атаки по словарю и атаки перебором.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }
    @Bean
    fun userDetailsService(passwordEncoder: PasswordEncoder): UserDetailsService {
        val user = User.withUsername("user")
            .password(passwordEncoder.encode("password"))
            .roles("USER")
            .build()
        return InMemoryUserDetailsManager(user)
    }
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .httpBasic()
        return http.build()
    }
}

Шифрование данных с помощью Jasypt:
Jasypt (Java Simplified Encryption) — это библиотека, которая предоставляет простые способы для шифрования и дешифрования данных.

dependencies {
    implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4")
}
jasypt.encryptor.password=yourEncryptionPassword
import org.jasypt.encryption.StringEncryptor
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor
import org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment
import java.util.*

@Configuration
class JasyptConfig {
    @Bean(name = ["jasyptStringEncryptor"])
    fun stringEncryptor(): StringEncryptor {
        val encryptor = StandardPBEStringEncryptor()
        encryptor.setPassword("yourEncryptionPassword")
        return encryptor
    }
    @Bean
    fun propertyPlaceholderConfigurer(environment: Environment): EncryptablePropertyPlaceholderConfigurer {
        val configurer = EncryptablePropertyPlaceholderConfigurer(stringEncryptor())
        configurer.setLocation(environment)
        return configurer
    }
}
import org.jasypt.util.text.AES256TextEncryptor
import org.springframework.stereotype.Service

@Service
class DataService {
    private val textEncryptor = AES256TextEncryptor().apply {
        setPassword("yourEncryptionPassword")
    }
    fun encryptData(data: String): String {
        return textEncryptor.encrypt(data)
    }
    fun decryptData(encryptedData: String): String {
        return textEncryptor.decrypt(encryptedData)
    }
}
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
class DataController(private val dataService: DataService) {
    @GetMapping("/encrypt")
    fun encrypt(@RequestParam data: String): String {
        return dataService.encryptData(data)
    }
    @GetMapping("/decrypt")
    fun decrypt(@RequestParam encryptedData: String): String {
        return dataService.decryptData(encryptedData)
    }
}

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

Генерация самозаверяющего сертификата:
Для генерации самозаверяющего сертификата можно использовать keytool, который входит в состав JDK.

keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650

myalias:
псевдоним ключа.

keystore.p12:
имя файла хранилища ключей.

validity 3650:
срок действия сертификата (в днях).

При выполнении этой команды вас попросят ввести различные данные, такие как пароль для хранилища ключей, ваше имя, организация и т.д. После этого будет создан файл keystore.p12.

Настройка Spring Boot для использования SSL:
Добавьте настройки SSL в файл application.properties.

server.port=8443
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=your_password
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=myalias

Перемещение файла keystore.p12 в ресурсную папку

Переместите файл keystore.p12 в папку src/main/resources вашего проекта, чтобы Spring Boot мог его найти.

Обновление конфигурации безопасности (если требуется)

Если ваше приложение использует Spring Security, вы можете настроить его для поддержки HTTPS.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests { requests ->
                requests.anyRequest().authenticated()
            }.formLogin { form ->
                form.loginPage("/login").permitAll()
            }.logout { logout ->
                logout.permitAll()
            }.requiresChannel { channel ->
                channel.anyRequest().requiresSecure()
            }
        return http.build()
    }
}

Spring Boot Actuator — это мощный инструмент в экосистеме Spring Boot, который предоставляет готовые функции для мониторинга и управления работающими приложениями. Actuator предоставляет набор конечных точек (endpoints), которые можно использовать для получения информации о приложении, выполнения операций управления и мониторинга его состояния.

Основные возможности Spring Boot Actuator:

Мониторинг состояния:
Предоставляет информацию о состоянии приложения, таких как метрики производительности, информация о базе данных, данные о системе и т.д.

Управление приложением:
Позволяет выполнять административные операции, такие как перезапуск приложения, получение информации о конфигурации и т.д.

Интеграция с инструментами мониторинга:
Легко интегрируется с инструментами мониторинга и оповещения, такими как Prometheus, Grafana и другие.

Включение Spring Boot Actuator:
Для начала работы с Actuator, необходимо добавить зависимость в ваш проект.
Пример проекта с использованием Gradle

plugins {
    id("org.springframework.boot") version "2.6.2"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.6.0"
    kotlin("plugin.spring") version "1.6.0"
}
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Настройка Spring Boot Actuator:
По умолчанию некоторые конечные точки Actuator включены, но доступ к ним ограничен. Можно настроить, какие конечные точки будут доступны, а также уровень их безопасности.
application.properties

# Включение всех конечных точек Actuator
management.endpoints.web.exposure.include=*
# Настройка пути к конечным точкам Actuator
management.endpoints.web.base-path=/actuator

Основные конечные точки Actuator:

/actuator/health:
Проверка состояния приложения.

/actuator/info:
Общая информация о приложении.

/actuator/metrics:
Метрики производительности приложения.

/actuator/env:
Информация о текущей конфигурации окружения.

/actuator/beans:
Список всех бинов Spring в контексте приложения.

/actuator/threaddump:
Снимок текущих потоков.

/actuator/loggers:
Уровни логирования и их изменение.

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

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

Получение информации о приложении

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

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

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

Защита конечных точек Actuator:
Для защиты конечных точек Actuator можно использовать Spring Security. Это позволяет ограничить доступ к административным функциям только авторизованным пользователям.

Пример конфигурации безопасности:
В этом примере доступ к конечным точкам Actuator ограничен пользователями с ролью ADMIN

import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
    override fun configure(http: HttpSecurity) {
        http.authorizeRequests()
            .antMatchers("/actuator/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .httpBasic()
    }
}
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeHttpRequests { authz ->
                authz
                    .requestMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
            }.formLogin { form ->
                form
                    .loginPage("/login")
                    .permitAll()
            }.logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }

    @Bean
    fun userDetailsService(): UserDetailsService {
        val user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build()

        val admin = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("admin")
            .roles("ADMIN")
            .build()
        return InMemoryUserDetailsManager(user, admin)
    }
}
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class HomeController {
    @GetMapping("/")
    fun home(model: Model): String {
        model.addAttribute("message", "Welcome to the Home Page!")
        return "home"
    }
    @GetMapping("/login")
    fun login(): String {
        return "login"
    }
}

src/main/resources/templates/home.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home</title>
</head>
<body>
    <h1 th:text="${message}">Welcome to the Home Page!</h1>
    <a href="/logout">Logout</a>
</body>
</html>

src/main/resources/templates/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form th:action="@{/login}" method="post">
        <div>
            <label>Username:</label>
            <input type="text" name="username"/>
        </div>
        <div>
            <label>Password:</label>
            <input type="password" name="password"/>
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

Пользователь:
Для создания пользовательского класса, наследующего UserDetails, нужно создать класс, который будет реализовывать интерфейс UserDetails. Этот класс будет использоваться для представления пользователя в системе безопасности Spring.

import javax.persistence.*

@Entity
data class UserEntity(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val username: String,
    val password: String,
    val enabled: Boolean = true,
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "users_roles",
        joinColumns = [JoinColumn(name = "user_id")],
        inverseJoinColumns = [JoinColumn(name = "role_id")]
    )
    val roles: Set<RoleEntity> = HashSet()
)
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id

@Entity
data class RoleEntity(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val name: String
)
import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository<UserEntity, Long> {
    fun findByUsername(username: String): UserEntity?
}
import org.springframework.data.jpa.repository.JpaRepository

interface RoleRepository : JpaRepository<RoleEntity, Long> {
    fun findByName(name: String): RoleEntity?
}
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

class CustomUserDetails(private val user: UserEntity) : UserDetails {
    override fun getAuthorities(): Collection<GrantedAuthority> {
        return user.roles.map { SimpleGrantedAuthority(it.name) }
    }
    override fun getPassword(): String {
        return user.password
    }
    override fun getUsername(): String {
        return user.username
    }
    override fun isAccountNonExpired(): Boolean {
        return true
    }
    override fun isAccountNonLocked(): Boolean {
        return true
    }
    override fun isCredentialsNonExpired(): Boolean {
        return true
    }
    override fun isEnabled(): Boolean {
        return user.enabled
    }
}
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service

@Service
class CustomUserDetailsService(private val userRepository: UserRepository) : UserDetailsService {
    override fun loadUserByUsername(username: String): UserDetails {
        val user = userRepository.findByUsername(username)
            ?: throw UsernameNotFoundException("User not found with username: $username")
        return CustomUserDetails(user)
    }
}
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional
class UserService(
    private val userRepository: UserRepository,
    private val roleRepository: RoleRepository
) {
    fun createUser(username: String, password: String, roles: Set<String>): UserEntity {
        val roleEntities = roles.map { roleRepository.findByName(it) ?: RoleEntity(name = it) }.toSet()
        val user = UserEntity(username = username, password = password, roles = roleEntities)
        return userRepository.save(user)
    }
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig(
  private val customUserDetailsService: CustomUserDetailsService
) {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests { authz ->
                authz.antMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
            }.formLogin { form ->
                form.loginPage("/login")
                    .permitAll()
            }.logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }

    @Bean
    fun userDetailsService(): UserDetailsService {
        return customUserDetailsService
    }

    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }
}

Использование UserService для создания пользователя

import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component

@Component
class DataInitializer(private val userService: UserService) : CommandLineRunner {
    override fun run(vararg args: String?) {
        userService.createUser("user", passwordEncoder().encode("password"), setOf("ROLE_USER"))
        userService.createUser("admin", passwordEncoder().encode("admin"), setOf("ROLE_ADMIN"))
    }
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }
}

В Spring Security можно использовать аннотации @PreAuthorize и @Secured для ограничения доступа к методам контроллеров или сервисов на основе ролей. Вот примеры их использования.

import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class UserController {
    @PreAuthorize("hasRole('ROLE_USER')")
    @GetMapping("/user")
    fun userPage(model: Model): String {
        model.addAttribute("message", "Welcome, User!")
        return "user"
    }
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/admin")
    fun adminPage(model: Model): String {
        model.addAttribute("message", "Welcome, Admin!")
        return "admin"
    }
}
import org.springframework.security.access.annotation.Secured
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class AdminController {
    @Secured("ROLE_USER")
    @GetMapping("/user-secured")
    fun userPageSecured(model: Model): String {
        model.addAttribute("message", "Welcome, User! (Secured)")
        return "user-secured"
    }
    @Secured("ROLE_ADMIN")
    @GetMapping("/admin-secured")
    fun adminPageSecured(model: Model): String {
        model.addAttribute("message", "Welcome, Admin! (Secured)")
        return "admin-secured"
    }
}

In-Memory Storage:
In-Memory хранение пользователей — самый простой способ для создания пользователей. Вы уже видели пример выше. Вот еще раз пример для ясности:

@Bean
fun userDetailsService(): UserDetailsService {
    val user = User.withDefaultPasswordEncoder()
        .username("user")
        .password("password")
        .roles("USER")
        .build()
    val admin = User.withDefaultPasswordEncoder()
        .username("admin")
        .password("admin")
        .roles("ADMIN")
        .build()
    return InMemoryUserDetailsManager(user, admin)
}

JDBC Storage:
Для использования JDBC хранилища, вам потребуется настроить базу данных и использовать JdbcUserDetailsManager. Пример:

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    runtimeOnly("com.h2database:h2") // Или другой драйвер базы данных
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.provisioning.JdbcUserDetailsManager
import org.springframework.security.provisioning.UserDetailsManager
import org.springframework.security.web.SecurityFilterChain
import javax.sql.DataSource

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeHttpRequests { authz ->
                authz.requestMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
            }.formLogin { form ->
                form.loginPage("/login")
                    .permitAll()
            } .logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }
    @Bean
    fun userDetailsService(dataSource: DataSource): UserDetailsManager {
        val userDetailsManager = JdbcUserDetailsManager(dataSource)
        // Добавление пользователей, если их нет в базе данных
        if (!userDetailsManager.userExists("user")) {
            val user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build()
            userDetailsManager.createUser(user)
        }
        if (!userDetailsManager.userExists("admin")) {
            val admin = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("admin")
                .roles("ADMIN")
                .build()
            userDetailsManager.createUser(admin)
        }
        return userDetailsManager
    }
}
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true

Скрипты для создания таблиц:
Spring Security предоставляет готовые SQL-скрипты для создания таблиц пользователей и ролей. Добавьте их в src/main/resources/schema.sql:

CREATE TABLE users (
    username VARCHAR(50) NOT NULL PRIMARY KEY,
    password VARCHAR(500) NOT NULL,
    enabled BOOLEAN NOT NULL
);

CREATE TABLE authorities (
    username VARCHAR(50) NOT NULL,
    authority VARCHAR(50) NOT NULL,
    FOREIGN KEY (username) REFERENCES users (username)
);

CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);

Custom UserDetailsService:
Если вам нужно кастомное хранилище, создайте собственную реализацию UserDetailsService.

import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service

@Service
class CustomUserDetailsService : UserDetailsService {
    override fun loadUserByUsername(username: String): UserDetails {
        // Замените этот код на собственное хранилище (например, запрос к базе данных)
        if (username == "user") {
            return User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build()
        } else {
            throw UsernameNotFoundException("User not found")
        }
    }
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig(
  val customUserDetailsService: CustomUserDetailsService
) {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeHttpRequests { authz ->
                authz .requestMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
            }.formLogin { form ->
                form.loginPage("/login")
                    .permitAll()
            }.logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }

    @Bean
    fun userDetailsService(): UserDetailsService {
        return customUserDetailsService
    }
}

Spring Integration — это модуль в экосистеме Spring, предназначенный для создания корпоративных интеграционных решений. Он предоставляет поддержку интеграционных шаблонов (Enterprise Integration Patterns, EIP), которые помогают разработчикам проектировать и реализовывать интеграцию различных систем и компонентов в приложении. Spring Integration позволяет легко связывать и координировать взаимодействие между различными системами и компонентами, обеспечивая масштабируемость, надежность и простоту поддержки.

Основные возможности Spring Integration:

Поддержка интеграционных шаблонов (EIP):
Включает в себя поддержку популярных шаблонов интеграции, таких как маршрутизация сообщений, преобразование сообщений, фильтрация сообщений и агрегация сообщений.

Соединение с различными системами:
Обеспечивает подключение к различным источникам данных и внешним системам, таким как базы данных, очереди сообщений (JMS, RabbitMQ), веб-службы (REST, SOAP), файлы и другие.

Модульная архитектура:
Предоставляет модульную архитектуру, позволяющую легко добавлять и конфигурировать компоненты интеграции.

Расширяемость:
Позволяет разработчикам легко расширять функциональность путем добавления своих собственных компонентов интеграции.

Основные компоненты Spring Integration:

Message (Сообщение):
Основная структура данных, которая передается между компонентами. Сообщение состоит из полезной нагрузки (payload) и заголовков (headers).

Channel (Канал):
Транспортный механизм, через который передаются сообщения. Каналы могут быть точка-точка (point-to-point) или публикация-подписка (publish-subscribe).

Endpoint (Конечная точка):
Компоненты, которые отправляют или получают сообщения. Конечные точки включают преобразователи сообщений (message transformers), маршрутизаторы (routers), фильтры (filters) и другие обработчики сообщений.

Gateway (Шлюз):
Интерфейсы, которые обеспечивают интеграцию с внешними системами и протоколами. Шлюзы могут быть использованы для отправки и получения сообщений из/в веб-службы, очереди сообщений и других систем.

Пример использования Spring Integration:
Рассмотрим пример, где мы используем Spring Integration для чтения сообщений из очереди JMS и их обработки.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-integration")
    implementation("org.springframework.boot:spring-boot-starter-activemq")
    implementation("org.springframework.integration:spring-integration-jms")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.integration.annotation.ServiceActivator
import org.springframework.integration.channel.DirectChannel
import org.springframework.integration.config.EnableIntegration
import org.springframework.integration.core.MessageHandler
import org.springframework.integration.jms.dsl.Jms
import org.springframework.jms.annotation.EnableJms
import org.springframework.jms.core.JmsTemplate
import javax.jms.ConnectionFactory

@Configuration
@EnableIntegration
@EnableJms
class IntegrationConfig {
    @Bean
    fun inputChannel() = DirectChannel()
    @Bean
    fun jmsInboundAdapter(connectionFactory: ConnectionFactory) =
        Jms.inboundAdapter(connectionFactory)
            .destination("inputQueue")
            .channel(inputChannel())
            .get()
    @Bean
    @ServiceActivator(inputChannel = "inputChannel")
    fun messageHandler(): MessageHandler {
        return MessageHandler { message ->
            println("Received message: ${message.payload}")
        }
    }
}

Отправка сообщений в очередь JMS

import org.springframework.jms.core.JmsTemplate
import org.springframework.stereotype.Component

@Component
class JmsProducer(private val jmsTemplate: JmsTemplate) {
    fun sendMessage(destination: String, message: String) {
        jmsTemplate.convertAndSend(destination, message)
    }
}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import javax.annotation.PostConstruct

@SpringBootApplication
class DemoApplication(private val jmsProducer: JmsProducer) {
    @PostConstruct
    fun init() {
        jmsProducer.sendMessage("inputQueue", "Hello, Spring Integration!")
    }
}

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

Spring Batch — это модуль в экосистеме Spring, предназначенный для создания масштабируемых, надежных и многопоточечных пакетных приложений. Он предоставляет мощный фреймворк для обработки больших объемов данных в пакетном режиме, включая чтение, обработку и запись данных.

Основные возможности Spring Batch:

Чтение, обработка и запись данных:
Поддерживает различные источники данных для чтения, такие как базы данных, файлы, очереди сообщений и т.д.
Позволяет выполнять сложные операции обработки данных.
Поддерживает запись данных в различные источники.

Модульная архитектура:
Пакетная работа (Job) состоит из шагов (Step), каждый из которых может быть конфигурирован независимо.
Каждый шаг включает в себя три основные фазы: чтение, обработка и запись (Read, Process, Write).

Управление транзакциями и отказами:
Поддерживает управление транзакциями, откатами и повторными попытками для обеспечения надежности и целостности данных.

Планирование и мониторинг:
Интеграция с различными планировщиками задач (Quartz, Spring Scheduling и др.).
Поддержка мониторинга и аудита выполнения пакетных задач.

Поддержка параллелизма и многопоточности:
Обеспечивает возможность выполнения шагов и задач в многопоточном режиме для повышения производительности.

Основные компоненты Spring Batch:

Job (Работа):
Пакетная задача, состоящая из одного или нескольких шагов (Step). Определяет, какие шаги должны быть выполнены и в каком порядке.

Step (Шаг):
Единица выполнения внутри работы. Состоит из этапов чтения, обработки и записи данных. Каждый шаг может иметь свою собственную логику и конфигурацию.

ItemReader (Читатель элементов):
Компонент, который отвечает за чтение данных из источника. Примеры включают FlatFileItemReader для чтения данных из файла и JdbcCursorItemReader для чтения данных из базы данных.

ItemProcessor (Процессор элементов):
Компонент, который отвечает за обработку данных. Может выполнять любые преобразования или фильтрацию данных.

ItemWriter (Записыватель элементов):
Компонент, который отвечает за запись данных в целевой источник. Примеры включают FlatFileItemWriter для записи данных в файл и JdbcBatchItemWriter для записи данных в базу данных.

Пример использования Spring Batch:
Рассмотрим пример использования Spring Batch для чтения данных из CSV-файла, их обработки и записи в базу данных.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-batch")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    runtimeOnly("com.h2database:h2")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.batch.core.Job
import org.springframework.batch.core.Step
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory
import org.springframework.batch.core.launch.support.RunIdIncrementer
import org.springframework.batch.item.ItemProcessor
import org.springframework.batch.item.ItemReader
import org.springframework.batch.item.ItemWriter
import org.springframework.batch.item.file.FlatFileItemReader
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper
import org.springframework.batch.item.file.mapping.DefaultLineMapper
import org.springframework.batch.item.file.mapping.DelimitedLineTokenizer
import org.springframework.batch.item.file.transform.LineTokenizer
import org.springframework.batch.item.file.transform.Range
import org.springframework.batch.item.support.ListItemReader
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.ClassPathResource
import javax.sql.DataSource

@Configuration
@EnableBatchProcessing
class BatchConfig(
    private val jobBuilderFactory: JobBuilderFactory,
    private val stepBuilderFactory: StepBuilderFactory,
    private val dataSource: DataSource
) {
    @Bean
    fun personReader(): ItemReader<Person> {
        val reader = FlatFileItemReader<Person>()
        reader.setResource(ClassPathResource("people.csv"))
        reader.setLineMapper(DefaultLineMapper<Person>().apply {
            setLineTokenizer(DelimitedLineTokenizer().apply {
                setNames("firstName", "lastName")
            })
            setFieldSetMapper(BeanWrapperFieldSetMapper<Person>().apply {
                setTargetType(Person::class.java)
            })
        })
        return reader
    }
    @Bean
    fun personProcessor(): ItemProcessor<Person, Person> {
        return ItemProcessor { person ->
            person.copy(
                firstName = person.firstName.toUpperCase(), 
                lastName = person.lastName.toUpperCase()
            )
        }
    }
    @Bean
    fun personWriter(): ItemWriter<Person> {
        return ItemWriter { items ->
            items.forEach { println("Writing person: $it") }
        }
    }
    @Bean
    fun importUserJob(): Job {
        return jobBuilderFactory.get("importUserJob")
            .incrementer(RunIdIncrementer())
            .flow(step1())
            .end()
            .build()
    }
    @Bean
    fun step1(): Step {
        return stepBuilderFactory.get("step1")
            .chunk<Person, Person>(10)
            .reader(personReader())
            .processor(personProcessor())
            .writer(personWriter())
            .build()
    }
}
data class Person(
    val firstName: String = "",
    val lastName: String = ""
)

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

John,Doe
Jane,Doe

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

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

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

Конфигурация базы данных:
Если в вашем classpath есть H2, HSQLDB или другая поддерживаемая база данных, Spring Boot автоматически создаст бины для DataSource, EntityManager и настроит их.

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

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

META-INF/spring.factories:
Этот файл указывает на классы автоконфигурации, которые должны быть загружены. Он содержится в JAR-файлах с автоконфигурацией.

Классы автоконфигурации:
Классы, помеченные аннотацией @Configuration и обычно содержащие логику настройки бинов, основанную на присутствии или отсутствии определенных классов или бинов в контексте приложения.

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

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

@Configuration
public class MyServiceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService();
    }

}

Регистрация автоконфигурации в META-INF/spring.factories:

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

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

@ConditionalOnClass:
Автоконфигурация применяется, если указанный класс находится в classpath.

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

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

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

@Configuration
@ConditionalOnClass(MyService.class)
public class MyServiceAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService();
    }
}

Отключение автоконфигурации:
Иногда может потребоваться отключить определенные автоконфигурации. Это можно сделать с помощью аннотации @SpringBootApplication или @EnableAutoConfiguration.

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.runApplication

@SpringBootApplication(exclude = [DataSourceAutoConfiguration::class])
class MyApplication

fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)
}

В Spring Boot можно использовать профили для управления различными конфигурациями вашего приложения, включая разные базовые URL. Профили позволяют вам создавать несколько конфигураций для разных сред (например, разработка, тестирование, продакшен) и переключаться между ними, не изменяя основной код приложения.

Шаги для создания нескольких вариантов сборки с разным base URL:
Определение профилей в application.properties или application.yml
Создайте файлы конфигурации для каждого профиля. Например, создадим три профиля: dev, test и prod, каждый из которых будет иметь свой базовый URL.

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

Настройка основного файла конфигурации:
В основном файле application.properties или application.yml можно определить общие настройки и указать активный профиль по умолчанию (необязательно).

# application.properties
spring.profiles.active=dev

Чтение конфигурации в вашем коде:
Используйте аннотацию @Value или @ConfigurationProperties для получения значения base URL из конфигурационных файлов.

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration

@Configuration
class AppConfig {
    @Value("\${app.base.url}")
    lateinit var baseUrl: String
}

Использование профилей в коде:
Вы можете также использовать аннотацию @Profile для включения или отключения бинов в зависимости от активного профиля.

import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Service

interface ExampleService {
    fun getBaseUrl(): String
}

@Service
@Profile("dev")
class DevExampleService(private val config: AppConfig) : ExampleService {
    override fun getBaseUrl(): String {
        return config.baseUrl
    }
}

@Service
@Profile("test")
class TestExampleService(private val config: AppConfig) : ExampleService {
    override fun getBaseUrl(): String {
        return config.baseUrl
    }
}

@Service
@Profile("prod")
class ProdExampleService(private val config: AppConfig) : ExampleService {
    override fun getBaseUrl(): String {
        return config.baseUrl
    }
}

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

Через параметры командной строки

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

Через переменные среды

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

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

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
}

Примеры реактивного и не реактивного кода для Spring WebFlux и Spring Web (обычный Spring MVC) с использованием Kotlin. Будет показано, как создавать REST API для простого CRUD-приложения, которое работает с пользователями (User).

Пример не реактивного кода для Spring Web (Spring MVC):

import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType

@Entity
data class User(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    val email: String,
    val username: String
)
import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository<User, Long>
import org.springframework.stereotype.Service

@Service
class UserService(private val userRepository: UserRepository) {
    fun getAllUsers(): List<User> {
        return userRepository.findAll()
    }
    fun getUserById(id: Long): User? {
        return userRepository.findById(id).orElse(null)
    }
    fun createUser(user: User): User {
        return userRepository.save(user)
    }
    fun updateUser(id: Long, user: User): User? {
        return userRepository.findById(id).map {
            val updatedUser = it.copy(email = user.email, username = user.username)
            userRepository.save(updatedUser)
        }.orElse(null)
    }
    fun deleteUser(id: Long) {
        userRepository.deleteById(id)
    }
}
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {
    @GetMapping
    fun getAllUsers(): List<User> {
        return userService.getAllUsers()
    }
    @GetMapping("/{id}")
    fun getUserById(@PathVariable id: Long): User? {
        return userService.getUserById(id)
    }
    @PostMapping
    fun createUser(@RequestBody user: User): User {
        return userService.createUser(user)
    }
    @PutMapping("/{id}")
    fun updateUser(@PathVariable id: Long, @RequestBody user: User): User? {
        return userService.updateUser(id, user)
    }
    @DeleteMapping("/{id}")
    fun deleteUser(@PathVariable id: Long) {
        userService.deleteUser(id)
    }
}

Пример реактивного кода для Spring WebFlux:

import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document

@Document(collection = "users")
data class User(
    @Id
    val id: String? = null,
    val email: String,
    val username: String
)
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import reactor.core.publisher.Mono

interface UserRepository : ReactiveMongoRepository<User, String>
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@Service
class UserService(private val userRepository: UserRepository) {
    fun getAllUsers(): Flux<User> {
        return userRepository.findAll()
    }
    fun getUserById(id: String): Mono<User> {
        return userRepository.findById(id)
    }
    fun createUser(user: User): Mono<User> {
        return userRepository.save(user)
    }
    fun updateUser(id: String, user: User): Mono<User> {
        return userRepository.findById(id)
            .flatMap {
                val updatedUser = it.copy(email = user.email, username = user.username)
                userRepository.save(updatedUser)
            }
    }
    fun deleteUser(id: String): Mono<Void> {
        return userRepository.deleteById(id)
    }
}
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {
    @GetMapping
    fun getAllUsers(): Flux<User> {
        return userService.getAllUsers()
    }
    @GetMapping("/{id}")
    fun getUserById(@PathVariable id: String): Mono<User> {
        return userService.getUserById(id)
    }
    @PostMapping
    fun createUser(@RequestBody user: User): Mono<User> {
        return userService.createUser(user)
    }
    @PutMapping("/{id}")
    fun updateUser(@PathVariable id: String, @RequestBody user: User): Mono<User> {
        return userService.updateUser(id, user)
    }
    @DeleteMapping("/{id}")
    fun deleteUser(@PathVariable id: String): Mono<Void> {
        return userService.deleteUser(id)
    }
}

Основные отличия заключаются в том, что реактивный подход использует типы Mono и Flux для работы с асинхронными потоками данных, что позволяет улучшить производительность и масштабируемость приложения, особенно при работе с большим количеством одновременных запросов.

ApplicationContext это центральный интерфейс для конфигурирования приложения и получения доступов к его компонентам (бинам). Он является расширением интерфейса BeanFactory, который обеспечивает базовую функциональность контейнера инверсии управления (IoC). Основное отличие ApplicationContext от BeanFactory заключается в дополнительных возможностях, таких как:
Поддержка различных типов событий.
Возможность загрузки сообщений из файлов локализации.
Поддержка декларативного управления транзакциями.

Пример создания ApplicationContext:
Создание ApplicationContext из XML конфигурации

<!-- beans.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="exampleBean" class="com.example.ExampleBean">
        <property name="message" value="Hello, Spring!" />
    </bean>
</beans>
import org.springframework.context.ApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    val context: ApplicationContext = ClassPathXmlApplicationContext("beans.xml")
    val exampleBean: ExampleBean = context.getBean("exampleBean", ExampleBean::class.java)
    println(exampleBean.message)
}

Создание ApplicationContext

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class AppConfig {
    @Bean
    fun exampleBean(): ExampleBean {
        return ExampleBean("Hello, Spring with Kotlin DSL!")
    }
}
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext

fun main() {
    val context: ApplicationContext = AnnotationConfigApplicationContext(AppConfig::class.java)
    val exampleBean: ExampleBean = context.getBean(ExampleBean::class.java)
    println(exampleBean.message)
}

BeanFactory является центральным интерфейсом в Spring для управления объектами, которые составляют ваше приложение. Хотя BeanFactory является фундаментальным интерфейсом для контейнера Spring, чаще используется его более функциональный потомок — ApplicationContext. Однако, BeanFactory может быть полезен в некоторых ситуациях, например, когда вам нужно минимальное использование памяти или более тонкий контроль над инициализацией.

Основные методы BeanFactory:

getBean:
Возвращает экземпляр бина по его имени или типу.

containsBean:
Проверяет, существует ли бин с указанным именем.

isSingleton:
Проверяет, является ли бин с указанным именем синглтоном.

isPrototype:
Проверяет, является ли бин с указанным именем прототипом.

getType:
Возвращает тип бина по его имени.

getAliases:
Возвращает все псевдонимы для бина с указанным именем.

Пример использования BeanFactory с XML:

<!-- beans.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myBean" class="com.example.MyBean">
        <property name="name" value="Bean Name"/>
    </bean>
</beans>
import org.springframework.beans.factory.BeanFactory
import org.springframework.beans.factory.xml.XmlBeanFactory
import org.springframework.core.io.ClassPathResource

class MyBean {
    var name: String? = null
}

fun main() {
    // Загрузка конфигурационного файла Spring
    val resource = ClassPathResource("beans.xml")
    val beanFactory: BeanFactory = XmlBeanFactory(resource)
    // Получение бина по имени
    val myBean = beanFactory.getBean("myBean") as MyBean
    println("Bean Name: ${myBean.name}")
    // Проверка, существует ли бин
    val containsBean = beanFactory.containsBean("myBean")
    println("Contains 'myBean': $containsBean")
    // Проверка, является ли бин синглтоном
    val isSingleton = beanFactory.isSingleton("myBean")
    println("Is 'myBean' Singleton: $isSingleton")
    // Получение типа бина
    val beanType = beanFactory.getType("myBean")
    println("Bean Type: $beanType")
    // Получение всех псевдонимов для бина
    val aliases = beanFactory.getAliases("myBean")
    println("Bean Aliases: ${aliases.joinToString()}")
}
import org.springframework.context.ApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    // Загрузка конфигурационного файла Spring
    val context: ApplicationContext = ClassPathXmlApplicationContext("beans.xml")
    // Получение бина по имени
    val myBean = context.getBean("myBean") as MyBean
    println("Bean Name: ${myBean.name}")
    // Проверка, существует ли бин
    val containsBean = context.containsBean("myBean")
    println("Contains 'myBean': $containsBean")
    // Проверка, является ли бин синглтоном
    val isSingleton = context.isSingleton("myBean")
    println("Is 'myBean' Singleton: $isSingleton")
    // Получение типа бина
    val beanType = context.getType("myBean")
    println("Bean Type: $beanType")
    // Получение всех псевдонимов для бина
    val aliases = context.getAliases("myBean")
    println("Bean Aliases: ${aliases.joinToString()}")
}

Пример с использованием аннотаций:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class AppConfig {
    @Bean
    fun myBean(): MyBean {
        return MyBean().apply {
            name = "Bean Name"
        }
    }
}
import org.springframework.beans.factory.BeanFactory
import org.springframework.stereotype.Service

@Service
class TestClass(
    private val beanFactory: BeanFactory
) {
    fun start() {
        val myBean = beanFactory.getBean("myBean") as GreetingHandler
    }
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext

fun main() {
    // Создание контекста на основе конфигурационного класса
    val context = AnnotationConfigApplicationContext(AppConfig::class.java)
    // Получение BeanFactory из контекста
    val beanFactory = context.beanFactory
    // Получение бина по имени
    val myBean = beanFactory.getBean("myBean") as MyBean
    println("Bean Name: ${myBean.name}")
    // Проверка, существует ли бин
    val containsBean = beanFactory.containsBean("myBean")
    println("Contains 'myBean': $containsBean")
    // Проверка, является ли бин синглтоном
    val isSingleton = beanFactory.isSingleton("myBean")
    println("Is 'myBean' Singleton: $isSingleton")
    // Получение типа бина
    val beanType = beanFactory.getType("myBean")
    println("Bean Type: $beanType")
    // Получение всех псевдонимов для бина
    val aliases = beanFactory.getAliases("myBean")
    println("Bean Aliases: ${aliases.joinToString()}")
}

Spring BOM (Bill Of Materials) — это механизм управления зависимостями, предоставляемый Spring для упрощения управления версиями различных артефактов (библиотек) в экосистеме Spring. BOM используется для того, чтобы гарантировать совместимость и согласованность версий всех зависимостей, используемых в проекте. Используя BOM, вы можете указать одну версию BOM, и все связанные библиотеки будут автоматически использовать соответствующие версии, указанные в BOM.

Пример с использованием Spring BOM:

plugins {
    id 'org.springframework.boot' version '2.6.7'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}
dependencyManagement {
    imports {
        mavenBom "org.springframework.boot:spring-boot-dependencies:2.6.7"
    }
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Пример без использования Spring BOM:

plugins {
    id 'org.springframework.boot' version '2.6.7'
    id 'java'
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.6.7'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.6.7'
    implementation 'org.springframework.boot:spring-boot-starter-security:2.6.7'
    runtimeOnly 'com.h2database:h2:1.4.200'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:2.6.7'
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class SecurityConfig {
    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http.authorizeExchange { exchanges ->
                exchanges.pathMatchers("/public/**")
                    .permitAll()
                    .anyExchange()
                    .authenticated()
            }
            .httpBasic().and()
            .formLogin().and()
            .csrf().disable()
            .build()
    }
    @Bean
    fun userDetailsService(): MapReactiveUserDetailsService {
        val user: UserDetails = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build()
        val admin: UserDetails = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("password")
            .roles("ADMIN")
            .build()
        return MapReactiveUserDetailsService(user, admin)
    }
}

@Service
class SecuredService {
    @PreAuthorize("hasRole('ADMIN')")
    fun adminMethod(): Mono<String> {
        return Mono.just("Admin access granted")
    }
    @PreAuthorize("hasRole('USER')")
    fun userMethod(): Mono<String> {
        return Mono.just("User access granted")
    }
}

Используется аннотация @EnableWebFluxSecurity для включения безопасности WebFlux.
Добавлена аннотация @EnableReactiveMethodSecurity для включения безопасности на уровне методов.
Конфигурационный класс SecurityConfig определяет два бина: securityWebFilterChain и userDetailsService.
Метод securityWebFilterChain настраивает правила безопасности, разрешая доступ к публичным URL (например, /public/**) всем пользователям, и требуя аутентификации для всех остальных запросов. Также включены базовая и форма аутентификации, а CSRF защита отключена.
Метод userDetailsService создает два пользователя с ролями “USER” и “ADMIN” с использованием простого метода шифрования паролей.
Класс SecuredService содержит методы, защищенные аннотациями @PreAuthorize, которые ограничивают доступ к методам в зависимости от ролей пользователей. Метод adminMethod доступен только пользователям с ролью “ADMIN”, а метод userMethod — пользователям с ролью “USER”.

Еще пример:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.crypto.password.NoOpPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.server.SecurityWebFilterChain
import reactor.core.publisher.Mono

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class WebSecurityConfig(
    private val authenticationManager: AuthenticationManager,
    private val securityContextRepository: SecurityContextRepository
) {
    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return NoOpPasswordEncoder.getInstance()
    }
    @Bean
    fun securityWebFilterChain(httpSecurity: ServerHttpSecurity): SecurityWebFilterChain {
        return httpSecurity.exceptionHandling()
            .authenticationEntryPoint { swe, _ ->
                Mono.fromRunnable {
                    swe.response.statusCode = HttpStatus.UNAUTHORIZED
                }
            }
            .accessDeniedHandler { swe, _ ->
                Mono.fromRunnable {
                    swe.response.statusCode = HttpStatus.FORBIDDEN
                }
            }
            .and()
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .authenticationManager(authenticationManager)
            .securityContextRepository(securityContextRepository)
            .authorizeExchange()
            .pathMatchers("/", "/login", "/favicon.ico").permitAll()
            .pathMatchers("/controller").hasRole("ADMIN")
            .anyExchange().authenticated()
            .and()
            .build()
    }
}

Spring Framework предоставляет мощные и гибкие инструменты для валидации данных, включая аннотации, встроенные валидационные механизмы и поддержку пользовательских валидаторов.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import javax.validation.constraints.Email
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size

data class User(
    @field:NotBlank(message = "Name is mandatory")
    val name: String,
    @field:Email(message = "Email should be valid")
    @field:NotBlank(message = "Email is mandatory")
    val email: String,
    @field:Size(min = 8, message = "Password should be at least 8 characters")
    val password: String
)
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import javax.validation.Valid

@RestController
@RequestMapping("/api/users")
class UserController {
    @PostMapping("/register")
    fun registerUser(@Valid @RequestBody user: User): ResponseEntity<String> {
        // В реальном приложении здесь будет логика регистрации пользователя
        return ResponseEntity("User registered successfully", HttpStatus.OK)
    }
}

Обработка ошибок валидации:

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus

@ControllerAdvice
class ValidationHandler {
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException::class)
    fun handleValidationExceptions(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, String>> {
        val errors: MutableMap<String, String> = HashMap()
        ex.bindingResult.fieldErrors.forEach { error ->
            errors[error.field] = error.defaultMessage ?: "Invalid value"
        }
        return ResponseEntity(errors, HttpStatus.BAD_REQUEST)
    }
}

Пример запроса:

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

Пример ответа:

{
    "name": "Name is mandatory",
    "email": "Email should be valid",
    "password": "Password should be at least 8 characters"
}

Утечки памяти могут быть серьезной проблемой для любых приложений, включая Spring-приложения. Выявление и устранение утечек памяти требует систематического подхода, использования инструментов для профилирования и мониторинга, а также знания типичных причин утечек памяти в Java-приложениях.

Основные шаги для выявления утечек памяти:
Мониторинг памяти в реальном времени
Использование инструментов для профилирования
Анализ дампов памяти
Использование логов и метрик
Поиск типичных проблемных мест

Мониторинг памяти в реальном времени:
Для мониторинга использования памяти в реальном времени можно использовать такие инструменты, как JVisualVM, JConsole или внешние APM (Application Performance Monitoring) решения, такие как New Relic, Dynatrace или Prometheus.

Использование инструментов для профилирования:
Профилировщики помогают выявлять утечки памяти, показывая, какие объекты занимают много памяти и как они связаны друг с другом.

JVisualVM:
Запустите JVisualVM, который поставляется с JDK.
Подключитесь к работающему JVM-процессу.
Перейдите на вкладку Memory и нажмите Heap Dump для создания дампа памяти.
Анализируйте количество объектов и их удерживаемые размеры.

YourKit:
YourKit Java Profiler предоставляет мощные возможности для анализа памяти, включая сбор дампов, анализ утечек и профилирование выполнения.
Запустите YourKit и подключитесь к JVM.
Создайте дамп памяти и проанализируйте его с помощью встроенных инструментов.

Анализ дампов памяти:
Анализ дампов памяти помогает понять, какие объекты не освобождаются из памяти.

Eclipse Memory Analyzer (MAT):
Используйте Eclipse MAT для анализа дампов памяти.
Откройте дамп памяти и используйте инструменты, такие как Leak Suspects Report и Histogram для выявления проблем.

Использование логов и метрик:
Логи и метрики могут помочь в выявлении проблем с памятью.

Spring Boot Actuator:
Используйте метрики, такие как jvm.memory.used, для мониторинга использования памяти.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
}
management.endpoints.web.exposure.include=*

Garbage Collection Logs:
Включите логи сборщика мусора в вашем приложении, добавив следующие параметры JVM:

-Xlog:gc*:file=gc.log:time

Поиск типичных проблемных мест:
Некоторые распространенные причины утечек памяти в Spring-приложениях включают

Неудаляемые кэшированные объекты:
Убедитесь, что кэши настроены правильно и объекты удаляются из кэша по мере необходимости.

Неправильное использование синглтонов:
Обратите внимание на использование синглтонов и проверьте, что они не удерживают ссылки на объекты, которые должны быть собраны сборщиком мусора.

Утечки в сессиях:
Убедитесь, что объекты сессий не сохраняются дольше, чем это необходимо.

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

Пример использования JVisualVM для выявления утечек памяти:

Запуск Spring Boot приложения:
Запустите ваше Spring Boot приложение.
Подключение JVisualVM:
Откройте JVisualVM из JDK.
Подключитесь к вашему JVM процессу, который выполняет Spring Boot приложение.

Сбор данных о памяти:
Перейдите на вкладку Monitor для наблюдения за общим использованием памяти.
Перейдите на вкладку Sampler и начните профилирование памяти.

Создание и анализ дампа памяти:
Перейдите на вкладку Heap Dump и создайте дамп памяти.
Проанализируйте дамп памяти на предмет большого количества объектов одного типа или объектов, которые не должны быть в памяти.

RouterFunction это концепция в Spring WebFlux, которая предоставляет функциональный способ определения маршрутов для обработки HTTP-запросов.

Вместо аннотаций, таких как @RequestMapping, используется функциональный подход для конфигурирования маршрутов. Это позволяет создавать маршруты более декларативно и гибко.

Основные особенности RouterFunction:

Функциональный стиль:
Использует функциональные интерфейсы для определения маршрутов и обработчиков запросов.

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

Асинхронность:
Полностью поддерживает асинхронное и неблокирующее программирование, что делает его подходящим для высоконагруженных приложений.

RequestPredicate:
интерфейс в Spring WebFlux, который используется для проверки соответствия входящего HTTP-запроса определенным условиям. Он играет ключевую роль в функциональном программировании маршрутизации в WebFlux, помогая определить, какие обработчики должны обрабатывать конкретные запросы на основе различных критериев, таких как путь, HTTP-метод, заголовки и параметры запроса.

Основные особенности RequestPredicate:

Проверка условий запроса:
Позволяет проверять соответствие запросов определенным условиям, таким как пути, методы, заголовки и параметры запроса.

Сочетание условий:
Можно комбинировать несколько условий с помощью методов and(), or() и negate() для создания сложных предикатов.

Использование в маршрутизации:
Используется вместе с RouterFunction для определения маршрутов в функциональном стиле.

andRoute:
используется для цепочки нескольких маршрутов в функциональном стиле маршрутизации. Это позволяет объединять несколько маршрутов вместе, чтобы они могли обрабатываться одной конфигурацией маршрутизации.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class GreetingHandler {
    fun hello(request: ServerRequest): Mono<ServerResponse> {
        return ServerResponse.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .bodyValue("Hello, Spring WebFlux!")
    }
    fun index(request: ServerRequest): Mono<ServerResponse> {
        return ServerResponse.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .bodyValue("Welcome to the home page!")
    }
}
import com.example.handler.GreetingHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RequestPredicate
import org.springframework.web.reactive.function.server.RequestPredicates.*
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions.route
import org.springframework.web.reactive.function.server.ServerResponse

@Configuration
class GreetingRouter {
    @Bean
    fun route(greetingHandler: GreetingHandler): RouterFunction<ServerResponse> {
        val helloRoute: RequestPredicate = GET("/hello")
             .and(accept(MediaType.TEXT_PLAIN))
        return route(helloRoute, greetingHandler::hello)
            .andRoute(GET("/"), greetingHandler::index)
    }
}

или

import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono

@RestController
@RequestMapping(produces = [MediaType.TEXT_PLAIN_VALUE])
class GreetingHandler {
    @GetMapping("/hello")
    fun hello(): Mono<String> {
        return Mono.just("Hello, Spring WebFlux!")
    }
    @GetMapping("/")
    fun index(): Mono<String> {
        return Mono.just("Welcome to the home page!")
    }
}

еще примеры:

data class User(
    val id: Long,
    val name: String,
    val email: String
)
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@Service
class UserService {
    private val users = listOf(
        User(1, "John Doe", "[email protected]"),
        User(2, "Jane Doe", "[email protected]")
    )
    fun getAllUsers(): Flux<User> {
        return Flux.fromIterable(users)
    }
    fun getUserById(id: Long): Mono<User> {
        return Mono.justOrEmpty(users.find { it.id == id })
    }
}
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class UserHandler(private val userService: UserService) {
    fun getAllUsers(request: ServerRequest): Mono<ServerResponse> {
        return ServerResponse.ok().body(userService.getAllUsers(), User::class.java)
    }
    fun getUserById(request: ServerRequest): Mono<ServerResponse> {
        val userId = request.pathVariable("id").toLong()
        return userService.getUserById(userId)
            .flatMap { user -> ServerResponse.ok().bodyValue(user) }
            .switchIfEmpty(ServerResponse.notFound().build())
    }
    fun getUsersByHeader(request: ServerRequest): Mono<ServerResponse> {
        val headerValue = request.headers().firstHeader("X-USER-ROLE") ?: return ServerResponse.badRequest().build()
        return ServerResponse.ok().body(userService.getUsersByRole(headerValue), User::class.java)
    }
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.server.RequestPredicates.*
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions.route
import org.springframework.web.reactive.function.server.ServerResponse

@Configuration
class RouterConfig {
    @Bean
    fun userRoutes(userHandler: UserHandler): RouterFunction<ServerResponse> {
        return route()
            .GET("/users", userHandler::getAllUsers)
            .GET("/users/{id}", userHandler::getUserById)
            .GET("/users/role", headers("X-USER-ROLE"), userHandler::getUsersByHeader)
            .build()
    }
}

RequestPredicate:
Интерфейс, определяющий условие, которому должен соответствовать запрос. Используется для создания условий маршрутизации.

Примеры предикатов:

GET(“/users”):
Проверяет, что запрос использует метод GET и путь “/users”.

GET(“/users/{id}”):
Проверяет, что запрос использует метод GET и путь “/users/{id}”.

headers(“X-USER-ROLE”):
Проверяет, что запрос содержит заголовок “X-USER-ROLE”.

Комбинирование предикатов:
Предикаты можно комбинировать с помощью методов and(), or() и negate(). Например, можно создать сложное условие, проверяющее и путь, и наличие заголовка, и метод HTTP.

Для выполнения фоновых задач по таймеру в Spring можно использовать аннотацию @Scheduled, которая позволяет вам планировать выполнение методов с заданной периодичностью.

Включение поддержки задач по расписанию:
Для начала нужно включить поддержку аннотации @Scheduled в вашем приложении. Это делается с помощью аннотации @EnableScheduling в одном из конфигурационных классов или в основном классе приложения.

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

@Configuration
@EnableScheduling
class SchedulerConfig

Создайте сервис для фоновых задач:
Создайте сервис, в котором будут находиться методы, выполняющиеся по расписанию. Эти методы нужно аннотировать @Scheduled.

import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import java.time.LocalDateTime

@Service
class ScheduledTasks {

    @Scheduled(fixedRate = 5000)
    fun reportCurrentTime() {
        println("Текущее время: ${LocalDateTime.now()}")
    }
}

Параметры аннотации @Scheduled:

fixedRate:
Определяет фиксированный интервал между запусками метода, в миллисекундах. Например, fixedRate = 300000 означает, что метод будет выполняться каждые 5 минут (300000 миллисекунд).

fixedDelay:
Определяет интервал между завершением последнего запуска метода и началом следующего. Например, fixedDelay = 300000 также запустит метод каждые 5 минут, но только после завершения предыдущего запуска.

@Scheduled(fixedDelay = 5000)
fun taskWithFixedDelay() {
    println("Задача с fixedDelay выполняется: ${LocalDateTime.now()}")
}

initialDelay:
Определяет задержку перед первым запуском метода, в миллисекундах. Например, initialDelay = 10000 означает, что метод будет выполнен через 10 секунд после запуска приложения.

@Scheduled(initialDelay = 10000, fixedRate = 5000)
fun taskWithInitialDelay() {
    println("Задача с initialDelay выполняется: ${LocalDateTime.now()}")
}

cron:
Позволяет задать расписание в формате cron. Например, cron = «0 0 * * * *» запустит метод каждый час.

Пример использования cron:
Этот метод будет выполняться ежедневно в 00:00.

@Scheduled(cron = "0 0/1 * 1/1 * ?")
fun taskWithCronExpression() {
    println("Задача с cron выражением выполняется каждую минуту: ${LocalDateTime.now()}")
}

Настройка пула потоков:
По умолчанию, задачи выполняются в одном потоке, что может быть ограничением, если у вас несколько долгих задач. Чтобы использовать пул потоков, можно настроить TaskScheduler.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler

@Configuration
class SchedulerConfig {

    @Bean
    fun taskScheduler(): ThreadPoolTaskScheduler {
        val scheduler = ThreadPoolTaskScheduler()
        scheduler.poolSize = 10
        scheduler.setThreadNamePrefix("scheduled-task-pool-")
        return scheduler
    }
}

Теперь задачи будут выполняться в отдельном пуле потоков, что позволяет запускать несколько задач параллельно.

Для импорта CSV-файлов в Spring можно использовать различные библиотеки, такие как OpenCSV или Apache Commons CSV. Вот пример использования OpenCSV для импорта данных из CSV-файла в Spring-приложении.

dependencies {
    implementation 'com.opencsv:opencsv:5.7.1'
}
data class Person(
    val id: Long,
    val name: String,
    val age: Int,
    val email: String
)
import com.opencsv.bean.CsvToBeanBuilder
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import java.io.InputStreamReader

@Service
class CsvService {

    fun importCsv(file: MultipartFile): List<Person> {
        val reader = InputStreamReader(file.inputStream)
        val csvToBean = CsvToBeanBuilder<Person>(reader)
            .withType(Person::class.java)
            .withIgnoreLeadingWhiteSpace(true)
            .build()
        return csvToBean.parse()
    }
}
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile

@RestController
class CsvController(private val csvService: CsvService) {

    @PostMapping("/import")
    fun importCsv(@RequestParam("file") file: MultipartFile): ResponseEntity<List<Person>> {
        return try {
            val people = csvService.importCsv(file)
            ResponseEntity(people, HttpStatus.OK)
        } catch (e: Exception) {
            ResponseEntity(HttpStatus.BAD_REQUEST)
        }
    }
}

Теперь вы можете протестировать ваш API, отправив POST-запрос на /import с загруженным CSV-файлом.

Пример CSV-файла

id,name,age,email
1,John Doe,30,[email protected]
2,Jane Smith,25,[email protected]

Copyright: Roman Kryvolapov