Open all questions about Spring
In this article:
➤ What parameters can be in application.properties
➤ How to use WebSocket in Spring
➤ How to Use GraphQL in Spring
➤ How to use OAuth 2.0 in Spring
➤ How to configure encryption in spring
➤ How to configure HTTPS in Spring
➤ What is Spring Boot Actuator
➤ How to set up authorization using Spring Security
➤ What is Spring Integration
➤ What is Spring Batch
➤ What is AutoConfiguration in Spring
➤ How to configure different build profiles in Spring
➤ What is Spring WebFlux
➤ What is ApplicationContext in Spring
➤ What is BeanFactory
➤ What is Spring BOM
➤ How to set up security in WebFlux
➤ How to set up data validation in Spring
➤ How to detect memory leaks in Spring
➤ What is RouterFunction and RequestPredicate in Spring WebFlux
➤ How a Spring application can send and receive data from other APIs
➤ How to make a background task on a timer in Spring
➤ How to import data from CSV in Spring
➤ What parameters can be in application.properties
Database configuration:
# Database connection URL spring.datasource.url=jdbc:h2:mem:testdb # Database driver spring.datasource.driverClassName=org.h2.Driver # Database username spring.datasource.username=sa # Database password spring.datasource.password=password # Database platform (using Hibernate) spring.jpa.database-platform=org.hibernate.dialect.H2Dialect # Automatic database schema creation and update spring.jpa.hibernate.ddl-auto=update
H2 console configuration:
# Enable H2 console spring.h2.console.enabled=true # Path to H2 console spring.h2.console.path=/h2-console
Server configuration:
# Port on which the server is launched server.port=8080 # Default encoding server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=true
Security Configuration:
# Path to the login page spring.security.user.name=user # Password for login page spring.security.user.password=secret # User role spring.security.user.roles=USER
Caching configuration:
# Enable caching spring.cache.type=simple
Logging configuration:
# Logging level for the application logging.level.root=INFO logging.level.org.springframework.web=DEBUG # Path to file lairs logging.file.name=logs/spring-boot-application.log # Log output format logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
Internationalization configuration:
# Locale by default spring.mvc.locale=ru_RU # Enable automatic locale detection spring.mvc.locale-resolver=accept-header
Email sending configuration:
# SMTP server spring.mail.host=smtp.example.com # SMTP port spring.mail.port=587 # Username for SMTP [email protected] # Password for SMTP spring.mail.password=password # Protocol spring.mail.protocol=smtp # Path to email templates spring.mail.templates.path=classpath:/templates/
Application settings configuration:
# Example of user parameter myapp.custom-property=value
➤ How to use WebSocket in Spring
Add dependencies for Spring WebSocket to your build.gradle.kts (if you are using the Kotlin DSL for Gradle):
dependencies { implementation("org.springframework.boot:spring-boot-starter-websocket") implementation("org.springframework.boot:spring-boot-starter-web") }
WebSocket Configuration
import org.springframework.context.annotation.Configuration import org.springframework.web.socket.config.annotation.EnableWebSocket import org.springframework.web.socket.config.annotation.WebSocketConfigurer import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry @Configuration @EnableWebSocket class WebSocketConfig(private val webSocketHandler: MyWebSocketHandler) : WebSocketConfigurer { override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { registry.addHandler(webSocketHandler, "/ws").setAllowedOrigins("*") } }
Implementing a WebSocket Handler
import org.springframework.stereotype.Component import org.springframework.web.socket.TextMessage import org.springframework.web.socket.WebSocketSession import org.springframework.web.socket.handler.TextWebSocketHandler @Component class MyWebSocketHandler : TextWebSocketHandler() { override fun handleTextMessage(session: WebSocketSession, message: TextMessage) { val payload = message.payload println("Received: $payload") val response = TextMessage("Server received: $payload") session.sendMessage(response) } }
➤ How to Use GraphQL in Spring
Adding dependencies
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 Definition:
Create a schema.graphqls file in the src/main/resources folder.
type Query { getUser(id: ID!): User getAllUsers: [User] } type Mutation { createUser(input: CreateUserInput): User } type User { id: ID! name: String! email: String! } input CreateUserInput { name: String! email: String! }
data class User( val id: Long, val name: String, val email: String )
import org.springframework.stereotype.Repository @Repository class UserRepository { private val users = mutableListOf<User>() private var idCounter = 1L fun getUserById(id: Long): User? { return users.find { it.id == id } } fun getAllUsers(): List<User> { return users } fun createUser(name: String, email: String): User { val user = User(id = idCounter++, name = name, email = email) users.add(user) return user } }
GraphQL Resolvers
import com.coxautodev.graphql.tools.GraphQLQueryResolver import org.springframework.stereotype.Component @Component class UserQueryResolver(private val userRepository: UserRepository) : GraphQLQueryResolver { fun getUser(id: Long): User? { return userRepository.getUserById(id) } fun getAllUsers(): List<User> { return userRepository.getAllUsers() } }
import com.coxautodev.graphql.tools.GraphQLMutationResolver import org.springframework.stereotype.Component data class CreateUserInput(val name: String, val email: String) @Component class UserMutationResolver(private val userRepository: UserRepository) : GraphQLMutationResolver { fun createUser(input: CreateUserInput): User { return userRepository.createUser(input.name, input.email) } }
Examples of queries and mutations
Request to get user by ID
query { getUser(id: 1) { id name email } }
Request to get all users
query { getAllUsers { id name email } }
Mutation to create a new user
mutation { createUser(input: { name: "John Doe", email: "[email protected]" }) { id name email } }
➤ How to use OAuth 2.0 in Spring
Setting up OAuth 2.0 in Spring Boot allows your application to securely interact with third-party services such as Google, Facebook, and other OAuth providers. In this example, I will show how to set up a Spring Boot application to authenticate users via OAuth 2.0 using the Google provider.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'com.fasterxml.jackson.module:jackson-module-kotlin' implementation 'org.jetbrains.kotlin:kotlin-reflect' implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
spring.security.oauth2.client.registration.google.client-id=YOUR_CLIENT_ID spring.security.oauth2.client.registration.google.client-secret=YOUR_CLIENT_SECRET spring.security.oauth2.client.registration.google.scope=profile,email spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/login/oauth2/code/{registrationId} spring.security.oauth2.client.registration.google.client-name=Google spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/auth spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo spring.security.oauth2.client.provider.google.user-name-attribute=sub
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class HomeController { @GetMapping("/") fun home(model: Model, @AuthenticationPrincipal principal: OidcUser?): String { if (principal != null) { model.addAttribute("name", principal.name) model.addAttribute("email", principal.email) } return "home" } @GetMapping("/login") fun login(): String { return "login" } }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests { requests -> requests.antMatchers("/", "/login").permitAll() .anyRequest().authenticated() }.oauth2Login { oauth2Login -> oauth2Login.loginPage("/login") .defaultSuccessURL("/") } return http.build() } }
➤ How a Spring application can send and receive data from other APIs
RestTemplate, WebClient, FeignClient, Apache HttpClient, OkHttp, Retrofit, HTTP4K, Ktor can be used for this
RestTemplate:
Nature: Synchronous.
Usage: A simple, blocking HTTP client.
Status: In support mode; it is recommended to switch to WebClient.
Typical use: Legacy projects and simple HTTP calls.
Advantages: Ease of use, familiar to many developers.
WebClient:
Nature: Asynchronous and non-blocking.
Usage: Part of Spring WebFlux, supports reactive programming.
Status: Modern client for HTTP requests.
Typical use: Highly loaded systems and applications that require high performance.
Advantages: Support for asynchronous and synchronous requests, data streams.
FeignClient:
Nature: Synchronous and asynchronous.
Usage: A high-level client for interacting with external APIs.
Status: Integrates with Spring Cloud, used for microservices.
Typical use: Microservice architectures.
Advantages: Automatic client creation, high abstraction.
Apache HttpClient:
Nature: Synchronous and asynchronous.
Usage: Low-level HTTP client with many configurations.
Status: Widely used, powerful and flexible.
Typical use: Cases where low-level control over HTTP requests is required.
Advantages: Flexibility and powerful customization options.
OkHttp:
Nature: Synchronous and asynchronous.
Usage: Modern HTTP client with simple API.
Status: Often used in combination with Retrofit.
Typical use: Android development and JVM-based applications.
Advantages: Simple and clean API.
Retrofit:
Nature: Synchronous and asynchronous.
Usage: High-level client for working with REST API, integrates with OkHttp.
Status: A popular choice for Android and microservices architectures.
Typical use: Android and microservices.
Advantages: Automatic creation of clients, support for various converters.
HTTP4K:
Nature: Asynchronous.
Usage: A functional library for creating HTTP clients and servers.
Status: A lightweight and flexible library.
Typical use: Projects requiring functional programming.
Advantages: Functional approach, simplicity and flexibility.
Who:
Nature: Asynchronous.
Usage: A framework from JetBrains for creating server and client applications in Kotlin.
Status: A modular and easily customizable library.
Typical use: High performance asynchronous applications.
Advantages: Excellent integration with Kotlin, high performance.
Comparative analysis of use:
RestTemplate:
Still used in legacy projects, but its usage is declining.
WebClient:
An increasingly popular choice for new projects, especially those that require asynchrony and reactivity.
FeignClient:
Often used in microservice architectures integrated with Spring Cloud.
Apache HttpClient and OkHttp:
They are used for low-level control and in specific tasks.
Retrofit:
Popular in Android development.
HTTP4K and Ktor:
Less common, but popular among Kotlin developers and those who prefer functional and modular approaches.
RestTemplate:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' }
rest: url: https://api.example.com/data
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.client.RestTemplate @Configuration class AppConfig { @Bean fun restTemplate(): RestTemplate { return RestTemplate() } }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate @Service class ApiService(@Autowired private val restTemplate: RestTemplate) { fun getDataFromExternalApi(url: String): String? { return restTemplate.getForObject(url, String::class.java) } }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpEntity import org.springframework.http.HttpMethod import org.springframework.http.ResponseEntity import org.springframework.stereotype.Service import org.springframework.web.client.RestTemplate data class RequestObject(val name: String, val value: String) data class ResponseObject(val id: Long, val status: String) @Service class ApiService(@Autowired private val restTemplate: RestTemplate) { fun sendAndReceiveObject(url: String, requestObject: RequestObject): ResponseObject? { val requestEntity = HttpEntity(requestObject) val responseEntity: ResponseEntity<ResponseObject> = restTemplate.exchange( url, HttpMethod.POST, requestEntity, ResponseObject::class.java ) return responseEntity.body } }
WebClient:
dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' }
webclient: base-url: https://api.example.com
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.reactive.function.client.WebClient @Configuration class WebClientConfig { @Bean fun webClient(): WebClient { return WebClient.builder().build() } }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.web.reactive.function.client.WebClient @Service class ApiService(@Autowired private val webClient: WebClient) { fun getDataFromExternalApi(url: String): String? { return webClient.get() .uri(url) .retrieve() .bodyToMono(String::class.java) .block() } }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.web.reactive.function.client.WebClient import reactor.core.publisher.Mono data class RequestObject(val name: String, val value: String) data class ResponseObject(val id: Long, val status: String) @Service class ApiService(@Autowired private val webClient: WebClient) { fun sendAndReceiveObject(url: String, requestObject: RequestObject): ResponseObject? { return webClient.post() .uri(url) .body(Mono.just(requestObject), RequestObject::class.java) .retrieve() .bodyToMono(ResponseObject::class.java) .block() } }
FeignClient:
dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' }
feign: client: config: externalApiClient: url: https://api.example.com
import org.springframework.cloud.openfeign.EnableFeignClients import org.springframework.context.annotation.Configuration @Configuration @EnableFeignClients class FeignConfig
import org.springframework.cloud.openfeign.FeignClient import org.springframework.web.bind.annotation.GetMapping @FeignClient(name = "externalApiClient", url = "https://api.external.com") interface ExternalApiClient { @GetMapping("/data") fun getData(): String }
import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @Service class ApiService(@Autowired private val externalApiClient: ExternalApiClient) { fun getDataFromExternalApi(): String { return externalApiClient.getData() } }
Apache HttpClient:
dependencies { implementation 'org.apache.httpcomponents:httpclient:4.5.13' }
import org.apache.http.client.methods.HttpGet import org.apache.http.impl.client.CloseableHttpClient import org.apache.http.impl.client.HttpClients import org.apache.http.util.EntityUtils fun sendHttpRequest(url: String): String? { val httpClient: CloseableHttpClient = HttpClients.createDefault() val httpGet = HttpGet(url) httpClient.execute(httpGet).use { response -> return EntityUtils.toString(response.entity) } }
OkHttp:
dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.0' }
import okhttp3.OkHttpClient import okhttp3.Request fun sendOkHttpRequest(url: String): String? { val client = OkHttpClient() val request = Request.Builder() .url(url) .build() client.newCall(request).execute().use { response -> return response.body?.string() } }
Retrofit:
dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' }
import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST data class RequestObject(val name: String, val value: String) data class ResponseObject(val id: Long, val status: String) interface ApiService { @POST("data") suspend fun sendData(@Body requestObject: RequestObject): ResponseObject @GET("data") suspend fun getData(): ResponseObject } val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .build() val apiService = retrofit.create(ApiService::class.java)
Spring WebClient with Reactor:
import org.springframework.web.reactive.function.client.WebClient import reactor.core.publisher.Mono val webClient = WebClient.create("https://api.example.com") fun sendAndReceiveReactiveObject(requestObject: RequestObject): Mono<ResponseObject> { return webClient.post() .uri("/data") .body(Mono.just(requestObject), RequestObject::class.java) .retrieve() .bodyToMono(ResponseObject::class.java) }
HTTP4K:
dependencies { implementation "org.http4k:http4k-core:4.9.9.0" implementation "org.http4k:http4k-client-okhttp:4.9.9.0" }
import org.http4k.client.OkHttp import org.http4k.core.Method import org.http4k.core.Request import org.http4k.core.Response import org.http4k.format.Gson.auto import org.http4k.lens.Body data class RequestObject(val name: String, val value: String) data class ResponseObject(val id: Long, val status: String) val client = OkHttp() fun sendHttp4kRequest(url: String, requestObject: RequestObject): ResponseObject? { val requestLens = Body.auto<RequestObject>().toLens() val responseLens = Body.auto<ResponseObject>().toLens() val request = Request(Method.POST, url) .with(requestLens of requestObject) val response: Response = client(request) return responseLens.extract(response) }
Who:
dependencies { implementation "io.ktor:ktor-client-core:1.5.2" implementation "io.ktor:ktor-client-cio:1.5.2" implementation "io.ktor:ktor-client-json:1.5.2" implementation "io.ktor:ktor-client-serialization:1.5.2" }
import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.client.features.json.* import io.ktor.client.features.json.serializer.* import kotlinx.serialization.Serializable @Serializable data class RequestObject(val name: String, val value: String) @Serializable data class ResponseObject(val id: Long, val status: String) val client = HttpClient(CIO) { install(JsonFeature) { serializer = KotlinxSerializer() } } suspend fun sendKtorRequest(url: String, requestObject: RequestObject): ResponseObject { return client.post(url) { body = requestObject } }
➤ How to configure encryption in Spring
In Spring, you can set up data encryption using various libraries and approaches. One popular way is to use Spring Security Crypto to encrypt and decrypt data. In this example, we will look at how to set up password encryption using BCrypt, as well as how to encrypt data using Jasypt.
Encrypting passwords with BCrypt:
BCrypt is a hash function specifically designed for password hashing. It provides strong protection against attacks such as dictionary attacks and brute-force attacks.
dependencies { implementation("org.springframework.boot:spring-boot-starter-security") }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } @Bean fun userDetailsService(passwordEncoder: PasswordEncoder): UserDetailsService { val user = User.withUsername("user") .password(passwordEncoder.encode("password")) .roles("USER") .build() return InMemoryUserDetailsManager(user) } @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic() return http.build() } }
Encrypting data with Jasypt:
Jasypt (Java Simplified Encryption) is a library that provides simple ways to encrypt and decrypt data.
dependencies { implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4") }
jasypt.encryptor.password=yourEncryptionPassword
import org.jasypt.encryption.StringEncryptor import org.jasypt.encryption.pbe.StandardPBEStringEncryptor import org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.env.Environment import java.util.* @Configuration class JasyptConfig { @Bean(name = ["jasyptStringEncryptor"]) fun stringEncryptor(): StringEncryptor { val encryptor = StandardPBEStringEncryptor() encryptor.setPassword("yourEncryptionPassword") return encryptor } @Bean fun propertyPlaceholderConfigurer(environment: Environment): EncryptablePropertyPlaceholderConfigurer { val configurer = EncryptablePropertyPlaceholderConfigurer(stringEncryptor()) configurer.setLocation(environment) return configurer } }
import org.jasypt.util.text.AES256TextEncryptor import org.springframework.stereotype.Service @Service class DataService { private val textEncryptor = AES256TextEncryptor().apply { setPassword("yourEncryptionPassword") } fun encryptData(data: String): String { return textEncryptor.encrypt(data) } fun decryptData(encryptedData: String): String { return textEncryptor.decrypt(encryptedData) } }
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController class DataController(private val dataService: DataService) { @GetMapping("/encrypt") fun encrypt(@RequestParam data: String): String { return dataService.encryptData(data) } @GetMapping("/decrypt") fun decrypt(@RequestParam encryptedData: String): String { return dataService.decryptData(encryptedData) } }
➤ How to configure HTTPS in Spring
Using SSL/TLS certificates to provide secure communication in a Spring Boot application involves setting up HTTPS. This involves creating or obtaining an SSL certificate, setting up the server, and updating the Spring Boot configuration. Below is a step-by-step example of setting up HTTPS using a self-signed certificate.
Generating a self-signed certificate:
To generate a self-signed certificate, you can use keytool, which is included with the JDK.
keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650
myalias:
key alias.
keystore.p12:
Keystore file name.
validity 3650:
certificate validity period (in days).
When you run this command, you will be asked to enter various details such as the keystore password, your name, organization, etc. After this, the keystore.p12 file will be created.
Configuring Spring Boot to use SSL:
Add SSL settings to the application.properties file.
server.port=8443 server.ssl.key-store=classpath:keystore.p12 server.ssl.key-store-password=your_password server.ssl.key-store-type=PKCS12 server.ssl.key-alias=myalias
Moving the keystore.p12 file to the resource folder
Move the keystore.p12 file to the src/main/resources folder of your project so that Spring Boot can find it.
Update security configuration (if required)
If your application uses Spring Security, you can configure it to support HTTPS.
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests { requests -> requests.anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login").permitAll() }.logout { logout -> logout.permitAll() }.requiresChannel { channel -> channel.anyRequest().requiresSecure() } return http.build() } }
➤ What is Spring Boot Actuator
Spring Boot Actuator is a powerful tool in the Spring Boot ecosystem that provides out-of-the-box functionality for monitoring and managing running applications. Actuator provides a set of endpoints that can be used to retrieve information about the application, perform management operations, and monitor its health.
Key features of Spring Boot Actuator:
Status monitoring:
Provides information about the application state, such as performance metrics, database information, system data, etc.
Application management:
Allows you to perform administrative operations such as restarting the application, retrieving configuration information, etc.
Integration with monitoring tools:
Easily integrates with monitoring and alerting tools such as Prometheus, Grafana and others.
Enabling Spring Boot Actuator:
To get started with Actuator, you need to add a dependency to your project.
Example project using 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 Setup:
By default, some Actuator endpoints are enabled, but access to them is restricted. You can configure which endpoints are available, as well as their security level.
application.properties
# Include all Actuator endpoints management.endpoints.web.exposure.include=* # Set the path to Actuator endpoints management.endpoints.web.base-path=/actuator
Actuator's main endpoints are:
/actuator/health:
Checking the application status.
/actuator/info:
General information about the application.
/actuator/metrics:
Application performance metrics.
/actuator/env:
Information about the current environment configuration.
/actuator/beans:
List of all Spring beans in the application context.
/actuator/threaddump:
Snapshot of current flows.
/actuator/loggers:
Logging levels and how to change them.
Examples of queries:
Checking the application status
curl http://localhost:8080/actuator/health
Getting information about the application
curl http://localhost:8080/actuator/info
Getting metrics
curl http://localhost:8080/actuator/metrics
Actuator Endpoint Protection:
Spring Security can be used to secure Actuator endpoints, allowing you to restrict access to administrative functions to authorized users only.
Example of security configuration:
In this example, access to Actuator endpoints is restricted to users with the ADMIN role.
import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter @Configuration @EnableWebSecurity class SecurityConfig : WebSecurityConfigurerAdapter() { override fun configure(http: HttpSecurity) { http.authorizeRequests() .antMatchers("/actuator/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .httpBasic() } }
➤ How to set up authorization using Spring Security
dependencies { implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeHttpRequests { authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form .loginPage("/login") .permitAll() }.logout { logout -> logout.permitAll() } return http.build() } @Bean fun userDetailsService(): UserDetailsService { val user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() val admin = User.withDefaultPasswordEncoder() .username("admin") .password("admin") .roles("ADMIN") .build() return InMemoryUserDetailsManager(user, admin) } }
import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class HomeController { @GetMapping("/") fun home(model: Model): String { model.addAttribute("message", "Welcome to the Home Page!") return "home" } @GetMapping("/login") fun login(): String { return "login" } }
src/main/resources/templates/home.html:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Home</title> </head> <body> <h1 th:text="${message}">Welcome to the Home Page!</h1> <a href="/logout">Logout</a> </body> </html>
src/main/resources/templates/login.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Login</title> </head> <body> <h1>Login</h1> <form th:action="@{/login}" method="post"> <div> <label>Username:</label> <input type="text" name="username"/> </div> <div> <label>Password:</label> <input type="password" name="password"/> </div> <div> <button type="submit">Login</button> </div> </form> </body> </html>
User:
To create a custom class that extends UserDetails, you need to create a class that implements the UserDetails interface. This class will be used to represent the user in the Spring security system.
import javax.persistence.* @Entity data class UserEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0, val username: String, val password: String, val enabled: Boolean = true, @ManyToMany(fetch = FetchType.EAGER) @JoinTable( name = "users_roles", joinColumns = [JoinColumn(name = "user_id")], inverseJoinColumns = [JoinColumn(name = "role_id")] ) val roles: Set<RoleEntity> = HashSet() )
import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.GenerationType import javax.persistence.Id @Entity data class RoleEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0, val name: String )
import org.springframework.data.jpa.repository.JpaRepository interface UserRepository : JpaRepository<UserEntity, Long> { fun findByUsername(username: String): UserEntity? }
import org.springframework.data.jpa.repository.JpaRepository interface RoleRepository : JpaRepository<RoleEntity, Long> { fun findByName(name: String): RoleEntity? }
import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.userdetails.UserDetails class CustomUserDetails(private val user: UserEntity) : UserDetails { override fun getAuthorities(): Collection<GrantedAuthority> { return user.roles.map { SimpleGrantedAuthority(it.name) } } override fun getPassword(): String { return user.password } override fun getUsername(): String { return user.username } override fun isAccountNonExpired(): Boolean { return true } override fun isAccountNonLocked(): Boolean { return true } override fun isCredentialsNonExpired(): Boolean { return true } override fun isEnabled(): Boolean { return user.enabled } }
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.stereotype.Service @Service class CustomUserDetailsService(private val userRepository: UserRepository) : UserDetailsService { override fun loadUserByUsername(username: String): UserDetails { val user = userRepository.findByUsername(username) ?: throw UsernameNotFoundException("User not found with username: $username") return CustomUserDetails(user) } }
import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service @Transactional class UserService( private val userRepository: UserRepository, private val roleRepository: RoleRepository ) { fun createUser(username: String, password: String, roles: Set<String>): UserEntity { val roleEntities = roles.map { roleRepository.findByName(it) ?: RoleEntity(name = it) }.toSet() val user = UserEntity(username = username, password = password, roles = roleEntities) return userRepository.save(user) } }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig( private val customUserDetailsService: CustomUserDetailsService ) { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeRequests { authz -> authz.antMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login") .permitAll() }.logout { logout -> logout.permitAll() } return http.build() } @Bean fun userDetailsService(): UserDetailsService { return customUserDetailsService } @Bean fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } }
Using UserService to create a user
import org.springframework.boot.CommandLineRunner import org.springframework.stereotype.Component @Component class DataInitializer(private val userService: UserService) : CommandLineRunner { override fun run(vararg args: String?) { userService.createUser("user", passwordEncoder().encode("password"), setOf("ROLE_USER")) userService.createUser("admin", passwordEncoder().encode("admin"), setOf("ROLE_ADMIN")) } fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } }
In Spring Security, you can use the @PreAuthorize and @Secured annotations to restrict access to controller or service methods based on roles. Here are some examples of how to use them.
import org.springframework.security.access.prepost.PreAuthorize import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class UserController { @PreAuthorize("hasRole('ROLE_USER')") @GetMapping("/user") fun userPage(model: Model): String { model.addAttribute("message", "Welcome, User!") return "user" } @PreAuthorize("hasRole('ROLE_ADMIN')") @GetMapping("/admin") fun adminPage(model: Model): String { model.addAttribute("message", "Welcome, Admin!") return "admin" } }
import org.springframework.security.access.annotation.Secured import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping @Controller class AdminController { @Secured("ROLE_USER") @GetMapping("/user-secured") fun userPageSecured(model: Model): String { model.addAttribute("message", "Welcome, User! (Secured)") return "user-secured" } @Secured("ROLE_ADMIN") @GetMapping("/admin-secured") fun adminPageSecured(model: Model): String { model.addAttribute("message", "Welcome, Admin! (Secured)") return "admin-secured" } }
In-Memory Storage:
In-Memory user storage is the simplest way to create users. You've already seen the example above. Here's another example for clarity:
@Bean fun userDetailsService(): UserDetailsService { val user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() val admin = User.withDefaultPasswordEncoder() .username("admin") .password("admin") .roles("ADMIN") .build() return InMemoryUserDetailsManager(user, admin) }
JDBC Storage:
To use JDBC storage, you will need to configure the database and use JdbcUserDetailsManager. Example:
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") // Or another database driver testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.User import org.springframework.security.provisioning.JdbcUserDetailsManager import org.springframework.security.provisioning.UserDetailsManager import org.springframework.security.web.SecurityFilterChain import javax.sql.DataSource @Configuration @EnableWebSecurity class SecurityConfig { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeHttpRequests { authz -> authz.requestMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login") .permitAll() } .logout { logout -> logout.permitAll() } return http.build() } @Bean fun userDetailsService(dataSource: DataSource): UserDetailsManager { val userDetailsManager = JdbcUserDetailsManager(dataSource) // Adding users if they are not in the database if (!userDetailsManager.userExists("user")) { val user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() userDetailsManager.createUser(user) } if (!userDetailsManager.userExists("admin")) { val admin = User.withDefaultPasswordEncoder() .username("admin") .password("admin") .roles("ADMIN") .build() userDetailsManager.createUser(admin) } return userDetailsManager } }
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.h2.console.enabled=true
Scripts for creating tables:
Spring Security provides ready-made SQL scripts for creating user and role tables. Add them to 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:
If you need a custom store, create your own implementation of UserDetailsService.
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.stereotype.Service @Service class CustomUserDetailsService : UserDetailsService { override fun loadUserByUsername(username: String): UserDetails { // Replace this code with your own storage (eg database query) if (username == "user") { return User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() } else { throw UsernameNotFoundException("User not found") } } }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.web.SecurityFilterChain @Configuration @EnableWebSecurity class SecurityConfig( val customUserDetailsService: CustomUserDetailsService ) { @Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http.authorizeHttpRequests { authz -> authz .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() }.formLogin { form -> form.loginPage("/login") .permitAll() }.logout { logout -> logout.permitAll() } return http.build() } @Bean fun userDetailsService(): UserDetailsService { return customUserDetailsService } }
➤ What is Spring Integration
Spring Integration is a module in the Spring ecosystem designed to create enterprise integration solutions. It provides support for Enterprise Integration Patterns (EIPs), which help developers design and implement the integration of various systems and components in an application. Spring Integration makes it easy to connect and coordinate interactions between different systems and components, ensuring scalability, reliability, and ease of support.
Key features of Spring Integration:
Support for Integration Templates (EIP):
Includes support for popular integration patterns such as message routing, message transformation, message filtering, and message aggregation.
Connection with various systems:
Provides connectivity to various data sources and external systems such as databases, message queues (JMS, RabbitMQ), web services (REST, SOAP), files and others.
Modular architecture:
Provides a modular architecture that allows you to easily add and configure integration components.
Extensibility:
Allows developers to easily extend functionality by adding their own integration components.
The main components of Spring Integration are:
Message:
The basic data structure that is passed between components. A message consists of a payload and headers.
Channel:
A transport mechanism through which messages are transmitted. Channels can be point-to-point or publish-subscribe.
Endpoint:
Components that send or receive messages. Endpoints include message transformers, routers, filters, and other message handlers.
Gateway:
Interfaces that provide integration with external systems and protocols. Gateways can be used to send and receive messages from/to web services, message queues, and other systems.
Example of using Spring Integration:
Let's look at an example where we use Spring Integration to read messages from a JMS queue and process them.
dependencies { implementation("org.springframework.boot:spring-boot-starter-integration") implementation("org.springframework.boot:spring-boot-starter-activemq") implementation("org.springframework.integration:spring-integration-jms") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.integration.annotation.ServiceActivator import org.springframework.integration.channel.DirectChannel import org.springframework.integration.config.EnableIntegration import org.springframework.integration.core.MessageHandler import org.springframework.integration.jms.dsl.Jms import org.springframework.jms.annotation.EnableJms import org.springframework.jms.core.JmsTemplate import javax.jms.ConnectionFactory @Configuration @EnableIntegration @EnableJms class IntegrationConfig { @Bean fun inputChannel() = DirectChannel() @Bean fun jmsInboundAdapter(connectionFactory: ConnectionFactory) = Jms.inboundAdapter(connectionFactory) .destination("inputQueue") .channel(inputChannel()) .get() @Bean @ServiceActivator(inputChannel = "inputChannel") fun messageHandler(): MessageHandler { return MessageHandler { message -> println("Received message: ${message.payload}") } } }
Sending messages to a JMS queue
import org.springframework.jms.core.JmsTemplate import org.springframework.stereotype.Component @Component class JmsProducer(private val jmsTemplate: JmsTemplate) { fun sendMessage(destination: String, message: String) { jmsTemplate.convertAndSend(destination, message) } }
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.context.annotation.Bean import javax.annotation.PostConstruct @SpringBootApplication class DemoApplication(private val jmsProducer: JmsProducer) { @PostConstruct fun init() { jmsProducer.sendMessage("inputQueue", "Hello, Spring Integration!") } } fun main(args: Array<String>) { runApplication<DemoApplication>(*args) }
➤ What is Spring Batch
Spring Batch is a module in the Spring ecosystem designed to build scalable, reliable, and multi-threaded batch applications. It provides a powerful framework for processing large amounts of data in batch mode, including reading, processing, and writing data.
Key features of Spring Batch:
Reading, processing and writing data:
Supports various data sources for reading, such as databases, files, message queues, etc.
Allows you to perform complex data processing operations.
Supports writing data to various sources.
Modular architecture:
A batch job (Job) consists of steps (Step), each of which can be configured independently.
Each step includes three main phases: Read, Process, Write.
Transaction and Failure Management:
Supports transaction management, rollbacks, and retries to ensure data reliability and integrity.
Planning and monitoring:
Integration with various task schedulers (Quartz, Spring Scheduling, etc.).
Support for monitoring and auditing the execution of batch tasks.
Support for parallelism and multithreading:
Provides the ability to execute steps and tasks in multi-threaded mode to improve performance.
The main components of Spring Batch are:
Job:
A batch task consisting of one or more steps (Step). Specifies which steps should be performed and in what order.
Step:
A unit of execution within a job. Consists of data reading, processing, and writing steps. Each step can have its own logic and configuration.
ItemReader:
A component that is responsible for reading data from a source. Examples include FlatFileItemReader for reading data from a file and JdbcCursorItemReader for reading data from a database.
ItemProcessor:
A component that is responsible for data processing. Can perform any transformations or filtering of data.
ItemWriter:
A component that is responsible for writing data to a target source. Examples include FlatFileItemWriter for writing data to a file and JdbcBatchItemWriter for writing data to a database.
Example of using Spring Batch:
Let's look at an example of using Spring Batch to read data from a CSV file, process it, and write it to a database.
dependencies { implementation("org.springframework.boot:spring-boot-starter-batch") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") runtimeOnly("com.h2database:h2") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.batch.core.Job import org.springframework.batch.core.Step import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing import org.springframework.batch.core.configuration.annotation.JobBuilderFactory import org.springframework.batch.core.configuration.annotation.StepBuilderFactory import org.springframework.batch.core.launch.support.RunIdIncrementer import org.springframework.batch.item.ItemProcessor import org.springframework.batch.item.ItemReader import org.springframework.batch.item.ItemWriter import org.springframework.batch.item.file.FlatFileItemReader import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper import org.springframework.batch.item.file.mapping.DefaultLineMapper import org.springframework.batch.item.file.mapping.DelimitedLineTokenizer import org.springframework.batch.item.file.transform.LineTokenizer import org.springframework.batch.item.file.transform.Range import org.springframework.batch.item.support.ListItemReader import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.io.ClassPathResource import javax.sql.DataSource @Configuration @EnableBatchProcessing class BatchConfig( private val jobBuilderFactory: JobBuilderFactory, private val stepBuilderFactory: StepBuilderFactory, private val dataSource: DataSource ) { @Bean fun personReader(): ItemReader<Person> { val reader = FlatFileItemReader<Person>() reader.setResource(ClassPathResource("people.csv")) reader.setLineMapper(DefaultLineMapper<Person>().apply { setLineTokenizer(DelimitedLineTokenizer().apply { setNames("firstName", "lastName") }) setFieldSetMapper(BeanWrapperFieldSetMapper<Person>().apply { setTargetType(Person::class.java) }) }) return reader } @Bean fun personProcessor(): ItemProcessor<Person, Person> { return ItemProcessor { person -> person.copy( firstName = person.firstName.toUpperCase(), lastName = person.lastName.toUpperCase() ) } } @Bean fun personWriter(): ItemWriter<Person> { return ItemWriter { items -> items.forEach { println("Writing person: $it") } } } @Bean fun importUserJob(): Job { return jobBuilderFactory.get("importUserJob") .incrementer(RunIdIncrementer()) .flow(step1()) .end() .build() } @Bean fun step1(): Step { return stepBuilderFactory.get("step1") .chunk<Person, Person>(10) .reader(personReader()) .processor(personProcessor()) .writer(personWriter()) .build() } }
data class Person( val firstName: String = "", val lastName: String = "" )
CSV file (resources/people.csv)
John,Doe Jane,Doe
➤ What is AutoConfiguration in Spring
Auto-configuration in Spring Boot is a mechanism that automatically configures your Spring application based on dependencies found in the classpath and various conditions. It is one of the key features of Spring Boot, allowing you to minimize the amount of configuration required to run your application.
How autoconfiguration works:
Spring Boot analyzes the classes in your project's classpath and automatically creates and configures the necessary beans for your application. This process is controlled by the @EnableAutoConfiguration annotation, which is typically included in the @SpringBootApplication annotation.
Examples of autoconfiguration operation:
Database configuration:
If you have H2, HSQLDB or another supported database in your classpath, Spring Boot will automatically create beans for DataSource, EntityManager and configure them.
Web server configuration:
If you have dependencies on spring-boot-starter-web, Spring Boot will automatically configure a web server (such as Tomcat) and bundle it with your application.
Main autoconfiguration files and classes:
META-INF/spring.factories:
This file specifies the autoconfiguration classes that should be loaded. It is contained in the autoconfiguration JAR files.
Autoconfiguration classes:
Classes annotated with the @Configuration annotation and typically containing bean configuration logic based on the presence or absence of certain classes or beans in the application context.
Autoconfiguration definition:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @Configuration public class MyServiceAutoConfiguration { @Bean @ConditionalOnMissingBean public MyService myService() { return new MyService(); } }
Registering autoconfiguration in META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.autoconfiguration.MyServiceAutoConfiguration
Autoconfiguration conditions:
Spring Boot provides a variety of annotations that can be used to control when autoconfiguration should be applied:
@ConditionalOnClass:
Autoconfiguration is applied if the specified class is on the classpath.
@ConditionalOnMissingBean:
Autoconfiguration is applied if the bean is not present in the context.
@ConditionalOnProperty:
Autoconfiguration is applied if a specific property is set in application.properties or 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(); } }
Disabling autoconfiguration:
Sometimes you may want to disable certain autoconfigurations. This can be done using the @SpringBootApplication or @EnableAutoConfiguration annotation.
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration import org.springframework.boot.runApplication @SpringBootApplication(exclude = [DataSourceAutoConfiguration::class]) class MyApplication fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args) }
➤ How to configure different build profiles in Spring
In Spring Boot, you can use profiles to manage different configurations of your application, including different base URLs. Profiles allow you to create multiple configurations for different environments (e.g. development, testing, production) and switch between them without changing the main application code.
Steps to create multiple build variants with different base URLs:
Defining profiles in application.properties or application.yml
Create configuration files for each profile. For example, let's create three profiles: dev, test and prod, each with its own base URL.
# application-dev.properties app.base.url=https://dev.example.com
# application-test.properties app.base.url=https://test.example.com
#application-prod.properties app.base.url=https://prod.example.com
Setting up the main configuration file:
In the main application.properties or application.yml file, you can define general settings and specify the default active profile (optional).
# application.properties spring.profiles.active=dev
Reading configuration in your code:
Use the @Value or @ConfigurationProperties annotation to retrieve the base URL value from configuration files.
import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Configuration @Configuration class AppConfig { @Value("\${app.base.url}") lateinit var baseUrl: String }
Using profiles in code:
You can also use the @Profile annotation to enable or disable beans depending on the active profile.
import org.springframework.context.annotation.Profile import org.springframework.stereotype.Service interface ExampleService { fun getBaseUrl(): String } @Service @Profile("dev") class DevExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl } } @Service @Profile("test") class TestExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl } } @Service @Profile("prod") class ProdExampleService(private val config: AppConfig) : ExampleService { override fun getBaseUrl(): String { return config.baseUrl } }
Launching an application with the specified profile:
You can specify the active profile when starting an application via command line parameters, environment variables, or a configuration file.
Via command line parameters
$ java -jar myapp.jar --spring.profiles.active=prod
Via environment variables
$ export SPRING_PROFILES_ACTIVE=prod $ java -jar myapp.jar
➤ What is Spring WebFlux
Spring WebFlux is a reactive web framework introduced in Spring 5 that allows you to build asynchronous and non-blocking web applications. WebFlux can run on various reactive engines such as Netty, Jetty, or Tomcat.
dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive' }
Reactive and non-reactive code examples for Spring WebFlux and Spring Web (regular Spring MVC) using Kotlin. We will show you how to create a REST API for a simple CRUD application that works with users (User).
Example of non-reactive code for Spring Web (Spring MVC):
import javax.persistence.Entity import javax.persistence.Id import javax.persistence.GeneratedValue import javax.persistence.GenerationType @Entity data class User( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null, val email: String, val username: String )
import org.springframework.data.jpa.repository.JpaRepository interface UserRepository : JpaRepository<User, Long>
import org.springframework.stereotype.Service @Service class UserService(private val userRepository: UserRepository) { fun getAllUsers(): List<User> { return userRepository.findAll() } fun getUserById(id: Long): User? { return userRepository.findById(id).orElse(null) } fun createUser(user: User): User { return userRepository.save(user) } fun updateUser(id: Long, user: User): User? { return userRepository.findById(id).map { val updatedUser = it.copy(email = user.email, username = user.username) userRepository.save(updatedUser) }.orElse(null) } fun deleteUser(id: Long) { userRepository.deleteById(id) } }
import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/users") class UserController(private val userService: UserService) { @GetMapping fun getAllUsers(): List<User> { return userService.getAllUsers() } @GetMapping("/{id}") fun getUserById(@PathVariable id: Long): User? { return userService.getUserById(id) } @PostMapping fun createUser(@RequestBody user: User): User { return userService.createUser(user) } @PutMapping("/{id}") fun updateUser(@PathVariable id: Long, @RequestBody user: User): User? { return userService.updateUser(id, user) } @DeleteMapping("/{id}") fun deleteUser(@PathVariable id: Long) { userService.deleteUser(id) } }
Example reactive code for Spring WebFlux:
import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document @Document(collection = "users") data class User( @Id val id: String? = null, val email: String, val username: String )
import org.springframework.data.mongodb.repository.ReactiveMongoRepository import reactor.core.publisher.Mono interface UserRepository : ReactiveMongoRepository<User, String>
import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Mono @Service class UserService(private val userRepository: UserRepository) { fun getAllUsers(): Flux<User> { return userRepository.findAll() } fun getUserById(id: String): Mono<User> { return userRepository.findById(id) } fun createUser(user: User): Mono<User> { return userRepository.save(user) } fun updateUser(id: String, user: User): Mono<User> { return userRepository.findById(id) .flatMap { val updatedUser = it.copy(email = user.email, username = user.username) userRepository.save(updatedUser) } } fun deleteUser(id: String): Mono<Void> { return userRepository.deleteById(id) } }
import org.springframework.web.bind.annotation.* import reactor.core.publisher.Flux import reactor.core.publisher.Mono @RestController @RequestMapping("/users") class UserController(private val userService: UserService) { @GetMapping fun getAllUsers(): Flux<User> { return userService.getAllUsers() } @GetMapping("/{id}") fun getUserById(@PathVariable id: String): Mono<User> { return userService.getUserById(id) } @PostMapping fun createUser(@RequestBody user: User): Mono<User> { return userService.createUser(user) } @PutMapping("/{id}") fun updateUser(@PathVariable id: String, @RequestBody user: User): Mono<User> { return userService.updateUser(id, user) } @DeleteMapping("/{id}") fun deleteUser(@PathVariable id: String): Mono<Void> { return userService.deleteUser(id) } }
The main differences are that the reactive approach uses Mono and Flux types to work with asynchronous data streams, which can improve the performance and scalability of the application, especially when working with a large number of simultaneous requests.
➤ What is ApplicationContext in Spring
ApplicationContext is the central interface for configuring an application and accessing its components (beans). It is an extension of the BeanFactory interface, which provides the basic functionality of an inversion of control (IoC) container. The main difference between ApplicationContext and BeanFactory is in additional capabilities, such as:
Support for various types of events.
Possibility to load messages from localization files.
Support for declarative transaction management.
Example of creating ApplicationContext:
Creating an ApplicationContext from XML Configuration
<!-- beans.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="exampleBean" class="com.example.ExampleBean"> <property name="message" value="Hello, Spring!" /> </bean> </beans>
import org.springframework.context.ApplicationContext import org.springframework.context.support.ClassPathXmlApplicationContext fun main() { val context: ApplicationContext = ClassPathXmlApplicationContext("beans.xml") val exampleBean: ExampleBean = context.getBean("exampleBean", ExampleBean::class.java) println(exampleBean.message) }
Creating an ApplicationContext
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class AppConfig { @Bean fun exampleBean(): ExampleBean { return ExampleBean("Hello, Spring with Kotlin DSL!") } }
import org.springframework.context.ApplicationContext import org.springframework.context.annotation.AnnotationConfigApplicationContext fun main() { val context: ApplicationContext = AnnotationConfigApplicationContext(AppConfig::class.java) val exampleBean: ExampleBean = context.getBean(ExampleBean::class.java) println(exampleBean.message) }
➤ What is BeanFactory
BeanFactory is the central interface in Spring for managing the objects that make up your application. Although BeanFactory is the fundamental interface for the Spring container, its more functional descendant, ApplicationContext, is more commonly used. However, BeanFactory can be useful in some situations, such as when you need minimal memory usage or finer control over initialization.
Main methods of BeanFactory:
getBean:
Returns a bean instance by its name or type.
containsBean:
Checks if a bean with the given name exists.
isSingleton:
Checks whether the bean with the given name is a singleton.
isPrototype:
Checks whether the bean with the given name is a prototype.
getType:
Returns the type of a bean given its name.
getAliases:
Returns all aliases for the bean with the specified name.
Example of using BeanFactory with XML:
<!-- beans.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myBean" class="com.example.MyBean"> <property name="name" value="Bean Name"/> </bean> </beans>
import org.springframework.beans.factory.BeanFactory import org.springframework.beans.factory.xml.XmlBeanFactory import org.springframework.core.io.ClassPathResource class MyBean { var name: String? = null } fun main() { // Loading Spring Configuration File val resource = ClassPathResource("beans.xml") val beanFactory: BeanFactory = XmlBeanFactory(resource) // Getting a bean by name val myBean = beanFactory.getBean("myBean") as MyBean println("Bean Name: ${myBean.name}") // Checking if a bin exists val containsBean = beanFactory.containsBean("myBean") println("Contains 'myBean': $containsBean") // Checking if a bean is a singleton val isSingleton = beanFactory.isSingleton("myBean") println("Is 'myBean' Singleton: $isSingleton") // Getting the bean type val beanType = beanFactory.getType("myBean") println("Bean Type: $beanType") // Get all aliases for a bean val aliases = beanFactory.getAliases("myBean") println("Bean Aliases: ${aliases.joinToString()}") }
import org.springframework.context.ApplicationContext import org.springframework.context.support.ClassPathXmlApplicationContext fun main() { // Loading Spring Configuration File val context: ApplicationContext = ClassPathXmlApplicationContext("beans.xml") // Getting a bean by name val myBean = context.getBean("myBean") as MyBean println("Bean Name: ${myBean.name}") // Checking if a bin exists val containsBean = context.containsBean("myBean") println("Contains 'myBean': $containsBean") // Checking if a bean is a singleton val isSingleton = context.isSingleton("myBean") println("Is 'myBean' Singleton: $isSingleton") // Getting the bean type val beanType = context.getType("myBean") println("Bean Type: $beanType") // Get all aliases for a bean val aliases = context.getAliases("myBean") println("Bean Aliases: ${aliases.joinToString()}") }
Example using annotations:
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class AppConfig { @Bean fun myBean(): MyBean { return MyBean().apply { name = "Bean Name" } } }
import org.springframework.beans.factory.BeanFactory import org.springframework.stereotype.Service @Service class TestClass( private val beanFactory: BeanFactory ) { fun start() { val myBean = beanFactory.getBean("myBean") as GreetingHandler } }
import org.springframework.context.annotation.AnnotationConfigApplicationContext fun main() { // Creating a context based on a configuration class val context = AnnotationConfigApplicationContext(AppConfig::class.java) // Getting a BeanFactory from a context val beanFactory = context.beanFactory // Getting a bean by name val myBean = beanFactory.getBean("myBean") as MyBean println("Bean Name: ${myBean.name}") // Checking if a bin exists val containsBean = beanFactory.containsBean("myBean") println("Contains 'myBean': $containsBean") // Checking if a bean is a singleton val isSingleton = beanFactory.isSingleton("myBean") println("Is 'myBean' Singleton: $isSingleton") // Getting the bean type val beanType = beanFactory.getType("myBean") println("Bean Type: $beanType") // Get all aliases for a bean val aliases = beanFactory.getAliases("myBean") println("Bean Aliases: ${aliases.joinToString()}") }
➤ What is Spring BOM
Spring BOM (Bill Of Materials) is a dependency management mechanism provided by Spring to simplify version management of various artifacts (libraries) in the Spring ecosystem. BOM is used to ensure that all dependencies used in a project are compatible and consistent in their versions. Using BOM, you can specify one version of BOM, and all related libraries will automatically use the corresponding versions specified in the BOM.
Example using 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' }
Example without using 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' }
➤ How to set up security in WebFlux
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.web.server.ServerHttpSecurity import org.springframework.security.core.userdetails.MapReactiveUserDetailsService import org.springframework.security.core.userdetails.User import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.access.prepost.PreAuthorize import org.springframework.stereotype.Service import reactor.core.publisher.Mono @Configuration @EnableWebFluxSecurity @EnableReactiveMethodSecurity class SecurityConfig { @Bean fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { return http.authorizeExchange { exchanges -> exchanges.pathMatchers("/public/**") .permitAll() .anyExchange() .authenticated() } .httpBasic().and() .formLogin().and() .csrf().disable() .build() } @Bean fun userDetailsService(): MapReactiveUserDetailsService { val user: UserDetails = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() val admin: UserDetails = User.withDefaultPasswordEncoder() .username("admin") .password("password") .roles("ADMIN") .build() return MapReactiveUserDetailsService(user, admin) } } @Service class SecuredService { @PreAuthorize("hasRole('ADMIN')") fun adminMethod(): Mono<String> { return Mono.just("Admin access granted") } @PreAuthorize("hasRole('USER')") fun userMethod(): Mono<String> { return Mono.just("User access granted") } }
The @EnableWebFluxSecurity annotation is used to enable WebFlux security.
Added @EnableReactiveMethodSecurity annotation to enable method-level security.
The SecurityConfig configuration class defines two beans: securityWebFilterChain and userDetailsService.
The securityWebFilterChain method configures security rules to allow access to public URLs (e.g. /public/**) for all users, and require authentication for all other requests. Basic and Form authentication are also enabled, and CSRF protection is disabled.
The userDetailsService method creates two users with the roles “USER” and “ADMIN” using a simple password encryption method.
The SecuredService class contains methods protected by @PreAuthorize annotations, which restrict access to the methods based on user roles. The adminMethod method is only available to users with the “ADMIN” role, and the userMethod method is only available to users with the “USER” role.
Another example:
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.HttpStatus import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.web.server.ServerHttpSecurity import org.springframework.security.crypto.password.NoOpPasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.server.SecurityWebFilterChain import reactor.core.publisher.Mono @Configuration @EnableWebFluxSecurity @EnableReactiveMethodSecurity class WebSecurityConfig( private val authenticationManager: AuthenticationManager, private val securityContextRepository: SecurityContextRepository ) { @Bean fun passwordEncoder(): PasswordEncoder { return NoOpPasswordEncoder.getInstance() } @Bean fun securityWebFilterChain(httpSecurity: ServerHttpSecurity): SecurityWebFilterChain { return httpSecurity.exceptionHandling() .authenticationEntryPoint { swe, _ -> Mono.fromRunnable { swe.response.statusCode = HttpStatus.UNAUTHORIZED } } .accessDeniedHandler { swe, _ -> Mono.fromRunnable { swe.response.statusCode = HttpStatus.FORBIDDEN } } .and() .csrf().disable() .formLogin().disable() .httpBasic().disable() .authenticationManager(authenticationManager) .securityContextRepository(securityContextRepository) .authorizeExchange() .pathMatchers("/", "/login", "/favicon.ico").permitAll() .pathMatchers("/controller").hasRole("ADMIN") .anyExchange().authenticated() .and() .build() } }
➤ How to set up data validation in Spring
The Spring Framework provides powerful and flexible tools for data validation, including annotations, built-in validation mechanisms, and support for custom validators.
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import javax.validation.constraints.Email import javax.validation.constraints.NotBlank import javax.validation.constraints.Size data class User( @field:NotBlank(message = "Name is mandatory") val name: String, @field:Email(message = "Email should be valid") @field:NotBlank(message = "Email is mandatory") val email: String, @field:Size(min = 8, message = "Password should be at least 8 characters") val password: String )
import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import javax.validation.Valid @RestController @RequestMapping("/api/users") class UserController { @PostMapping("/register") fun registerUser(@Valid @RequestBody user: User): ResponseEntity<String> { // In a real application, this would be the user registration logic. return ResponseEntity("User registered successfully", HttpStatus.OK) } }
Handling validation errors:
import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.MethodArgumentNotValidException import org.springframework.web.bind.annotation.ControllerAdvice import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.ResponseStatus @ControllerAdvice class ValidationHandler { @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException::class) fun handleValidationExceptions(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, String>> { val errors: MutableMap<String, String> = HashMap() ex.bindingResult.fieldErrors.forEach { error -> errors[error.field] = error.defaultMessage ?: "Invalid value" } return ResponseEntity(errors, HttpStatus.BAD_REQUEST) } }
Example request:
POST /api/users/register Content-Type: application/json { "name": "", "email": "invalid-email", "password": "short" }
Example answer:
{ "name": "Name is mandatory", "email": "Email should be valid", "password": "Password should be at least 8 characters" }
➤ How to detect memory leaks in Spring
Memory leaks can be a serious problem for any application, including Spring applications. Identifying and fixing memory leaks requires a systematic approach, using profiling and monitoring tools, and knowledge of the typical causes of memory leaks in Java applications.
Basic steps to detect memory leaks:
Real-time memory monitoring
Using Profiling Tools
Memory dump analysis
Using logs and metrics
Finding typical problem areas
Real-time memory monitoring:
To monitor memory usage in real time, you can use tools like JVisualVM, JConsole, or external APM (Application Performance Monitoring) solutions like New Relic, Dynatrace, or Prometheus.
Using profiling tools:
Profilers help identify memory leaks by showing which objects are taking up a lot of memory and how they are related to each other.
JVisualVM:
Launch JVisualVM, which comes with the JDK.
Attach to a running JVM process.
Go to the Memory tab and click Heap Dump to create a memory dump.
Analyze the number of objects and their held sizes.
YourKit:
YourKit Java Profiler provides powerful memory analysis capabilities, including dump collection, leak analysis, and execution profiling.
Launch YourKit and connect to the JVM.
Create a memory dump and analyze it using built-in tools.
Memory dump analysis:
Analyzing memory dumps helps you understand which objects are not being freed from memory.
Eclipse Memory Analyzer (MAT):
Use Eclipse MAT to analyze memory dumps.
Open the memory dump and use tools like Leak Suspects Report and Histogram to identify problems.
Using logs and metrics:
Logs and metrics can help identify memory problems.
Spring Boot Actuator:
Use metrics such as jvm.memory.used to monitor memory usage.
dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") }
management.endpoints.web.exposure.include=*
Garbage Collection Logs:
Enable garbage collector logging in your application by adding the following JVM options:
-Xlog:gc*:file=gc.log:time
Finding typical problem areas:
Some common causes of memory leaks in Spring applications include
Unremovable cached objects:
Make sure that caches are configured correctly and that objects are removed from the cache as needed.
Incorrect use of singletons:
Pay attention to the use of singletons and check that they do not hold references to objects that should be garbage collected.
Session leaks:
Ensure that session objects are not persisted longer than necessary.
Non-released resources:
Check that all external resources (files, network connections, etc.) are closed correctly.
An example of using JVisualVM to detect memory leaks:
Running a Spring Boot application:
Run your Spring Boot application.
Connecting JVisualVM:
Open JVisualVM from the JDK.
Connect to your JVM process that runs the Spring Boot application.
Collecting memory data:
Go to the Monitor tab to monitor overall memory usage.
Go to the Sampler tab and start memory profiling.
Creating and analyzing a memory dump:
Go to the Heap Dump tab and create a memory dump.
Analyze the memory dump for large numbers of objects of the same type or objects that should not be in memory.
➤ What is RouterFunction and RequestPredicate in Spring WebFlux
RouterFunction is a concept in Spring WebFlux that provides a functional way to define routes for handling HTTP requests.
Instead of annotations like @RequestMapping, a functional approach is used to configure routes. This allows you to create routes more declaratively and flexibly.
Key features of RouterFunction:
Functional style:
Uses functional interfaces to define routes and request handlers.
Declarativeness:
Routes and handlers are defined in code, making routing easier to manage.
Asynchrony:
Fully supports asynchronous and non-blocking programming, making it suitable for high-load applications.
RequestPredicate:
An interface in Spring WebFlux that is used to check whether an incoming HTTP request meets certain conditions. It plays a key role in functional programming routing in WebFlux, helping to determine which handlers should handle specific requests based on various criteria such as path, HTTP method, headers, and request parameters.
Key features of RequestPredicate:
Checking query conditions:
Allows you to check whether requests match certain conditions, such as paths, methods, headers, and request parameters.
Combination of conditions:
You can combine multiple conditions using the and(), or(), and negate() methods to create complex predicates.
Usage in routing:
Used in conjunction with RouterFunction to define routes in a functional style.
andRoute:
used to chain multiple routes in a functional routing style. This allows multiple routes to be chained together so that they can be handled by a single routing configuration.
dependencies { implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.ServerResponse import reactor.core.publisher.Mono @Component class GreetingHandler { fun hello(request: ServerRequest): Mono<ServerResponse> { return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .bodyValue("Hello, Spring WebFlux!") } fun index(request: ServerRequest): Mono<ServerResponse> { return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .bodyValue("Welcome to the home page!") } }
import com.example.handler.GreetingHandler import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.MediaType import org.springframework.web.reactive.function.server.RequestPredicate import org.springframework.web.reactive.function.server.RequestPredicates.* import org.springframework.web.reactive.function.server.RouterFunction import org.springframework.web.reactive.function.server.RouterFunctions.route import org.springframework.web.reactive.function.server.ServerResponse @Configuration class GreetingRouter { @Bean fun route(greetingHandler: GreetingHandler): RouterFunction<ServerResponse> { val helloRoute: RequestPredicate = GET("/hello") .and(accept(MediaType.TEXT_PLAIN)) return route(helloRoute, greetingHandler::hello) .andRoute(GET("/"), greetingHandler::index) } }
or
import org.springframework.http.MediaType import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import reactor.core.publisher.Mono @RestController @RequestMapping(produces = [MediaType.TEXT_PLAIN_VALUE]) class GreetingHandler { @GetMapping("/hello") fun hello(): Mono<String> { return Mono.just("Hello, Spring WebFlux!") } @GetMapping("/") fun index(): Mono<String> { return Mono.just("Welcome to the home page!") } }
more examples:
data class User( val id: Long, val name: String, val email: String )
import org.springframework.stereotype.Service import reactor.core.publisher.Flux import reactor.core.publisher.Mono @Service class UserService { private val users = listOf( User(1, "John Doe", "[email protected]"), User(2, "Jane Doe", "[email protected]") ) fun getAllUsers(): Flux<User> { return Flux.fromIterable(users) } fun getUserById(id: Long): Mono<User> { return Mono.justOrEmpty(users.find { it.id == id }) } }
import org.springframework.stereotype.Component import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.ServerResponse import reactor.core.publisher.Mono @Component class UserHandler(private val userService: UserService) { fun getAllUsers(request: ServerRequest): Mono<ServerResponse> { return ServerResponse.ok().body(userService.getAllUsers(), User::class.java) } fun getUserById(request: ServerRequest): Mono<ServerResponse> { val userId = request.pathVariable("id").toLong() return userService.getUserById(userId) .flatMap { user -> ServerResponse.ok().bodyValue(user) } .switchIfEmpty(ServerResponse.notFound().build()) } fun getUsersByHeader(request: ServerRequest): Mono<ServerResponse> { val headerValue = request.headers().firstHeader("X-USER-ROLE") ?: return ServerResponse.badRequest().build() return ServerResponse.ok().body(userService.getUsersByRole(headerValue), User::class.java) } }
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.reactive.function.server.RequestPredicates.* import org.springframework.web.reactive.function.server.RouterFunction import org.springframework.web.reactive.function.server.RouterFunctions.route import org.springframework.web.reactive.function.server.ServerResponse @Configuration class RouterConfig { @Bean fun userRoutes(userHandler: UserHandler): RouterFunction<ServerResponse> { return route() .GET("/users", userHandler::getAllUsers) .GET("/users/{id}", userHandler::getUserById) .GET("/users/role", headers("X-USER-ROLE"), userHandler::getUsersByHeader) .build() } }
RequestPredicate:
An interface that defines a condition that a request must meet. Used to create routing conditions.
Examples of predicates:
GET(“/users”):
Checks that the request uses the GET method and the path “/users”.
GET(“/users/{id}”):
Checks that the request uses the GET method and the path “/users/{id}”.
headers(“X-USER-ROLE”):
Checks that the request contains the “X-USER-ROLE” header.
Combining predicates:
Predicates can be combined using the and(), or(), and negate() methods. For example, you can create a complex condition that checks for a path, the presence of a header, and the HTTP method.
➤ How to make a background task on a timer in Spring
To execute background tasks on a timer in Spring, you can use the @Scheduled annotation, which allows you to schedule methods to execute at a specified frequency.
Enabling scheduled task support:
First, you need to enable support for the @Scheduled annotation in your application. This is done using the @EnableScheduling annotation in one of the configuration classes or in the main application class.
import org.springframework.context.annotation.Configuration import org.springframework.scheduling.annotation.EnableScheduling @Configuration @EnableScheduling class SchedulerConfig
Create a service for background tasks:
Create a service that will contain scheduled methods. These methods should be annotated with @Scheduled.
import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import java.time.LocalDateTime @Service class ScheduledTasks { @Scheduled(fixedRate = 5000) fun reportCurrentTime() { println("Current time: ${LocalDateTime.now()}") } }
@Scheduled annotation parameters:
fixedRate:
Specifies a fixed interval between method runs, in milliseconds. For example, fixedRate = 300000 means that the method will be executed every 5 minutes (300000 milliseconds).
fixedDelay:
Defines the interval between the completion of the last method run and the start of the next one. For example, fixedDelay = 300000 will also run the method every 5 minutes, but only after the previous run has completed.
@Scheduled(fixedDelay = 5000) fun taskWithFixedDelay() { println("The task with fixedDelay is executed: ${LocalDateTime.now()}") }
initialDelay:
Specifies the delay before the method is first executed, in milliseconds. For example, initialDelay = 10000 means that the method will be executed 10 seconds after the application is launched.
@Scheduled(initialDelay = 10000, fixedRate = 5000) fun taskWithInitialDelay() { println("Task with initialDelay is executed: ${LocalDateTime.now()}") }
cron:
Allows you to set a schedule in cron format. For example, cron = "0 0 * * * *" will run the method every hour.
Example of using cron:
This method will be executed daily at 00:00.
@Scheduled(cron = "0 0/1 * 1/1 * ?") fun taskWithCronExpression() { println("The task with the cron expression is executed every minute: ${LocalDateTime.now()}") }
Thread pool setup:
By default, tasks are executed in a single thread, which can be limiting if you have multiple long running tasks. To use a thread pool, you can configure TaskScheduler.
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler @Configuration class SchedulerConfig { @Bean fun taskScheduler(): ThreadPoolTaskScheduler { val scheduler = ThreadPoolTaskScheduler() scheduler.poolSize = 10 scheduler.setThreadNamePrefix("scheduled-task-pool-") return scheduler } }
Now tasks will be executed in a separate thread pool, which allows you to run multiple tasks in parallel.
➤ How to import data from CSV in Spring
To import CSV files into Spring, you can use various libraries such as OpenCSV or Apache Commons CSV. Here is an example of using OpenCSV to import data from a CSV file into a Spring application.
dependencies { implementation 'com.opencsv:opencsv:5.7.1' }
data class Person( val id: Long, val name: String, val age: Int, val email: String )
import com.opencsv.bean.CsvToBeanBuilder import org.springframework.stereotype.Service import org.springframework.web.multipart.MultipartFile import java.io.InputStreamReader @Service class CsvService { fun importCsv(file: MultipartFile): List<Person> { val reader = InputStreamReader(file.inputStream) val csvToBean = CsvToBeanBuilder<Person>(reader) .withType(Person::class.java) .withIgnoreLeadingWhiteSpace(true) .build() return csvToBean.parse() } }
import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import org.springframework.web.multipart.MultipartFile @RestController class CsvController(private val csvService: CsvService) { @PostMapping("/import") fun importCsv(@RequestParam("file") file: MultipartFile): ResponseEntity<List<Person>> { return try { val people = csvService.importCsv(file) ResponseEntity(people, HttpStatus.OK) } catch (e: Exception) { ResponseEntity(HttpStatus.BAD_REQUEST) } } }
You can now test your API by sending a POST request to /import with the uploaded CSV file.
Example of CSV file
id,name,age,email 1,John Doe,30,[email protected] 2,Jane Smith,25,[email protected]