Sunday, December 3, 2023

Spring Core

Constructor Injection:

Real-life Use Case: Imagine a service that requires dependencies during its instantiation. Constructor injection allows you to inject these dependencies when creating an instance of the service. This is a common pattern for achieving a cleaner and more testable codebase.

import org.springframework.stereotype.Service

@Service
class MyService(val dependency: Dependency) {
    // ...
}

Component Scanning:

Real-life Use Case: In a Spring Boot application, component scanning is often used to automatically discover and register beans (components, services, controllers, etc.) within a specified package or package. This eliminates the need for explicit bean definitions and helps in maintaining a modular and scalable application structure.

import org.springframework.stereotype.Component

@Component
class MyComponent {
    // ...
}

Setter Injection:

Real-life Use Case: Setter injection is useful when you need to inject dependencies after the bean is created. This is common in scenarios where circular dependencies exist or when you want to inject dependencies dynamically based on certain conditions during the application lifecycle.

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class MyService {

    lateinit var dependency: Dependency
        @Autowired set

    // ...
}

Field Injection: 

Real-life Use Case: Field injection is another way to inject dependencies into a class. It's concise and works well for simple scenarios. However, some argue that it makes testing more difficult compared to constructor injection because you can't easily provide mock dependencies during testing.

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class MyService @Autowired constructor(val dependency: Dependency) {
    // ...
}

Qualifiers:

Real-life Use Case: When you have multiple implementations of an interface or abstract class, qualifiers help Spring to identify which bean should be injected. For example, if you have multiple implementations of a PaymentProcessor interface, you can use qualifiers to specify which implementation to inject into a service.

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service

@Service
@Qualifier("specificImplementation")
class MyService : MyInterface {
    // ...
}

Primary:

Real-life Use Case: The @Primary annotation is used to indicate a primary bean when there are multiple beans of the same type. For example, if you have multiple implementations of a DataSource, you can mark one as @Primary to indicate the default choice.

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

@Service
@Primary
class PrimaryService : MyInterface {
    // ...
}

Lazy Initialization:

Real-life Use Case: Lazy initialization is beneficial when you want to defer the creation of a bean until it's actually needed. This can be useful for performance optimization, especially in scenarios where creating the bean is resource-intensive and may not be necessary for all application flows.

import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Component

@Lazy
@Component
class MyLazyComponent {
    // ...
}

Bean Scopes:

Real-life Use Case: Bean scopes define the lifecycle of a bean. For example, prototype scope creates a new instance every time it is requested, while singleton scope creates a single instance for the entire application context. Choosing the right scope depends on the nature of the bean and how it should be managed.

import org.springframework.context.annotation.Scope
import org.springframework.stereotype.Component

@Scope("prototype")
@Component
class MyPrototypeComponent {
    // ...
}

Bean Lifecycle Methods 

Real-life Use Case: Sometimes, you need to perform certain actions when a bean is created or destroyed. For instance, you might want to initialize resources or close connections during bean creation and destruction. Lifecycle methods such as @PostConstruct and @PreDestroy provide hooks for such operations.


import javax.annotation.PostConstruct
import javax.annotation.PreDestroy

@Service
class MyService {

    @PostConstruct
    fun init() {
        // Initialization logic
    }

    @PreDestroy
    fun destroy() {
        // Destruction logic
    }
}

Java Config Bean:

Real-life Use Case: Instead of relying on XML configuration, many Spring Boot applications use Java-based configuration. The @Configuration annotation marks a class as a configuration class, and @Bean methods define beans. This approach provides a programmatic and type-safe way to configure the Spring application context.

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

@Configuration
class MyConfig {

    @Bean
    fun myBean(): MyBean {
        return MyBean()
    }
}

Spring Boot Actuator Overview:

Spring Boot Actuator includes several built-in endpoints that provide useful information about your application, such as health, metrics, environment, etc. These endpoints can be accessed via HTTP, JMX, or other protocols.

Securing Endpoints:

Securing Actuator endpoints is crucial to prevent unauthorized access and exposure of sensitive information. Here are the common steps for securing Actuator endpoints:

Enable Security:

In your application.properties or application.yml file, enable Spring Security:

spring.security.user.name=admin
spring.security.user.password=admin-password


This sets up a basic authentication with a username and password.

Restrict Endpoints:

Configure which endpoints should be secured and set up access rules. By default, all endpoints are secured.

management.endpoints.web.exposure.include=health,info

this example includes only the health and info endpoints. Adjust the list based on your requirements.

Customize Security Rules:

Customize security rules using Java configuration. You can create a configuration class that extends WebSecurityConfigurerAdapter and override the configure(HttpSecurity http) method:

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


This example requires the ADMIN role for accessing any endpoint under /actuator/**.

Custom Authentication Provider:

If you need more complex authentication logic, you can create a custom authentication provider


import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.userdetails.UserDetails
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.authentication.UsernamePasswordAuthenticationFilter

@Configuration
class SecurityConfig {

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

    @Bean
    fun userDetailsService(): UserDetailsService {
        // Implement your UserDetailsService
    }

    @Bean
    fun authenticationProvider(): AuthenticationProvider {
        val provider = CustomAuthenticationProvider()
        provider.setUserDetailsService(userDetailsService())
        provider.setPasswordEncoder(passwordEncoder())
        return provider
    }
}

Adjust this according to your custom authentication logic.

Securing Sensitive Endpoints:

Some Actuator endpoints may contain sensitive information. Consider securing them more tightly or disabling them in production. For example, you might want to secure the env endpoint: 

management.endpoint.env.enabled=false


This disables the env endpoint in production.

Remember to adapt the security configuration based on your application's requirements and the sensitivity of the data exposed by Actuator endpoints. Always use secure practices, such as strong passwords, encryption, and proper access controls.


No comments:

Post a Comment

LeetCode C++ Cheat Sheet June

🎯 Core Patterns & Representative Questions 1. Arrays & Hashing Two Sum – hash map → O(n) Contains Duplicate , Product of A...