Java Spring — Компоненты Spring
Какие могут быть параметры в application.properties
Конфигурация базы данных:
# URL подключения к базе данныхspring.datasource.url=jdbc:h2:mem:testdb# Драйвер базы данныхspring.datasource.driverClassName=org.h2.Driver# Имя пользователя базы данныхspring.datasource.username=sa# Пароль базы данныхspring.datasource.password=password# Платформа базы данных (используется Hibernate)spring.jpa.database-platform=org.hibernate.dialect.H2Dialect# Автоматическое создание и обновление схемы базы данныхspring.jpa.hibernate.ddl-auto=updateКонфигурация H2 консоли:
# Включение консоли H2spring.h2.console.enabled=true# Путь доступа к консоли H2spring.h2.console.path=/h2-consoleКонфигурация сервера:
# Порт, на котором запускается серверserver.port=8080# Кодировка по умолчаниюserver.servlet.encoding.charset=UTF-8server.servlet.encoding.enabled=trueserver.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=INFOlogging.level.org.springframework.web=DEBUG# Путь к файлу логовlogging.file.name=logs/spring-boot-application.log# Формат вывода логовlogging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%nlogging.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# Порт SMTPspring.mail.port=587# Имя пользователя для SMTPspring.mail.username=myemail@example.com# Пароль для SMTPspring.mail.password=password# Протоколspring.mail.protocol=smtp# Путь к шаблонам писемspring.mail.templates.path=classpath:/templates/Конфигурация параметров приложения:
# Пример пользовательского параметраmyapp.custom-property=valueКак использовать WebSocket в Spring
Добавьте зависимости для Spring WebSocket в ваш build.gradle.kts (если вы используете Kotlin DSL для Gradle):
dependencies { implementation("org.springframework.boot:spring-boot-starter-websocket") implementation("org.springframework.boot:spring-boot-starter-web")}Конфигурация WebSocket
import org.springframework.context.annotation.Configurationimport org.springframework.web.socket.config.annotation.EnableWebSocketimport org.springframework.web.socket.config.annotation.WebSocketConfigurerimport org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
@Configuration@EnableWebSocketclass WebSocketConfig(private val webSocketHandler: MyWebSocketHandler) : WebSocketConfigurer { override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { registry.addHandler(webSocketHandler, "/ws").setAllowedOrigins("*") }}Реализация обработчика WebSocket
import org.springframework.stereotype.Componentimport org.springframework.web.socket.TextMessageimport org.springframework.web.socket.WebSocketSessionimport org.springframework.web.socket.handler.TextWebSocketHandler
@Componentclass MyWebSocketHandler : TextWebSocketHandler() { override fun handleTextMessage(session: WebSocketSession, message: TextMessage) { val payload = message.payload println("Received: $payload") val response = TextMessage("Server received: $payload") session.sendMessage(response) }}Как использовать GraphQL в Spring
Добавление зависимостей
dependencies { implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:11.1.0") implementation("com.graphql-java-kickstart:graphiql-spring-boot-starter:11.1.0") implementation("com.graphql-java-kickstart:altair-spring-boot-starter:11.1.0") implementation("com.graphql-java-kickstart:voyager-spring-boot-starter:11.1.0") implementation("com.graphql-java-kickstart:graphql-java-tools:11.1.0")}Определение схемы GraphQL:
Создайте файл schema.graphqls в папке src/main/resources.
type Query { getUser(id: ID!): User getAllUsers: [User]}
type Mutation { createUser(input: CreateUserInput): User}
type User { id: ID! name: String! email: String!}
input CreateUserInput { name: String! email: String!}data class User( val id: Long, val name: String, val email: String)import org.springframework.stereotype.Repository
@Repositoryclass 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.GraphQLQueryResolverimport org.springframework.stereotype.Component
@Componentclass 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.GraphQLMutationResolverimport org.springframework.stereotype.Component
data class CreateUserInput(val name: String, val email: String)
@Componentclass 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: "john.doe@example.com" }) { id name email }}Как использовать OAuth 2.0 в Spring
Настройка OAuth 2.0 в Spring Boot позволяет вашему приложению безопасно взаимодействовать с сторонними сервисами, такими как Google, Facebook и другими провайдерами OAuth. В этом примере я покажу, как настроить Spring Boot приложение для аутентификации пользователей через OAuth 2.0 с использованием провайдера Google.
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_IDspring.security.oauth2.client.registration.google.client-secret=YOUR_CLIENT_SECRETspring.security.oauth2.client.registration.google.scope=profile,emailspring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}spring.security.oauth2.client.registration.google.client-name=Googlespring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/authspring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/tokenspring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfospring.security.oauth2.client.provider.google.user-name-attribute=subimport org.springframework.security.core.annotation.AuthenticationPrincipalimport org.springframework.security.oauth2.core.oidc.user.OidcUserimport org.springframework.stereotype.Controllerimport org.springframework.ui.Modelimport org.springframework.web.bind.annotation.GetMapping
@Controllerclass 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.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.security.config.annotation.web.builders.HttpSecurityimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurityimport org.springframework.security.web.SecurityFilterChain
@Configuration@EnableWebSecurityclass SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests { requests -> requests.antMatchers("/", "/login").permitAll() .anyRequest().authenticated() }.oauth2Login { oauth2Login -> oauth2Login.loginPage("/login") .defaultSuccessURL("/") } return http.build() }}Как Spring приложение может отправлять и принимать данные с других API
Для этого могут использоваться RestTemplate, WebClient, FeignClient, Apache HttpClient, OkHttp, Retrofit, HTTP4K, Ktor
RestTemplate:
Природа: Синхронный.
Использование: Простой, блокирующий HTTP клиент.
Статус: В режиме поддержки; рекомендуется переходить на WebClient.
Типичное использование: Легаси проекты и простые HTTP вызовы.
Преимущества: Легкость использования, знаком многим разработчикам.
WebClient:
Природа: Асинхронный и неблокирующий.
Использование: Часть Spring WebFlux, поддерживает реактивное программирование.
Статус: Современный клиент для HTTP запросов.
Типичное использование: Высоконагруженные системы и приложения, требующие высокой производительности.
Преимущества: Поддержка асинхронных и синхронных запросов, потоков данных.
FeignClient:
Природа: Синхронный и асинхронный.
Использование: Высокоуровневый клиент для взаимодействия с внешними API.
Статус: Интегрируется с Spring Cloud, используется для микросервисов.
Типичное использование: Микросервисные архитектуры.
Преимущества: Автоматическое создание клиентов, высокая абстракция.
Apache HttpClient:
Природа: Синхронный и асинхронный.
Использование: Низкоуровневый HTTP клиент с множеством конфигураций.
Статус: Широко используемый, мощный и гибкий.
Типичное использование: Случаи, когда требуется низкоуровневый контроль над HTTP запросами.
Преимущества: Гибкость и мощные возможности настройки.
OkHttp:
Природа: Синхронный и асинхронный.
Использование: Современный HTTP клиент с простым API.
Статус: Часто используется в сочетании с Retrofit.
Типичное использование: Android-разработка и JVM-базированные приложения.
Преимущества: Простой и чистый API.
Retrofit:
Природа: Синхронный и асинхронный.
Использование: Высокоуровневый клиент для работы с REST API, интегрируется с OkHttp.
Статус: Популярный выбор для Android и микросервисных архитектур.
Типичное использование: Android и микросервисы.
Преимущества: Автоматическое создание клиентов, поддержка различных конвертеров.
HTTP4K:
Природа: Асинхронный.
Использование: Функциональная библиотека для создания HTTP клиентов и серверов.
Статус: Легковесная и гибкая библиотека.
Типичное использование: Проекты, требующие функционального программирования.
Преимущества: Функциональный подход, простота и гибкость.
Ktor:
Природа: Асинхронный.
Использование: Фреймворк от JetBrains для создания серверных и клиентских приложений на Kotlin.
Статус: Модульная и легко настраиваемая библиотека.
Типичное использование: Высокопроизводительные асинхронные приложения.
Преимущества: Отличная интеграция с Kotlin, высокая производительность.
Сравнительный анализ использования:
RestTemplate:
Все еще используется в легаси проектах, но его использование сокращается.
WebClient:
Все более популярный выбор для новых проектов, особенно тех, которые требуют асинхронности и реактивности.
FeignClient:
Часто используется в микросервисных архитектурах, интегрированных с Spring Cloud.
Apache HttpClient и OkHttp:
Используются для низкоуровневого контроля и в специфичных задачах.
Retrofit:
Популярен в Android-разработке.
HTTP4K и Ktor:
Менее распространены, но популярны среди Kotlin-разработчиков и тех, кто предпочитает функциональный и модульный подходы.
RestTemplate:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web'}rest: url: https://api.example.com/dataimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.web.client.RestTemplate
@Configurationclass AppConfig { @Bean fun restTemplate(): RestTemplate { return RestTemplate() }}import org.springframework.beans.factory.annotation.Autowiredimport org.springframework.stereotype.Serviceimport org.springframework.web.client.RestTemplate
@Serviceclass ApiService(@Autowired private val restTemplate: RestTemplate) { fun getDataFromExternalApi(url: String): String? { return restTemplate.getForObject(url, String::class.java) }}import org.springframework.beans.factory.annotation.Autowiredimport org.springframework.http.HttpEntityimport org.springframework.http.HttpMethodimport org.springframework.http.ResponseEntityimport org.springframework.stereotype.Serviceimport org.springframework.web.client.RestTemplate
data class RequestObject(val name: String, val value: String)data class ResponseObject(val id: Long, val status: String)
@Serviceclass 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.comimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.web.reactive.function.client.WebClient
@Configurationclass WebClientConfig { @Bean fun webClient(): WebClient { return WebClient.builder().build() }}import org.springframework.beans.factory.annotation.Autowiredimport org.springframework.stereotype.Serviceimport org.springframework.web.reactive.function.client.WebClient
@Serviceclass 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.Autowiredimport org.springframework.stereotype.Serviceimport org.springframework.web.reactive.function.client.WebClientimport reactor.core.publisher.Mono
data class RequestObject(val name: String, val value: String)data class ResponseObject(val id: Long, val status: String)
@Serviceclass 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.comimport org.springframework.cloud.openfeign.EnableFeignClientsimport org.springframework.context.annotation.Configuration
@Configuration@EnableFeignClientsclass FeignConfigimport org.springframework.cloud.openfeign.FeignClientimport 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.Autowiredimport org.springframework.stereotype.Service
@Serviceclass 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.HttpGetimport org.apache.http.impl.client.CloseableHttpClientimport org.apache.http.impl.client.HttpClientsimport 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.OkHttpClientimport 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.Retrofitimport retrofit2.converter.gson.GsonConverterFactoryimport retrofit2.http.Bodyimport retrofit2.http.GETimport 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.WebClientimport 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.OkHttpimport org.http4k.core.Methodimport org.http4k.core.Requestimport org.http4k.core.Responseimport org.http4k.format.Gson.autoimport 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
@Serializabledata class RequestObject(val name: String, val value: String)
@Serializabledata class ResponseObject(val id: Long, val status: String)
val client = HttpClient(CIO) { install(JsonFeature) { serializer = KotlinxSerializer() }}
suspend fun sendKtorRequest(url: String, requestObject: RequestObject): ResponseObject { return client.post(url) { body = requestObject }}Как настроить шифрование в Spring
В Spring можно настроить шифрование данных с помощью различных библиотек и подходов. Одним из популярных способов является использование Spring Security Crypto для шифрования и дешифрования данных. В этом примере мы рассмотрим, как настроить шифрование паролей с помощью BCrypt, а также как шифровать данные с помощью Jasypt.
Шифрование паролей с помощью BCrypt:
BCrypt — это хэш-функция, специально разработанная для хеширования паролей. Она обеспечивает надежную защиту от атак, таких как атаки по словарю и атаки перебором.
dependencies { implementation("org.springframework.boot:spring-boot-starter-security")}import org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.security.config.annotation.web.builders.HttpSecurityimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurityimport org.springframework.security.core.userdetails.Userimport org.springframework.security.core.userdetails.UserDetailsServiceimport org.springframework.security.provisioning.InMemoryUserDetailsManagerimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoderimport org.springframework.security.crypto.password.PasswordEncoderimport org.springframework.security.web.SecurityFilterChain
@Configuration@EnableWebSecurityclass 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=yourEncryptionPasswordimport org.jasypt.encryption.StringEncryptorimport org.jasypt.encryption.pbe.StandardPBEStringEncryptorimport org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurerimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.core.env.Environmentimport java.util.*
@Configurationclass 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.AES256TextEncryptorimport org.springframework.stereotype.Service
@Serviceclass 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.GetMappingimport org.springframework.web.bind.annotation.RequestParamimport org.springframework.web.bind.annotation.RestController
@RestControllerclass DataController(private val dataService: DataService) { @GetMapping("/encrypt") fun encrypt(@RequestParam data: String): String { return dataService.encryptData(data) } @GetMapping("/decrypt") fun decrypt(@RequestParam encryptedData: String): String { return dataService.decryptData(encryptedData) }}Как настроить HTTPS в Spring
Использование SSL/TLS-сертификатов для обеспечения безопасного соединения в Spring Boot приложении включает в себя настройку HTTPS. Для этого нужно создать или получить SSL-сертификат, настроить сервер и обновить конфигурацию Spring Boot. Ниже приведен пошаговый пример настройки HTTPS с использованием самозаверяющего сертификата.
Генерация самозаверяющего сертификата:
Для генерации самозаверяющего сертификата можно использовать keytool, который входит в состав JDK.
keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650myalias:
псевдоним ключа.
keystore.p12:
имя файла хранилища ключей.
validity 3650:
срок действия сертификата (в днях).
При выполнении этой команды вас попросят ввести различные данные, такие как пароль для хранилища ключей, ваше имя, организация и т.д. После этого будет создан файл keystore.p12.
Настройка Spring Boot для использования SSL:
Добавьте настройки SSL в файл application.properties.
server.port=8443server.ssl.key-store=classpath:keystore.p12server.ssl.key-store-password=your_passwordserver.ssl.key-store-type=PKCS12server.ssl.key-alias=myaliasПеремещение файла keystore.p12 в ресурсную папку
Переместите файл keystore.p12 в папку src/main/resources вашего проекта, чтобы Spring Boot мог его найти.
Обновление конфигурации безопасности (если требуется)
Если ваше приложение использует Spring Security, вы можете настроить его для поддержки HTTPS.
import org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.security.config.annotation.web.builders.HttpSecurityimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurityimport org.springframework.security.web.SecurityFilterChain
@Configuration@EnableWebSecurityclass SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests { requests -> requests.anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login").permitAll() }.logout { logout -> logout.permitAll() }.requiresChannel { channel -> channel.anyRequest().requiresSecure() } return http.build() }}Что такое Spring Boot Actuator
Spring Boot Actuator — это мощный инструмент в экосистеме Spring Boot, который предоставляет готовые функции для мониторинга и управления работающими приложениями. Actuator предоставляет набор конечных точек (endpoints), которые можно использовать для получения информации о приложении, выполнения операций управления и мониторинга его состояния.
Основные возможности Spring Boot Actuator:
Мониторинг состояния:
Предоставляет информацию о состоянии приложения, таких как метрики производительности, информация о базе данных, данные о системе и т.д.
Управление приложением:
Позволяет выполнять административные операции, такие как перезапуск приложения, получение информации о конфигурации и т.д.
Интеграция с инструментами мониторинга:
Легко интегрируется с инструментами мониторинга и оповещения, такими как Prometheus, Grafana и другие.
Включение Spring Boot Actuator:
Для начала работы с Actuator, необходимо добавить зависимость в ваш проект.
Пример проекта с использованием Gradle
plugins { id("org.springframework.boot") version "2.6.2" id("io.spring.dependency-management") version "1.0.11.RELEASE" kotlin("jvm") version "1.6.0" kotlin("plugin.spring") version "1.6.0"}dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test")}Настройка Spring Boot Actuator:
По умолчанию некоторые конечные точки Actuator включены, но доступ к ним ограничен. Можно настроить, какие конечные точки будут доступны, а также уровень их безопасности.
application.properties
# Включение всех конечных точек Actuatormanagement.endpoints.web.exposure.include=*# Настройка пути к конечным точкам Actuatormanagement.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.Configurationimport org.springframework.security.config.annotation.web.builders.HttpSecurityimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurityimport org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
@Configuration@EnableWebSecurityclass SecurityConfig : WebSecurityConfigurerAdapter() { override fun configure(http: HttpSecurity) { http.authorizeRequests() .antMatchers("/actuator/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .httpBasic() }}Как настроить авторизацию при помощи Spring Security
dependencies { implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test")}import org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.security.config.annotation.web.builders.HttpSecurityimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurityimport org.springframework.security.core.userdetails.Userimport org.springframework.security.core.userdetails.UserDetailsServiceimport org.springframework.security.provisioning.InMemoryUserDetailsManagerimport org.springframework.security.web.SecurityFilterChain
@Configuration@EnableWebSecurityclass 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.Controllerimport org.springframework.ui.Modelimport org.springframework.web.bind.annotation.GetMapping
@Controllerclass 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.*
@Entitydata 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.Entityimport javax.persistence.GeneratedValueimport javax.persistence.GenerationTypeimport javax.persistence.Id
@Entitydata 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.GrantedAuthorityimport org.springframework.security.core.authority.SimpleGrantedAuthorityimport 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.UserDetailsimport org.springframework.security.core.userdetails.UserDetailsServiceimport org.springframework.security.core.userdetails.UsernameNotFoundExceptionimport org.springframework.stereotype.Service
@Serviceclass 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.Serviceimport org.springframework.transaction.annotation.Transactional
@Service@Transactionalclass 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.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.security.config.annotation.web.builders.HttpSecurityimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurityimport org.springframework.security.core.userdetails.UserDetailsServiceimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoderimport org.springframework.security.crypto.password.PasswordEncoderimport org.springframework.security.web.SecurityFilterChain
@Configuration@EnableWebSecurityclass 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.CommandLineRunnerimport org.springframework.stereotype.Component
@Componentclass 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.PreAuthorizeimport org.springframework.stereotype.Controllerimport org.springframework.ui.Modelimport org.springframework.web.bind.annotation.GetMapping
@Controllerclass 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.Securedimport org.springframework.stereotype.Controllerimport org.springframework.ui.Modelimport org.springframework.web.bind.annotation.GetMapping
@Controllerclass 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 хранение пользователей — самый простой способ для создания пользователей. Вы уже видели пример выше. Вот еще раз пример для ясности:
@Beanfun 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.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.security.config.annotation.web.builders.HttpSecurityimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurityimport org.springframework.security.core.userdetails.Userimport org.springframework.security.provisioning.JdbcUserDetailsManagerimport org.springframework.security.provisioning.UserDetailsManagerimport org.springframework.security.web.SecurityFilterChainimport javax.sql.DataSource
@Configuration@EnableWebSecurityclass 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:testdbspring.datasource.driverClassName=org.h2.Driverspring.datasource.username=saspring.datasource.password=passwordspring.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.UserDetailsimport org.springframework.security.core.userdetails.UserDetailsServiceimport org.springframework.security.core.userdetails.UsernameNotFoundExceptionimport org.springframework.stereotype.Service
@Serviceclass 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.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.security.config.annotation.web.builders.HttpSecurityimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurityimport org.springframework.security.core.userdetails.UserDetailsServiceimport org.springframework.security.web.SecurityFilterChain
@Configuration@EnableWebSecurityclass SecurityConfig( val customUserDetailsService: CustomUserDetailsService) {
@Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeHttpRequests { authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login") .permitAll() }.logout { logout -> logout.permitAll() } return http.build() }
@Bean fun userDetailsService(): UserDetailsService { return customUserDetailsService }}Что такое Spring Integration
Spring Integration — это модуль в экосистеме Spring, предназначенный для создания корпоративных интеграционных решений. Он предоставляет поддержку интеграционных шаблонов (Enterprise Integration Patterns, EIP), которые помогают разработчикам проектировать и реализовывать интеграцию различных систем и компонентов в приложении. Spring Integration позволяет легко связывать и координировать взаимодействие между различными системами и компонентами, обеспечивая масштабируемость, надежность и простоту поддержки.
Основные возможности Spring Integration:
Поддержка интеграционных шаблонов (EIP):
Включает в себя поддержку популярных шаблонов интеграции, таких как маршрутизация сообщений, преобразование сообщений, фильтрация сообщений и агрегация сообщений.
Соединение с различными системами:
Обеспечивает подключение к различным источникам данных и внешним системам, таким как базы данных, очереди сообщений (JMS, RabbitMQ), веб-службы (REST, SOAP), файлы и другие.
Модульная архитектура:
Предоставляет модульную архитектуру, позволяющую легко добавлять и конфигурировать компоненты интеграции.
Расширяемость:
Позволяет разработчикам легко расширять функциональность путем добавления своих собственных компонентов интеграции.
Основные компоненты Spring Integration:
Message (Сообщение):
Основная структура данных, которая передается между компонентами. Сообщение состоит из полезной нагрузки (payload) и заголовков (headers).
Channel (Канал):
Транспортный механизм, через который передаются сообщения. Каналы могут быть точка-точка (point-to-point) или публикация-подписка (publish-subscribe).
Endpoint (Конечная точка):
Компоненты, которые отправляют или получают сообщения. Конечные точки включают преобразователи сообщений (message transformers), маршрутизаторы (routers), фильтры (filters) и другие обработчики сообщений.
Gateway (Шлюз):
Интерфейсы, которые обеспечивают интеграцию с внешними системами и протоколами. Шлюзы могут быть использованы для отправки и получения сообщений из/в веб-службы, очереди сообщений и других систем.
Пример использования Spring Integration:
Рассмотрим пример, где мы используем Spring Integration для чтения сообщений из очереди JMS и их обработки.
dependencies { implementation("org.springframework.boot:spring-boot-starter-integration") implementation("org.springframework.boot:spring-boot-starter-activemq") implementation("org.springframework.integration:spring-integration-jms") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test")}import org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.integration.annotation.ServiceActivatorimport org.springframework.integration.channel.DirectChannelimport org.springframework.integration.config.EnableIntegrationimport org.springframework.integration.core.MessageHandlerimport org.springframework.integration.jms.dsl.Jmsimport org.springframework.jms.annotation.EnableJmsimport org.springframework.jms.core.JmsTemplateimport javax.jms.ConnectionFactory
@Configuration@EnableIntegration@EnableJmsclass 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.JmsTemplateimport org.springframework.stereotype.Component
@Componentclass JmsProducer(private val jmsTemplate: JmsTemplate) { fun sendMessage(destination: String, message: String) { jmsTemplate.convertAndSend(destination, message) }}import org.springframework.boot.autoconfigure.SpringBootApplicationimport org.springframework.boot.runApplicationimport org.springframework.context.annotation.Beanimport javax.annotation.PostConstruct
@SpringBootApplicationclass DemoApplication(private val jmsProducer: JmsProducer) { @PostConstruct fun init() { jmsProducer.sendMessage("inputQueue", "Hello, Spring Integration!") }}
fun main(args: Array<String>) { runApplication<DemoApplication>(*args)}Что такое Spring Batch
Spring Batch — это модуль в экосистеме Spring, предназначенный для создания масштабируемых, надежных и многопоточечных пакетных приложений. Он предоставляет мощный фреймворк для обработки больших объемов данных в пакетном режиме, включая чтение, обработку и запись данных.
Основные возможности Spring Batch:
Чтение, обработка и запись данных:
Поддерживает различные источники данных для чтения, такие как базы данных, файлы, очереди сообщений и т.д.
Позволяет выполнять сложные операции обработки данных.
Поддерживает запись данных в различные источники.
Модульная архитектура:
Пакетная работа (Job) состоит из шагов (Step), каждый из которых может быть конфигурирован независимо.
Каждый шаг включает в себя три основные фазы: чтение, обработка и запись (Read, Process, Write).
Управление транзакциями и отказами:
Поддерживает управление транзакциями, откатами и повторными попытками для обеспечения надежности и целостности данных.
Планирование и мониторинг:
Интеграция с различными планировщиками задач (Quartz, Spring Scheduling и др.).
Поддержка мониторинга и аудита выполнения пакетных задач.
Поддержка параллелизма и многопоточности:
Обеспечивает возможность выполнения шагов и задач в многопоточном режиме для повышения производительности.
Основные компоненты Spring Batch:
Job (Работа):
Пакетная задача, состоящая из одного или нескольких шагов (Step). Определяет, какие шаги должны быть выполнены и в каком порядке.
Step (Шаг):
Единица выполнения внутри работы. Состоит из этапов чтения, обработки и записи данных. Каждый шаг может иметь свою собственную логику и конфигурацию.
ItemReader (Читатель элементов):
Компонент, который отвечает за чтение данных из источника. Примеры включают FlatFileItemReader для чтения данных из файла и JdbcCursorItemReader для чтения данных из базы данных.
ItemProcessor (Процессор элементов):
Компонент, который отвечает за обработку данных. Может выполнять любые преобразования или фильтрацию данных.
ItemWriter (Записыватель элементов):
Компонент, который отвечает за запись данных в целевой источник. Примеры включают FlatFileItemWriter для записи данных в файл и JdbcBatchItemWriter для записи данных в базу данных.
Пример использования Spring Batch:
Рассмотрим пример использования Spring Batch для чтения данных из CSV-файла, их обработки и записи в базу данных.
dependencies { implementation("org.springframework.boot:spring-boot-starter-batch") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") runtimeOnly("com.h2database:h2") testImplementation("org.springframework.boot:spring-boot-starter-test")}import org.springframework.batch.core.Jobimport org.springframework.batch.core.Stepimport org.springframework.batch.core.configuration.annotation.EnableBatchProcessingimport org.springframework.batch.core.configuration.annotation.JobBuilderFactoryimport org.springframework.batch.core.configuration.annotation.StepBuilderFactoryimport org.springframework.batch.core.launch.support.RunIdIncrementerimport org.springframework.batch.item.ItemProcessorimport org.springframework.batch.item.ItemReaderimport org.springframework.batch.item.ItemWriterimport org.springframework.batch.item.file.FlatFileItemReaderimport org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapperimport org.springframework.batch.item.file.mapping.DefaultLineMapperimport org.springframework.batch.item.file.mapping.DelimitedLineTokenizerimport org.springframework.batch.item.file.transform.LineTokenizerimport org.springframework.batch.item.file.transform.Rangeimport org.springframework.batch.item.support.ListItemReaderimport org.springframework.beans.factory.annotation.Valueimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.core.io.ClassPathResourceimport javax.sql.DataSource
@Configuration@EnableBatchProcessingclass 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,DoeJane,DoeЧто такое AutoConfiguration в Spring
Автоконфигурация (Auto-configuration) в Spring Boot — это механизм, который автоматически конфигурирует ваше Spring-приложение на основе зависимостей, найденных в classpath, и различных условий. Это одна из ключевых особенностей Spring Boot, позволяющая минимизировать количество конфигураций, необходимых для запуска приложения.
Как работает автоконфигурация:
Spring Boot анализирует классы в classpath вашего проекта и автоматически создает и настраивает необходимые бины для вашего приложения. Этот процесс управляется аннотацией @EnableAutoConfiguration, которая обычно включена в аннотацию @SpringBootApplication.
Примеры работы автоконфигурации:
Конфигурация базы данных:
Если в вашем classpath есть H2, HSQLDB или другая поддерживаемая база данных, Spring Boot автоматически создаст бины для DataSource, EntityManager и настроит их.
Конфигурация веб-сервера:
Если у вас есть зависимости от spring-boot-starter-web, Spring Boot автоматически настроит веб-сервер (например, Tomcat) и свяжет его с вашим приложением.
Основные файлы и классы автоконфигурации:
META-INF/spring.factories:
Этот файл указывает на классы автоконфигурации, которые должны быть загружены. Он содержится в JAR-файлах с автоконфигурацией.
Классы автоконфигурации:
Классы, помеченные аннотацией @Configuration и обычно содержащие логику настройки бинов, основанную на присутствии или отсутствии определенных классов или бинов в контексте приложения.
Определение автоконфигурации:
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@Configurationpublic 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.SpringBootApplicationimport org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfigurationimport org.springframework.boot.runApplication
@SpringBootApplication(exclude = [DataSourceAutoConfiguration::class])class MyApplication
fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args)}Как настроить разные профили сборки в Spring
В Spring Boot можно использовать профили для управления различными конфигурациями вашего приложения, включая разные базовые URL. Профили позволяют вам создавать несколько конфигураций для разных сред (например, разработка, тестирование, продакшен) и переключаться между ними, не изменяя основной код приложения.
Шаги для создания нескольких вариантов сборки с разным base URL:
Определение профилей в application.properties или application.yml
Создайте файлы конфигурации для каждого профиля. Например, создадим три профиля: dev, test и prod, каждый из которых будет иметь свой базовый URL.
app.base.url=https://dev.example.comapp.base.url=https://test.example.comapp.base.url=https://prod.example.comНастройка основного файла конфигурации:
В основном файле application.properties или application.yml можно определить общие настройки и указать активный профиль по умолчанию (необязательно).
spring.profiles.active=devЧтение конфигурации в вашем коде:
Используйте аннотацию @Value или @ConfigurationProperties для получения значения base URL из конфигурационных файлов.
import org.springframework.beans.factory.annotation.Valueimport org.springframework.context.annotation.Configuration
@Configurationclass AppConfig { @Value("\${app.base.url}") lateinit var baseUrl: String}Использование профилей в коде:
Вы можете также использовать аннотацию @Profile для включения или отключения бинов в зависимости от активного профиля.
import org.springframework.context.annotation.Profileimport org.springframework.stereotype.Service
interface ExampleService { fun getBaseUrl(): String}
@Service@Profile("dev")class DevExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl }}
@Service@Profile("test")class TestExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl }}
@Service@Profile("prod")class ProdExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl }}Запуск приложения с указанным профилем:
Вы можете указать активный профиль при запуске приложения через параметры командной строки, переменные среды или файл конфигурации.
Через параметры командной строки
$ java -jar myapp.jar --spring.profiles.active=prodЧерез переменные среды
$ export SPRING_PROFILES_ACTIVE=prod$ java -jar myapp.jarЧто такое Spring WebFlux
Spring WebFlux — это реактивный веб-фреймворк, представленный в Spring 5, который позволяет создавать асинхронные и неблокирующие веб-приложения. WebFlux может работать на различных реактивных движках, таких как Netty, Jetty или Tomcat.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'}Примеры реактивного и не реактивного кода для Spring WebFlux и Spring Web (обычный Spring MVC) с использованием Kotlin. Будет показано, как создавать REST API для простого CRUD-приложения, которое работает с пользователями (User).
Пример не реактивного кода для Spring Web (Spring MVC):
import javax.persistence.Entityimport javax.persistence.Idimport javax.persistence.GeneratedValueimport javax.persistence.GenerationType
@Entitydata 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
@Serviceclass 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.Idimport 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.ReactiveMongoRepositoryimport reactor.core.publisher.Mono
interface UserRepository : ReactiveMongoRepository<User, String>import org.springframework.stereotype.Serviceimport reactor.core.publisher.Fluximport reactor.core.publisher.Mono
@Serviceclass 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.Fluximport reactor.core.publisher.Mono
@RestController@RequestMapping("/users")class UserController(private val userService: UserService) { @GetMapping fun getAllUsers(): Flux<User> { return userService.getAllUsers() } @GetMapping("/{id}") fun getUserById(@PathVariable id: String): Mono<User> { return userService.getUserById(id) } @PostMapping fun createUser(@RequestBody user: User): Mono<User> { return userService.createUser(user) } @PutMapping("/{id}") fun updateUser(@PathVariable id: String, @RequestBody user: User): Mono<User> { return userService.updateUser(id, user) } @DeleteMapping("/{id}") fun deleteUser(@PathVariable id: String): Mono<Void> { return userService.deleteUser(id) }}Основные отличия заключаются в том, что реактивный подход использует типы Mono и Flux для работы с асинхронными потоками данных, что позволяет улучшить производительность и масштабируемость приложения, особенно при работе с большим количеством одновременных запросов.
Что такое ApplicationContext в Spring
ApplicationContext это центральный интерфейс для конфигурирования приложения и получения доступов к его компонентам (бинам). Он является расширением интерфейса BeanFactory, который обеспечивает базовую функциональность контейнера инверсии управления (IoC). Основное отличие ApplicationContext от BeanFactory заключается в дополнительных возможностях, таких как:
Поддержка различных типов событий.
Возможность загрузки сообщений из файлов локализации.
Поддержка декларативного управления транзакциями.
Пример создания ApplicationContext:
Создание ApplicationContext из XML конфигурации
<beans 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.ApplicationContextimport 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.Beanimport org.springframework.context.annotation.Configuration
@Configurationclass AppConfig { @Bean fun exampleBean(): ExampleBean { return ExampleBean("Hello, Spring with Kotlin DSL!") }}import org.springframework.context.ApplicationContextimport org.springframework.context.annotation.AnnotationConfigApplicationContext
fun main() { val context: ApplicationContext = AnnotationConfigApplicationContext(AppConfig::class.java) val exampleBean: ExampleBean = context.getBean(ExampleBean::class.java) println(exampleBean.message)}Что такое BeanFactory
BeanFactory является центральным интерфейсом в Spring для управления объектами, которые составляют ваше приложение. Хотя BeanFactory является фундаментальным интерфейсом для контейнера Spring, чаще используется его более функциональный потомок — ApplicationContext. Однако, BeanFactory может быть полезен в некоторых ситуациях, например, когда вам нужно минимальное использование памяти или более тонкий контроль над инициализацией.
Основные методы BeanFactory:
getBean:
Возвращает экземпляр бина по его имени или типу.
containsBean:
Проверяет, существует ли бин с указанным именем.
isSingleton:
Проверяет, является ли бин с указанным именем синглтоном.
isPrototype:
Проверяет, является ли бин с указанным именем прототипом.
getType:
Возвращает тип бина по его имени.
getAliases:
Возвращает все псевдонимы для бина с указанным именем.
Пример использования BeanFactory с XML:
<beans 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.BeanFactoryimport org.springframework.beans.factory.xml.XmlBeanFactoryimport 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.ApplicationContextimport 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.Beanimport org.springframework.context.annotation.Configuration
@Configurationclass AppConfig { @Bean fun myBean(): MyBean { return MyBean().apply { name = "Bean Name" } }}import org.springframework.beans.factory.BeanFactoryimport org.springframework.stereotype.Service
@Serviceclass TestClass( private val beanFactory: BeanFactory) { fun start() { val myBean = beanFactory.getBean("myBean") as GreetingHandler }}import org.springframework.context.annotation.AnnotationConfigApplicationContext
fun main() { // Создание контекста на основе конфигурационного класса val context = AnnotationConfigApplicationContext(AppConfig::class.java) // Получение BeanFactory из контекста val beanFactory = context.beanFactory // Получение бина по имени val myBean = beanFactory.getBean("myBean") as MyBean println("Bean Name: ${myBean.name}") // Проверка, существует ли бин val containsBean = beanFactory.containsBean("myBean") println("Contains 'myBean': $containsBean") // Проверка, является ли бин синглтоном val isSingleton = beanFactory.isSingleton("myBean") println("Is 'myBean' Singleton: $isSingleton") // Получение типа бина val beanType = beanFactory.getType("myBean") println("Bean Type: $beanType") // Получение всех псевдонимов для бина val aliases = beanFactory.getAliases("myBean") println("Bean Aliases: ${aliases.joinToString()}")}Что такое Spring BOM
Spring BOM (Bill Of Materials) — это механизм управления зависимостями, предоставляемый Spring для упрощения управления версиями различных артефактов (библиотек) в экосистеме Spring. BOM используется для того, чтобы гарантировать совместимость и согласованность версий всех зависимостей, используемых в проекте. Используя BOM, вы можете указать одну версию BOM, и все связанные библиотеки будут автоматически использовать соответствующие версии, указанные в BOM.
Пример с использованием Spring BOM:
plugins { id 'org.springframework.boot' version '2.6.7' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java'}dependencyManagement { imports { mavenBom "org.springframework.boot:spring-boot-dependencies:2.6.7" }}dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test'}Пример без использования Spring BOM:
plugins { id 'org.springframework.boot' version '2.6.7' id 'java'}dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:2.6.7' implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.6.7' implementation 'org.springframework.boot:spring-boot-starter-security:2.6.7' runtimeOnly 'com.h2database:h2:1.4.200' testImplementation 'org.springframework.boot:spring-boot-starter-test:2.6.7'}Как настроить безопасность в WebFlux
import org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurityimport org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurityimport org.springframework.security.config.web.server.ServerHttpSecurityimport org.springframework.security.core.userdetails.MapReactiveUserDetailsServiceimport org.springframework.security.core.userdetails.Userimport org.springframework.security.core.userdetails.UserDetailsimport org.springframework.security.web.server.SecurityWebFilterChainimport org.springframework.security.access.prepost.PreAuthorizeimport org.springframework.stereotype.Serviceimport reactor.core.publisher.Mono
@Configuration@EnableWebFluxSecurity@EnableReactiveMethodSecurityclass 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) }}
@Serviceclass 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.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.http.HttpStatusimport org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurityimport org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurityimport org.springframework.security.config.web.server.ServerHttpSecurityimport org.springframework.security.crypto.password.NoOpPasswordEncoderimport org.springframework.security.crypto.password.PasswordEncoderimport org.springframework.security.web.server.SecurityWebFilterChainimport reactor.core.publisher.Mono
@Configuration@EnableWebFluxSecurity@EnableReactiveMethodSecurityclass WebSecurityConfig( private val authenticationManager: AuthenticationManager, private val securityContextRepository: SecurityContextRepository) { @Bean fun passwordEncoder(): PasswordEncoder { return NoOpPasswordEncoder.getInstance() } @Bean fun securityWebFilterChain(httpSecurity: ServerHttpSecurity): SecurityWebFilterChain { return httpSecurity.exceptionHandling() .authenticationEntryPoint { swe, _ -> Mono.fromRunnable { swe.response.statusCode = HttpStatus.UNAUTHORIZED } } .accessDeniedHandler { swe, _ -> Mono.fromRunnable { swe.response.statusCode = HttpStatus.FORBIDDEN } } .and() .csrf().disable() .formLogin().disable() .httpBasic().disable() .authenticationManager(authenticationManager) .securityContextRepository(securityContextRepository) .authorizeExchange() .pathMatchers("/", "/login", "/favicon.ico").permitAll() .pathMatchers("/controller").hasRole("ADMIN") .anyExchange().authenticated() .and() .build() }}Как настроить валидацию данных в Spring
Spring Framework предоставляет мощные и гибкие инструменты для валидации данных, включая аннотации, встроенные валидационные механизмы и поддержку пользовательских валидаторов.
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test")}import javax.validation.constraints.Emailimport javax.validation.constraints.NotBlankimport 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.HttpStatusimport org.springframework.http.ResponseEntityimport 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.HttpStatusimport org.springframework.http.ResponseEntityimport org.springframework.web.bind.MethodArgumentNotValidExceptionimport org.springframework.web.bind.annotation.ControllerAdviceimport org.springframework.web.bind.annotation.ExceptionHandlerimport org.springframework.web.bind.annotation.ResponseStatus
@ControllerAdviceclass 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/registerContent-Type: application/json{ "name": "", "email": "invalid-email", "password": "short"}Пример ответа:
{ "name": "Name is mandatory", "email": "Email should be valid", "password": "Password should be at least 8 characters"}Как определять утечки памяти в Spring
Утечки памяти могут быть серьезной проблемой для любых приложений, включая Spring-приложения. Выявление и устранение утечек памяти требует систематического подхода, использования инструментов для профилирования и мониторинга, а также знания типичных причин утечек памяти в Java-приложениях.
Основные шаги для выявления утечек памяти:
Мониторинг памяти в реальном времени
Использование инструментов для профилирования
Анализ дампов памяти
Использование логов и метрик
Поиск типичных проблемных мест
Мониторинг памяти в реальном времени:
Для мониторинга использования памяти в реальном времени можно использовать такие инструменты, как JVisualVM, JConsole или внешние APM (Application Performance Monitoring) решения, такие как New Relic, Dynatrace или Prometheus.
Использование инструментов для профилирования:
Профилировщики помогают выявлять утечки памяти, показывая, какие объекты занимают много памяти и как они связаны друг с другом.
JVisualVM:
Запустите JVisualVM, который поставляется с JDK.
Подключитесь к работающему JVM-процессу.
Перейдите на вкладку Memory и нажмите Heap Dump для создания дампа памяти.
Анализируйте количество объектов и их удерживаемые размеры.
YourKit:
YourKit Java Profiler предоставляет мощные возможности для анализа памяти, включая сбор дампов, анализ утечек и профилирование выполнения.
Запустите YourKit и подключитесь к JVM.
Создайте дамп памяти и проанализируйте его с помощью встроенных инструментов.
Анализ дампов памяти:
Анализ дампов памяти помогает понять, какие объекты не освобождаются из памяти.
Eclipse Memory Analyzer (MAT):
Используйте Eclipse MAT для анализа дампов памяти.
Откройте дамп памяти и используйте инструменты, такие как Leak Suspects Report и Histogram для выявления проблем.
Использование логов и метрик:
Логи и метрики могут помочь в выявлении проблем с памятью.
Spring Boot Actuator:
Используйте метрики, такие как jvm.memory.used, для мониторинга использования памяти.
dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator")}management.endpoints.web.exposure.include=*Garbage Collection Logs:
Включите логи сборщика мусора в вашем приложении, добавив следующие параметры JVM:
-Xlog:gc*:file=gc.log:timeПоиск типичных проблемных мест:
Некоторые распространенные причины утечек памяти в Spring-приложениях включают
Неудаляемые кэшированные объекты:
Убедитесь, что кэши настроены правильно и объекты удаляются из кэша по мере необходимости.
Неправильное использование синглтонов:
Обратите внимание на использование синглтонов и проверьте, что они не удерживают ссылки на объекты, которые должны быть собраны сборщиком мусора.
Утечки в сессиях:
Убедитесь, что объекты сессий не сохраняются дольше, чем это необходимо.
Неосвобождаемые ресурсы:
Проверьте, что все внешние ресурсы (файлы, сетевые соединения и т.д.) закрываются корректно.
Пример использования JVisualVM для выявления утечек памяти:
Запуск Spring Boot приложения:
Запустите ваше Spring Boot приложение.
Подключение JVisualVM:
Откройте JVisualVM из JDK.
Подключитесь к вашему JVM процессу, который выполняет Spring Boot приложение.
Сбор данных о памяти:
Перейдите на вкладку Monitor для наблюдения за общим использованием памяти.
Перейдите на вкладку Sampler и начните профилирование памяти.
Создание и анализ дампа памяти:
Перейдите на вкладку Heap Dump и создайте дамп памяти.
Проанализируйте дамп памяти на предмет большого количества объектов одного типа или объектов, которые не должны быть в памяти.
Что такое RouterFunction и RequestPredicate в Spring WebFlux
RouterFunction это концепция в Spring WebFlux, которая предоставляет функциональный способ определения маршрутов для обработки HTTP-запросов.
Вместо аннотаций, таких как @RequestMapping, используется функциональный подход для конфигурирования маршрутов. Это позволяет создавать маршруты более декларативно и гибко.
Основные особенности RouterFunction:
Функциональный стиль:
Использует функциональные интерфейсы для определения маршрутов и обработчиков запросов.
Декларативность:
Маршруты и обработчики определяются в коде, что позволяет легче управлять маршрутизацией.
Асинхронность:
Полностью поддерживает асинхронное и неблокирующее программирование, что делает его подходящим для высоконагруженных приложений.
RequestPredicate:
интерфейс в Spring WebFlux, который используется для проверки соответствия входящего HTTP-запроса определенным условиям. Он играет ключевую роль в функциональном программировании маршрутизации в WebFlux, помогая определить, какие обработчики должны обрабатывать конкретные запросы на основе различных критериев, таких как путь, HTTP-метод, заголовки и параметры запроса.
Основные особенности RequestPredicate:
Проверка условий запроса:
Позволяет проверять соответствие запросов определенным условиям, таким как пути, методы, заголовки и параметры запроса.
Сочетание условий:
Можно комбинировать несколько условий с помощью методов and(), or() и negate() для создания сложных предикатов.
Использование в маршрутизации:
Используется вместе с RouterFunction для определения маршрутов в функциональном стиле.
andRoute:
используется для цепочки нескольких маршрутов в функциональном стиле маршрутизации. Это позволяет объединять несколько маршрутов вместе, чтобы они могли обрабатываться одной конфигурацией маршрутизации.
dependencies { implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test")}import org.springframework.http.MediaTypeimport org.springframework.stereotype.Componentimport org.springframework.web.reactive.function.server.ServerRequestimport org.springframework.web.reactive.function.server.ServerResponseimport reactor.core.publisher.Mono
@Componentclass 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.GreetingHandlerimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.http.MediaTypeimport org.springframework.web.reactive.function.server.RequestPredicateimport org.springframework.web.reactive.function.server.RequestPredicates.*import org.springframework.web.reactive.function.server.RouterFunctionimport org.springframework.web.reactive.function.server.RouterFunctions.routeimport org.springframework.web.reactive.function.server.ServerResponse
@Configurationclass 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.MediaTypeimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RequestMappingimport org.springframework.web.bind.annotation.RestControllerimport 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.Serviceimport reactor.core.publisher.Fluximport reactor.core.publisher.Mono
@Serviceclass UserService { private val users = listOf( User(1, "John Doe", "john@example.com"), User(2, "Jane Doe", "jane@example.com") ) fun getAllUsers(): Flux<User> { return Flux.fromIterable(users) } fun getUserById(id: Long): Mono<User> { return Mono.justOrEmpty(users.find { it.id == id }) }}import org.springframework.stereotype.Componentimport org.springframework.web.reactive.function.server.ServerRequestimport org.springframework.web.reactive.function.server.ServerResponseimport reactor.core.publisher.Mono
@Componentclass 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.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.web.reactive.function.server.RequestPredicates.*import org.springframework.web.reactive.function.server.RouterFunctionimport org.springframework.web.reactive.function.server.RouterFunctions.routeimport org.springframework.web.reactive.function.server.ServerResponse
@Configurationclass RouterConfig { @Bean fun userRoutes(userHandler: UserHandler): RouterFunction<ServerResponse> { return route() .GET("/users", userHandler::getAllUsers) .GET("/users/{id}", userHandler::getUserById) .GET("/users/role", headers("X-USER-ROLE"), userHandler::getUsersByHeader) .build() }}RequestPredicate:
Интерфейс, определяющий условие, которому должен соответствовать запрос. Используется для создания условий маршрутизации.
Примеры предикатов:
GET(“/users”):
Проверяет, что запрос использует метод GET и путь “/users”.
GET(“/users/{id}”):
Проверяет, что запрос использует метод GET и путь “/users/{id}”.
headers(“X-USER-ROLE”):
Проверяет, что запрос содержит заголовок “X-USER-ROLE”.
Комбинирование предикатов:
Предикаты можно комбинировать с помощью методов and(), or() и negate(). Например, можно создать сложное условие, проверяющее и путь, и наличие заголовка, и метод HTTP.
Как в Spring сделать фоновую задачу по таймеру
Для выполнения фоновых задач по таймеру в Spring можно использовать аннотацию @Scheduled, которая позволяет вам планировать выполнение методов с заданной периодичностью.
Включение поддержки задач по расписанию:
Для начала нужно включить поддержку аннотации @Scheduled в вашем приложении. Это делается с помощью аннотации @EnableScheduling в одном из конфигурационных классов или в основном классе приложения.
import org.springframework.context.annotation.Configurationimport org.springframework.scheduling.annotation.EnableScheduling
@Configuration@EnableSchedulingclass SchedulerConfigСоздайте сервис для фоновых задач:
Создайте сервис, в котором будут находиться методы, выполняющиеся по расписанию. Эти методы нужно аннотировать @Scheduled.
import org.springframework.scheduling.annotation.Scheduledimport org.springframework.stereotype.Serviceimport java.time.LocalDateTime
@Serviceclass 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.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
@Configurationclass SchedulerConfig {
@Bean fun taskScheduler(): ThreadPoolTaskScheduler { val scheduler = ThreadPoolTaskScheduler() scheduler.poolSize = 10 scheduler.setThreadNamePrefix("scheduled-task-pool-") return scheduler }}Теперь задачи будут выполняться в отдельном пуле потоков, что позволяет запускать несколько задач параллельно.
Как в Spring импортировать данные из CSV
Для импорта CSV-файлов в Spring можно использовать различные библиотеки, такие как OpenCSV или Apache Commons CSV. Вот пример использования OpenCSV для импорта данных из CSV-файла в Spring-приложении.
dependencies { implementation 'com.opencsv:opencsv:5.7.1'}data class Person( val id: Long, val name: String, val age: Int, val email: String)import com.opencsv.bean.CsvToBeanBuilderimport org.springframework.stereotype.Serviceimport org.springframework.web.multipart.MultipartFileimport java.io.InputStreamReader
@Serviceclass 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.HttpStatusimport org.springframework.http.ResponseEntityimport org.springframework.web.bind.annotation.PostMappingimport org.springframework.web.bind.annotation.RequestParamimport org.springframework.web.bind.annotation.RestControllerimport org.springframework.web.multipart.MultipartFile
@RestControllerclass 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,email1,John Doe,30,johndoe@example.com2,Jane Smith,25,janesmith@example.com