Skip to content

Spring Core Concepts

Spring Framework Core Principles

1. Inversion of Control (IoC)

Container manages object lifecycle and dependencies instead of application code.

// Without IoC - tight coupling
public class OrderService {
    private PaymentService paymentService = new StripePaymentService();
}

// With IoC - loose coupling
public class OrderService {
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;  // Injected
    }
}

2. Dependency Injection (DI)

Three types: Constructor (preferred), Setter, Field

@Service
public class OrderService {
    private final OrderRepository repository;
    private final PaymentService paymentService;

    // Constructor injection - immutable, testable
    public OrderService(OrderRepository repository, PaymentService paymentService) {
        this.repository = repository;
        this.paymentService = paymentService;
    }
}

3. Aspect-Oriented Programming (AOP)

Modularize cross-cutting concerns (logging, security, transactions).

@Aspect
@Component
public class LoggingAspect {
    @Around("@annotation(Loggable)")
    public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        log.info("{} executed in {}ms", joinPoint.getSignature(),
                 System.currentTimeMillis() - start);
        return result;
    }
}

Bean Lifecycle

Bean Lifecycle


Bean Scopes

Bean Scopes

@Component
@Scope(value = "prototype")
public class ShoppingCart { }

// Inject prototype into singleton
@Service
public class CheckoutService {
    @Autowired
    private ObjectFactory<ShoppingCart> cartFactory;

    public void checkout() {
        ShoppingCart cart = cartFactory.getObject();  // New instance
    }
}

Spring MVC Request Lifecycle

Spring MVC Request Flow


Transaction Management

Propagation Types

Transaction Propagation

Isolation Levels

Transaction Isolation Levels

@Transactional(
    propagation = Propagation.REQUIRED,
    isolation = Isolation.READ_COMMITTED,
    timeout = 30,
    readOnly = false,
    rollbackFor = {BusinessException.class}
)
public void processOrder(Order order) { }

Spring Boot Auto-Configuration

Auto-Configuration Process


Configuration Priority

Configuration Priority


Exception Handling

@ControllerAdvice

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
        return new ErrorResponse("NOT_FOUND", ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleValidation(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getFieldErrors().stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .toList();
        return new ErrorResponse("VALIDATION_ERROR", String.join(", ", errors));
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleGeneral(Exception ex) {
        log.error("Unhandled exception", ex);
        return new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred");
    }
}

Caching

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users", "products");
    }
}

@Service
public class UserService {

    @Cacheable(value = "users", key = "#id")
    public User getUser(Long id) {
        return userRepository.findById(id).orElseThrow();
    }

    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }

    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    @CacheEvict(value = "users", allEntries = true)
    public void clearCache() { }
}

Async Processing

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}

@Service
public class NotificationService {

    @Async
    public void sendEmail(String to, String subject, String body) {
        // Runs in separate thread
        emailService.send(to, subject, body);
    }

    @Async
    public CompletableFuture<Report> generateReport(Long id) {
        Report report = reportService.generate(id);
        return CompletableFuture.completedFuture(report);
    }
}

Scheduling

@Configuration
@EnableScheduling
public class SchedulingConfig { }

@Service
public class ReportService {

    @Scheduled(fixedRate = 60000)  // Every 60 seconds
    public void generateHourlyReport() { }

    @Scheduled(fixedDelay = 60000)  // 60 seconds after last completion
    public void processQueue() { }

    @Scheduled(cron = "0 0 1 * * ?")  // Daily at 1 AM
    public void dailyCleanup() { }

    @Scheduled(cron = "0 */5 * * * ?")  // Every 5 minutes
    public void syncData() { }
}

Events

// Custom event
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
}

// Publishing
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public Order create(CreateOrderRequest request) {
        Order order = orderRepository.save(new Order(request));
        eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
        return order;
    }
}

// Listening
@Component
public class OrderEventListener {

    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        notificationService.notify(event.getOrder());
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void afterOrderCommitted(OrderCreatedEvent event) {
        // Only after transaction commits
    }

    @EventListener
    @Async
    public void handleAsync(OrderCreatedEvent event) {
        // Async processing
    }
}

Validation

@RestController
public class UserController {

    @PostMapping("/users")
    public User create(@Valid @RequestBody CreateUserRequest request) {
        return userService.create(request);
    }
}

public record CreateUserRequest(
    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 100)
    String name,

    @NotBlank
    @Email(message = "Invalid email format")
    String email,

    @NotNull
    @Min(18)
    @Max(120)
    Integer age,

    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$")
    String phone,

    @Valid  // Validate nested object
    Address address
) {}

// Custom validator
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
    String message() default "Email already exists";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
    @Autowired
    private UserRepository userRepository;

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        return email == null || !userRepository.existsByEmail(email);
    }
}

RestTemplate vs WebClient

RestTemplate vs WebClient

// RestTemplate
@Bean
public RestTemplate restTemplate() {
    return new RestTemplateBuilder()
        .setConnectTimeout(Duration.ofSeconds(5))
        .setReadTimeout(Duration.ofSeconds(10))
        .build();
}

User user = restTemplate.getForObject("/users/{id}", User.class, id);

// WebClient
@Bean
public WebClient webClient() {
    return WebClient.builder()
        .baseUrl("https://api.example.com")
        .defaultHeader("Authorization", "Bearer " + token)
        .build();
}

// Blocking
User user = webClient.get()
    .uri("/users/{id}", id)
    .retrieve()
    .bodyToMono(User.class)
    .block();

// Non-blocking
Mono<User> userMono = webClient.get()
    .uri("/users/{id}", id)
    .retrieve()
    .bodyToMono(User.class);

Profiles

@Configuration
@Profile("development")
public class DevConfig {
    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder().setType(H2).build();
    }
}

@Configuration
@Profile("production")
public class ProdConfig {
    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url(System.getenv("DATABASE_URL"))
            .build();
    }
}

// Profile expressions
@Profile("!production")           // NOT production
@Profile("development | test")    // development OR test
@Profile("cloud & aws")           // cloud AND aws

Spring vs Spring Boot

Spring vs Spring Boot


  • *