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 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¶
Transaction Management¶
Propagation Types¶
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¶
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
@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¶
- *