Skip to main content

Spring Framework: Deep Dive

This page covers advanced Spring Framework concepts including the bean lifecycle, AOP, data access patterns, reactive programming, and batch processing.


Spring Bean Lifecycle

Understanding the bean lifecycle is crucial for optimizing resource management in large-scale applications.

Lifecycle Phases

Container Start
→ Bean Definition Loading
→ Bean Instantiation
→ Dependency Injection
→ @PostConstruct / InitializingBean.afterPropertiesSet()
→ Custom init-method
→ Bean Ready for Use
→ @PreDestroy / DisposableBean.destroy()
→ Custom destroy-method
→ Bean Destroyed

Lifecycle Callbacks

CallbackMechanismWhen It Runs
@PostConstructAnnotationAfter dependency injection is complete
InitializingBean.afterPropertiesSet()InterfaceAfter all properties are set
Custom init-methodXML/annotation configAfter afterPropertiesSet()
@PreDestroyAnnotationBefore bean is removed from container
DisposableBean.destroy()InterfaceDuring container shutdown
Custom destroy-methodXML/annotation configAfter destroy()
@Component
public class DataSourceManager {

@PostConstruct
public void init() {
// Initialize connection pool
}

@PreDestroy
public void cleanup() {
// Close connections gracefully
}
}

ApplicationContext vs BeanFactory

FeatureBeanFactoryApplicationContext
Bean InstantiationLazy (on demand)Eager (at startup)
Event PropagationNoYes (ApplicationEvent)
AOP IntegrationManualBuilt-in
Internationalization (i18n)NoYes (MessageSource)
Web Context SupportNoYes (WebApplicationContext)
Resource LoadingBasicAdvanced (ResourceLoader)
Recommended ForLow-memory / embedded systemsEnterprise applications
// BeanFactory (basic)
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));

// ApplicationContext (preferred)
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

Circular Dependencies

A circular dependency occurs when two or more beans depend on each other:

Bean A → requires Bean B → requires Bean A → deadlock!

Resolution Strategies

StrategyHow It Works
Setter InjectionAllows beans to be instantiated before dependencies are set
@Lazy AnnotationDefers bean initialization until actually needed, breaking the cycle
Redesign ArchitectureIntroduce an interface or third bean to decouple
@Component
public class ServiceA {
private ServiceB serviceB;

@Autowired
@Lazy
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}

Stereotype Annotations

AnnotationLayerPurpose
@ComponentGenericAny Spring-managed component
@ServiceServiceBusiness logic and service tasks
@RepositoryData AccessDatabase interaction, exception translation
@ControllerPresentationWeb request handling (MVC)
@RestControllerPresentationRESTful web services (@Controller + @ResponseBody)

@Component, @Service, @Repository, and @Controller are technically interchangeable — they all register beans. However, using the correct stereotype improves code clarity and enables layer-specific features (e.g., @Repository adds persistence exception translation).


Data Access: JpaRepository vs CrudRepository

FeatureCrudRepositoryJpaRepository
CRUD OperationsYesYes (inherited)
Pagination & SortingNoYes
Batch OperationsNoYes (saveAll, deleteInBatch)
Flush Persistence ContextNoYes (flush(), saveAndFlush())
Best ForSimple data accessFull JPA capabilities
// CrudRepository — basic CRUD
public interface UserRepository extends CrudRepository<User, Long> {
}

// JpaRepository — full JPA features
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByStatus(OrderStatus status);
}

@Qualifier vs @Primary

When multiple beans of the same type exist, Spring needs to know which one to inject.

@Configuration
public class DataSourceConfig {

@Bean
@Primary // Default choice when no qualifier specified
public DataSource primaryDataSource() {
return new HikariDataSource(primaryConfig());
}

@Bean
@Qualifier("reporting")
public DataSource reportingDataSource() {
return new HikariDataSource(reportingConfig());
}
}

@Service
public class ReportService {
// Uses the @Qualifier to pick a specific bean
public ReportService(@Qualifier("reporting") DataSource dataSource) {
// ...
}
}
AnnotationBehavior
@PrimaryMarks a bean as the default when multiple candidates exist
@QualifierExplicitly selects a specific bean by name

@Transactional

The @Transactional annotation defines the scope of a database transaction. All operations within the annotated method either succeed or fail together.

@Service
public class TransferService {

@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountRepository.debit(fromId, amount);
accountRepository.credit(toId, amount);
// If credit fails, debit is rolled back
}
}

Key Attributes

AttributePurposeDefault
propagationHow transactions relate to each otherREQUIRED
isolationTransaction isolation levelDatabase default
readOnlyHint for optimization on read-only operationsfalse
rollbackForExceptions that trigger rollbackUnchecked exceptions
timeoutMaximum time for the transactionNo timeout

Aspect-Oriented Programming (AOP)

AOP modularizes cross-cutting concerns — functionality that spans multiple classes like logging, security, and transaction management.

Core Concepts

ConceptDescription
AspectA module encapsulating a cross-cutting concern
Join PointA specific point in execution (e.g., method call, field access)
PointcutAn expression that selects one or more join points
AdviceCode that runs at a selected join point (before, after, around)
WeavingProcess of linking aspects with target objects

Join Point vs Pointcut

  • Join Point = the actual location in the program where an aspect can be applied
  • Pointcut = the expression that determines which join points the advice applies to
@Aspect
@Component
public class LoggingAspect {

// Pointcut: selects all methods in service package
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}

// Advice: runs before the selected join points
@Before("serviceMethods()")
public void logMethodEntry(JoinPoint joinPoint) {
log.info("Entering: {}", joinPoint.getSignature().getName());
}
}

AOP Disadvantages

  • Makes execution flow harder to trace and debug
  • Modularized code runs separately from the main application flow
  • Can introduce unexpected behavior if pointcut expressions are too broad

Spring WebFlux vs Spring MVC

AspectSpring MVCSpring WebFlux
Programming ModelSynchronous, blockingAsynchronous, non-blocking
ConcurrencyThread-per-requestEvent-loop (fewer threads)
Built OnServlet APIProject Reactor
Best ForTraditional web appsHigh-concurrency, streaming
ServerTomcat, JettyNetty, Undertow
// Spring MVC (blocking)
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}

// Spring WebFlux (reactive)
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable Long id) {
return userService.findById(id);
}

Spring Batch

Spring Batch is a framework for processing large volumes of data efficiently — ideal for data migration, report generation, and scheduled jobs.

Architecture

Job
└── Step 1
│ ├── ItemReader → reads data (DB, file, API)
│ ├── ItemProcessor → applies business logic
│ └── ItemWriter → writes processed data
└── Step 2
└── Tasklet → single operation step

Key Components

ComponentRole
JobDefines the entire batch process
StepA single phase within a job
ItemReaderReads input data
ItemProcessorTransforms data
ItemWriterWrites output data
JobRepositoryStores metadata about job executions

Testing: @Mock vs @Spy

AnnotationBehaviorUse Case
@MockFully mocked instance; no real code executesIsolating dependencies in unit tests
@SpyPartial mock wrapping a real instance; real methods execute unless overriddenTesting with some real behavior
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

@Mock
private PaymentGateway paymentGateway; // Fully mocked

@Spy
private OrderValidator orderValidator; // Real logic, selectively stubbed

@InjectMocks
private OrderService orderService;

@Test
void shouldProcessOrder() {
when(paymentGateway.charge(any())).thenReturn(true);
doReturn(true).when(orderValidator).validate(any()); // Override one method

orderService.process(new Order());
verify(paymentGateway).charge(any());
}
}

Configuration: Annotations vs XML

AspectAnnotationsXML
ReadabilityConcise, inline with codeVerbose, separate files
MaintenanceEasier — part of the codebaseHarder — separate from code
FlexibilityRequires recompilation for changesCan be modified without recompilation
Complex ConfigCan get clutteredBetter for complex wiring
Best ForMost modern projectsLegacy systems, external config needs

Best practice: Use annotations for most configurations. Reserve XML for cases where external configuration without recompilation is required.


Auto-Configuration Conflicts

When multiple @AutoConfiguration classes define the same bean, the last one loaded takes precedence. Control ordering with:

AnnotationPurpose
@AutoConfigureOrderSet explicit ordering priority
@AutoConfigureAfterLoad after a specific auto-configuration
@AutoConfigureBeforeLoad before a specific auto-configuration
@ConditionalOnMissingBeanOnly create bean if it doesn't already exist
@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class CustomDataSourceConfig {

@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return new CustomDataSource();
}
}