Skip to main content

Week 2 Day 5 — Bean Lifecycle

Goal

Today I want to understand the Spring bean lifecycle.

Main questions:

  1. What is the bean lifecycle?
  2. What happens when Spring creates a bean?
  3. When are dependencies injected?
  4. What is @PostConstruct?
  5. What is @PreDestroy?
  6. What are InitializingBean and DisposableBean?
  7. What are initMethod and destroyMethod in @Bean?
  8. What is BeanPostProcessor?
  9. How is BeanPostProcessor related to AOP and proxies?
  10. What are common exam traps?

1. Quick Review from Day 4

In Day 4, I learned:

  • Bean scope controls how long a bean lives.
  • The default scope is singleton.
  • Spring singleton means one bean instance per Spring container.
  • Singleton beans should usually be stateless.
  • Prototype scope creates a new instance each time the bean is requested.
  • Request scope creates one bean per HTTP request.
  • Session scope creates one bean per HTTP session.
  • A scoped proxy allows a singleton to depend on a request-scoped or session-scoped bean.

Memory sentence:

Singleton means one bean per Spring container.
Prototype means a new bean each time it is requested.

Today I learn what happens from bean creation to bean destruction.


2. What Is Bean Lifecycle?

Bean lifecycle means:

The steps a Spring bean goes through from creation to destruction.

Spring does not only create objects.

Spring can also:

1. Instantiate the bean
2. Inject dependencies
3. Set bean name
4. Set application context
5. Run post-processors
6. Run initialization callbacks
7. Create proxies
8. Use the bean
9. Run destruction callbacks
10. Destroy the bean when the context closes

Simple definition:

Bean lifecycle is the process Spring follows to create, configure, initialize, use, and destroy beans.


3. Simplified Bean Lifecycle

For the exam, remember this simplified order:

1. BeanDefinition is read
2. Bean is instantiated
3. Dependencies are injected
4. Aware callbacks run
5. BeanPostProcessor before initialization
6. Initialization callback runs
7. BeanPostProcessor after initialization
8. Bean is ready to use
9. Destruction callback runs when context closes

Memory sentence:

Create, inject, initialize, use, destroy.


4. Lifecycle as a Picture

BeanDefinition

Instantiate object

Inject dependencies

Aware callbacks

BeanPostProcessor before init

@PostConstruct / afterPropertiesSet / init method

BeanPostProcessor after init

Bean ready

Application uses bean

@PreDestroy / destroy / destroy method

Bean destroyed

5. Example Bean

@Service
public class EmailService {

private final EmailClient emailClient;

public EmailService(EmailClient emailClient) {
this.emailClient = emailClient;
}

@PostConstruct
public void init() {
System.out.println("EmailService initialized");
}

@PreDestroy
public void cleanup() {
System.out.println("EmailService destroyed");
}
}

Spring does this:

1. Creates EmailClient bean
2. Creates EmailService bean
3. Injects EmailClient through constructor
4. Calls init()
5. Uses EmailService
6. Calls cleanup() when context closes

6. Step 1 — BeanDefinition Is Registered

Before Spring creates the object, it registers a BeanDefinition.

Example:

@Service
public class TaskService {
}

Spring scans the class and creates metadata:

BeanDefinition:
- bean name: taskService
- class: TaskService
- scope: singleton
- dependencies: maybe TaskRepository
- lazy: false

Remember:

BeanDefinition = recipe
Bean = actual object

7. Step 2 — Bean Is Instantiated

Instantiation means:

Spring creates the Java object.

Example:

@Service
public class TaskService {
}

Spring internally creates something like:

new TaskService(...)

But this happens inside the container.

Important:

Instantiation creates the object, but dependencies may not yet be fully configured depending on injection style.

With constructor injection, dependencies are needed during construction.


8. Step 3 — Dependencies Are Injected

Spring injects dependencies.

Example:

@Service
public class TaskService {

private final TaskRepository taskRepository;

public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}
}

Spring finds a TaskRepository bean and injects it.

For constructor injection:

Dependency is provided during object creation.

For setter or field injection:

Object is created first, then dependencies are set.

9. Constructor Injection and Lifecycle

Constructor injection happens during instantiation.

Example:

public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}

Spring cannot create TaskService unless it can provide TaskRepository.

This is good because required dependencies are checked early.

Memory sentence:

Constructor injection makes required dependencies part of bean creation.


10. Field Injection and Lifecycle

Example:

@Service
public class TaskService {

@Autowired
private TaskRepository taskRepository;
}

Lifecycle:

1. Spring creates TaskService object.
2. Spring injects taskRepository field afterward.

This is one reason field injection is less ideal.

The object exists for a moment without required dependencies.


11. Step 4 — Aware Callbacks

Spring has special Aware interfaces.

They allow a bean to receive Spring infrastructure objects.

Examples:

BeanNameAware
BeanFactoryAware
ApplicationContextAware
EnvironmentAware
ResourceLoaderAware

Example:

@Component
public class MyBean implements BeanNameAware {

@Override
public void setBeanName(String name) {
System.out.println("Bean name is " + name);
}
}

This gives the bean its Spring bean name.


12. ApplicationContextAware

Example:

@Component
public class MyBean implements ApplicationContextAware {

private ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}

This gives the bean access to the ApplicationContext.

But be careful.

Usually this is not needed in normal business code.

Prefer constructor injection.


13. Should I Use Aware Interfaces Often?

Usually no.

Aware interfaces are useful for framework-level or infrastructure code.

In normal service code, prefer:

@Service
public class OrderService {

private final PaymentService paymentService;

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

Avoid unnecessary:

applicationContext.getBean(PaymentService.class)

Memory sentence:

Aware interfaces are powerful, but normal business code should prefer dependency injection.


14. Step 5 — BeanPostProcessor Before Initialization

A BeanPostProcessor can modify beans before and after initialization.

Before initialization:

postProcessBeforeInitialization()

This happens before:

@PostConstruct
InitializingBean.afterPropertiesSet()
custom init method

Conceptually:

Bean created
Dependencies injected
BeanPostProcessor before init
Initialization callback
BeanPostProcessor after init
Bean ready

15. Step 6 — Initialization Callbacks

Initialization means:

Run custom logic after dependencies are injected and before the bean is used.

There are several ways:

1. @PostConstruct
2. InitializingBean.afterPropertiesSet()
3. @Bean(initMethod = "...")

Most common in application code:

@PostConstruct

16. @PostConstruct

@PostConstruct marks a method that runs after dependency injection.

Example:

@Component
public class CacheLoader {

private final ProductRepository productRepository;

public CacheLoader(ProductRepository productRepository) {
this.productRepository = productRepository;
}

@PostConstruct
public void loadCache() {
System.out.println("Loading cache after dependencies are ready");
}
}

Spring calls loadCache() after dependencies are injected.

Memory sentence:

@PostConstruct runs after dependency injection.


17. When to Use @PostConstruct

Use @PostConstruct for initialization logic such as:

validating required configuration
warming up cache
logging startup information
checking external connection setup
initializing in-memory structures

Example:

@PostConstruct
public void validateConfig() {
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalStateException("API key is missing");
}
}

18. Be Careful with Heavy Work in @PostConstruct

Do not put too much heavy business logic in @PostConstruct.

Bad:

@PostConstruct
public void importAllCustomersFromExternalSystem() {
// huge long-running job
}

Why bad?

Application startup becomes slow.
Failure may stop the whole app.
It mixes startup lifecycle with business jobs.

Better:

Use ApplicationRunner, CommandLineRunner, scheduled jobs, or explicit admin actions.

19. @PostConstruct and Constructor

Question:

Why not put initialization logic directly in the constructor?

Because not all Spring features may be ready in the constructor.

In the constructor:

The object is being created.
Some Spring lifecycle steps have not happened yet.
AOP proxy is not ready yet.
@PostConstruct has not run yet.

For dependency-based initialization, @PostConstruct is often safer.

But keep it simple.


20. @PostConstruct Example with Config

@Component
public class ExternalApiClient {

private final ExternalApiProperties properties;

public ExternalApiClient(ExternalApiProperties properties) {
this.properties = properties;
}

@PostConstruct
public void init() {
System.out.println("External API URL: " + properties.baseUrl());
}
}

Here properties is already injected when init() runs.


21. InitializingBean

Spring also has an interface:

InitializingBean

Example:

@Component
public class CacheLoader implements InitializingBean {

@Override
public void afterPropertiesSet() {
System.out.println("Initialization after properties are set");
}
}

afterPropertiesSet() runs after dependency injection.


22. @PostConstruct vs InitializingBean

Topic@PostConstructInitializingBean
StyleannotationSpring interface
Coupling to Springlowerhigher
Method nameany method namemust be afterPropertiesSet
Common in app codeyesless common
Best fornormal app initializationSpring-specific infrastructure

Memory sentence:

@PostConstruct is usually preferred because it does not require implementing a Spring interface.


23. @Bean(initMethod = "...")

For beans created with @Bean, I can define an init method.

Example:

@Configuration
public class ClientConfig {

@Bean(initMethod = "connect")
public ExternalClient externalClient() {
return new ExternalClient();
}
}

Class:

public class ExternalClient {

public void connect() {
System.out.println("Connecting...");
}
}

Spring calls:

connect()

after creating the bean.


24. When to Use initMethod

Use initMethod when:

the class is from a third-party library
I cannot add @PostConstruct
the class already has an init method
I create the object with @Bean

Example:

@Bean(initMethod = "start")
public ExternalServerClient externalServerClient() {
return new ExternalServerClient();
}

25. Initialization Order

If several initialization mechanisms are used, the rough order is:

1. @PostConstruct
2. InitializingBean.afterPropertiesSet()
3. custom initMethod

For most application code, do not use all three together.

Use one clear approach.

Exam-level memory:

Initialization callbacks run after dependency injection and before the bean is ready for use.


26. Step 7 — BeanPostProcessor After Initialization

After initialization callbacks, Spring calls:

postProcessAfterInitialization()

This is very important because many Spring features use bean post-processors.

Examples:

AOP proxy creation
@Transactional processing
@Autowired processing
@PostConstruct processing
@ConfigurationProperties binding support

AOP proxies are often created after initialization.

Memory sentence:

BeanPostProcessors are extension points that can modify or wrap beans during creation.


27. What Is BeanPostProcessor?

BeanPostProcessor is an interface that lets Spring or custom code modify beans during the lifecycle.

Example:

@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("Before init: " + beanName);
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("After init: " + beanName);
return bean;
}
}

This runs for many beans in the context.


28. What Can BeanPostProcessor Do?

A BeanPostProcessor can:

inspect beans
modify bean properties
replace a bean with another object
wrap a bean with a proxy
trigger annotations
apply framework behavior

This is powerful.

It is also dangerous if used carelessly.


29. BeanPostProcessor and Proxies

A very important concept:

A BeanPostProcessor can return a proxy instead of the original bean.

Example:

Original bean: PaymentService
Returned bean: Transaction proxy wrapping PaymentService

So when another bean injects PaymentService, it may actually receive the proxy.

This is how many Spring features work.


30. BeanPostProcessor and @Transactional

When Spring sees:

@Service
public class PaymentService {

@Transactional
public void pay() {
}
}

Spring may create a proxy around PaymentService.

Conceptually:

PaymentService bean

BeanPostProcessor detects @Transactional

Creates proxy

Application uses proxy

The proxy opens and closes transactions around method calls.

We will study this more in AOP and transactions.


31. BeanPostProcessor and AOP

AOP depends heavily on post-processing.

Conceptually:

1. Spring creates bean.
2. Spring initializes bean.
3. A BeanPostProcessor checks if bean needs AOP.
4. If yes, Spring creates a proxy.
5. Other beans receive the proxy.

This is why lifecycle matters for certification.

Memory sentence:

AOP proxies are often created by BeanPostProcessors.


32. Step 8 — Bean Is Ready

After initialization and post-processing, the bean is ready.

Other beans can use it.

Example:

@RestController
public class PaymentController {

private final PaymentService paymentService;

public PaymentController(PaymentService paymentService) {
this.paymentService = paymentService;
}
}

The injected paymentService may be:

real PaymentService

or:

PaymentService proxy

depending on Spring features like AOP, transactions, or security.


33. Step 9 — Destruction Callbacks

When the application context closes, Spring destroys singleton beans.

Destruction callbacks include:

1. @PreDestroy
2. DisposableBean.destroy()
3. @Bean(destroyMethod = "...")

Most common:

@PreDestroy

34. @PreDestroy

@PreDestroy marks a method that runs before the bean is destroyed.

Example:

@Component
public class ExternalApiClient {

@PreDestroy
public void shutdown() {
System.out.println("Closing external API client");
}
}

Spring calls this when the application context shuts down.

Memory sentence:

@PreDestroy runs before Spring destroys the bean.


35. When to Use @PreDestroy

Use it for cleanup:

closing resources
stopping background workers
flushing buffers
closing connections
releasing locks
shutting down custom clients

Example:

@PreDestroy
public void cleanup() {
connection.close();
}

36. DisposableBean

Spring has an interface:

DisposableBean

Example:

@Component
public class ExternalClient implements DisposableBean {

@Override
public void destroy() {
System.out.println("Destroying bean");
}
}

This works, but it couples the class to Spring.

Usually prefer:

@PreDestroy

37. @Bean(destroyMethod = "...")

For third-party classes:

@Configuration
public class ClientConfig {

@Bean(destroyMethod = "close")
public ExternalClient externalClient() {
return new ExternalClient();
}
}

Spring calls:

close()

when the context shuts down.

Useful when the class has a method like:

close()
shutdown()
stop()
destroy()

38. @PostConstruct and @PreDestroy Example

@Component
public class FileStorageClient {

public FileStorageClient() {
System.out.println("Constructor");
}

@PostConstruct
public void init() {
System.out.println("Init after dependencies");
}

@PreDestroy
public void destroy() {
System.out.println("Cleanup before shutdown");
}
}

Expected order:

Constructor
Init after dependencies
Application runs
Cleanup before shutdown

39. Prototype Beans and Destruction

Important trap:

Spring does not manage the full destruction lifecycle of prototype beans.

Example:

@Component
@Scope("prototype")
public class ImportContext {

@PreDestroy
public void cleanup() {
System.out.println("cleanup");
}
}

Spring creates and initializes the prototype bean.

But when the application context closes, Spring does not automatically call @PreDestroy for every prototype it created.

Why?

Because Spring gives the prototype object to the caller and no longer tracks it in the same way.

Memory sentence:

Prototype beans are created by Spring, but cleanup is the caller’s responsibility.


40. Singleton Beans and Destruction

For singleton beans, Spring usually manages destruction.

Example:

@Component
public class S3Client {

@PreDestroy
public void close() {
System.out.println("Closing S3 client");
}
}

When the context closes, Spring calls close().


41. Web Scopes and Destruction

For request-scoped beans:

Destruction happens at the end of the HTTP request.

For session-scoped beans:

Destruction happens when the HTTP session ends.

For singleton beans:

Destruction happens when the application context closes.

For prototype beans:

Spring does not automatically manage destruction.

42. Lifecycle Order Summary

High-level order:

1. Instantiate
2. Inject dependencies
3. Aware callbacks
4. BeanPostProcessor before initialization
5. Initialization callbacks
6. BeanPostProcessor after initialization
7. Bean ready
8. Destruction callbacks

Initialization callback examples:

@PostConstruct
InitializingBean.afterPropertiesSet()
@Bean(initMethod = "...")

Destruction callback examples:

@PreDestroy
DisposableBean.destroy()
@Bean(destroyMethod = "...")

43. Real Example: External Client

Third-party class:

public class TaxApiClient {

public void connect() {
System.out.println("Connecting to tax API");
}

public void close() {
System.out.println("Closing tax API client");
}
}

Configuration:

@Configuration
public class TaxApiConfig {

@Bean(initMethod = "connect", destroyMethod = "close")
public TaxApiClient taxApiClient() {
return new TaxApiClient();
}
}

Spring lifecycle:

1. Create TaxApiClient
2. Call connect()
3. Use TaxApiClient
4. On shutdown, call close()

44. Real Example: Validate Configuration at Startup

@Component
public class JwtConfigValidator {

private final JwtProperties jwtProperties;

public JwtConfigValidator(JwtProperties jwtProperties) {
this.jwtProperties = jwtProperties;
}

@PostConstruct
public void validate() {
if (jwtProperties.secret().length() < 32) {
throw new IllegalStateException("JWT secret is too short");
}
}
}

If the configuration is invalid, the application fails at startup.

This can be good because bad config is detected early.


45. Real Example: Log Active Configuration Safely

@Component
public class StartupLogger {

private final AppProperties appProperties;

public StartupLogger(AppProperties appProperties) {
this.appProperties = appProperties;
}

@PostConstruct
public void logStartupInfo() {
System.out.println("App started: " + appProperties.name());
}
}

Important:

Do not log secrets.

Good to log:

app name
active feature flags
non-sensitive URLs
profile
version

Bad to log:

passwords
tokens
JWT secrets
API keys
private credentials

46. @PostConstruct and Proxies

Important advanced trap:

@PostConstruct runs during bean initialization.

AOP proxy behavior may not work the same inside @PostConstruct.

Example:

@Service
public class PaymentService {

@PostConstruct
public void init() {
pay();
}

@Transactional
public void pay() {
// transaction logic
}
}

Calling pay() from inside the same class is self-invocation.

It may not go through the proxy.

So @Transactional may not work as expected.

Memory sentence:

Do not rely on proxy-based annotations during self-calls in @PostConstruct.

We will study this deeply in AOP and transactions.


47. @PostConstruct and Self-Invocation

Bad pattern:

@PostConstruct
public void init() {
transactionalMethod();
}

Why bad?

The call is inside the same class.
It may bypass the Spring proxy.
@Transactional, @Async, @Cacheable, or security annotations may not apply.

Better:

Use ApplicationRunner, event listener, or move logic to another bean if proxy behavior is required.

48. Lifecycle and Lazy Beans

By default, singleton beans are created eagerly at startup.

But if a bean is lazy:

@Component
@Lazy
public class HeavyService {
}

Spring creates it only when first needed.

Then its lifecycle starts at that later time.

Important:

@PostConstruct runs when the lazy bean is actually created.

Not necessarily at application startup.


49. @Lazy Example

@Component
@Lazy
public class ReportGenerator {

@PostConstruct
public void init() {
System.out.println("ReportGenerator initialized");
}
}

If no other bean needs ReportGenerator at startup, it may not be created immediately.

When it is first requested, Spring creates it and runs @PostConstruct.


50. Common Exam Traps

Trap 1

@PostConstruct runs after dependency injection, not before.


Trap 2

@PreDestroy runs before Spring destroys the bean.


Trap 3

Spring does not automatically call destroy callbacks for prototype beans.


Trap 4

InitializingBean and DisposableBean work, but they couple code to Spring.


Trap 5

@Bean(initMethod = "...") and @Bean(destroyMethod = "...") are useful for third-party classes.


Trap 6

BeanPostProcessor can modify or wrap beans.


Trap 7

AOP proxies are often created by BeanPostProcessors.


Trap 8

The object injected into another bean may be a proxy, not the original object.


Trap 9

Do not rely on @Transactional self-invocation inside @PostConstruct.


Trap 10

Lazy beans are initialized when first requested, not necessarily at startup.


51. Real Exam Question: @PostConstruct

Question:

When does a method annotated with @PostConstruct run?

Answer:

It runs after the bean has been created and dependencies have been injected, but before the bean is ready for normal use.


52. Real Exam Question: @PreDestroy

Question:

When does a method annotated with @PreDestroy run?

Answer:

It runs before the bean is destroyed, usually when the application context is closing for singleton beans.


53. Real Exam Question: Prototype Destruction

Question:

Does Spring call @PreDestroy automatically for prototype beans?

Answer:

No. Spring creates and initializes prototype beans, but it does not automatically manage their destruction. The caller is responsible for cleanup.


54. Real Exam Question: InitializingBean

Question:

What does InitializingBean.afterPropertiesSet() do?

Answer:

It is an initialization callback that Spring calls after bean properties and dependencies have been set.


55. Real Exam Question: DisposableBean

Question:

What does DisposableBean.destroy() do?

Answer:

It is a destruction callback that Spring calls when the bean is being destroyed, usually when the application context closes.


56. Real Exam Question: @Bean(initMethod)

Question:

@Bean(initMethod = "connect")
public ExternalClient externalClient() {
return new ExternalClient();
}

When does connect() run?

Answer:

Spring calls connect() after creating and configuring the bean, during initialization.


57. Real Exam Question: @Bean(destroyMethod)

Question:

@Bean(destroyMethod = "close")
public ExternalClient externalClient() {
return new ExternalClient();
}

When does close() run?

Answer:

Spring calls close() when the bean is destroyed, usually when the application context closes.


58. Real Exam Question: BeanPostProcessor

Question:

What is a BeanPostProcessor?

Answer:

A BeanPostProcessor is a Spring extension point that can modify, inspect, or wrap beans before and after initialization. It can also return a proxy instead of the original bean.


59. Real Exam Question: AOP and BeanPostProcessor

Question:

How are AOP proxies related to BeanPostProcessor?

Answer:

AOP proxies are often created by BeanPostProcessors after bean initialization. The post-processor can wrap the original bean in a proxy that adds behavior such as transactions, security, caching, or logging.


60. Real Exam Question: Lifecycle Order

Question:

Which happens first: dependency injection or @PostConstruct?

Answer:

Dependency injection happens first. @PostConstruct runs after dependencies have been injected.


61. Real Exam Question: Lazy Bean

Question:

When does @PostConstruct run for a lazy bean?

Answer:

It runs when the lazy bean is actually created, which may be later than application startup.


62. Real Exam Question: Constructor vs @PostConstruct

Question:

Why might initialization logic be placed in @PostConstruct instead of the constructor?

Answer:

Because @PostConstruct runs after Spring has injected dependencies and completed more of the bean setup. The constructor runs while the object is still being created.


63. Interview Answer

Question:

What is the Spring bean lifecycle?

Good answer:

The Spring bean lifecycle is the process a bean goes through from creation to destruction. Spring reads the bean definition, instantiates the bean, injects dependencies, calls aware callbacks, applies BeanPostProcessors, runs initialization callbacks such as @PostConstruct, and then the bean is ready to use. When the application context closes, Spring runs destruction callbacks such as @PreDestroy for singleton beans.


64. Interview Answer

Question:

What is the difference between @PostConstruct and @PreDestroy?

Good answer:

@PostConstruct marks a method that runs after dependency injection and before the bean is used. It is used for initialization logic. @PreDestroy marks a method that runs before the bean is destroyed, usually when the application context is closing. It is used for cleanup logic such as closing resources or stopping background tasks.


65. Interview Answer

Question:

What is a BeanPostProcessor?

Good answer:

A BeanPostProcessor is an extension point in Spring that can modify beans before and after initialization. It can inspect beans, change them, or wrap them with proxies. Many Spring features use BeanPostProcessors internally, including support for lifecycle annotations and AOP proxy creation.


66. Interview Answer

Question:

Why are BeanPostProcessors important for AOP?

Good answer:

BeanPostProcessors are important for AOP because they can wrap a bean with a proxy after the bean has been created and initialized. That proxy can add behavior around method calls, such as transactions, security checks, caching, or logging. This is why a bean injected into another bean may actually be a proxy rather than the original object.


67. Interview Answer

Question:

Does Spring manage prototype bean destruction?

Good answer:

Spring creates and initializes prototype beans, but it does not automatically manage their full destruction lifecycle. After Spring returns a prototype bean to the caller, the caller is responsible for cleanup. This is different from singleton beans, where Spring calls destruction callbacks when the application context closes.


68. Tiny Code Practice

Create this bean:

@Component
public class LifecycleDemoBean {

public LifecycleDemoBean() {
System.out.println("1. Constructor");
}

@PostConstruct
public void init() {
System.out.println("2. PostConstruct");
}

@PreDestroy
public void destroy() {
System.out.println("3. PreDestroy");
}
}

Expected output conceptually:

Application startup:
1. Constructor
2. PostConstruct

Application shutdown:
3. PreDestroy

Question:

Why does @PostConstruct run after the constructor?

Answer:

Because the constructor creates the object first. After that, Spring injects dependencies and runs initialization callbacks such as @PostConstruct.


69. Tiny Bug Practice

Problem:

@Service
public class StartupService {

@PostConstruct
public void init() {
runInTransaction();
}

@Transactional
public void runInTransaction() {
// database logic
}
}

Question:

What is the problem?

Answer:

init() calls runInTransaction() inside the same class. This is self-invocation and may bypass the Spring proxy. Because @Transactional is proxy-based, the transaction may not be applied as expected.

Better options:

Move transactional logic to another bean.
Use ApplicationRunner.
Use an application event listener.
Avoid proxy-based self-calls in @PostConstruct.

Practice Questions and Answers

Question 1

What is the bean lifecycle?

Answer:

The bean lifecycle is the process Spring follows to create, configure, initialize, use, and destroy a bean.


Question 2

What is the simplified lifecycle order?

Answer:

The simplified order is: instantiate the bean, inject dependencies, run aware callbacks, run BeanPostProcessor before initialization, run initialization callbacks, run BeanPostProcessor after initialization, use the bean, and finally run destruction callbacks.


Question 3

When are dependencies injected?

Answer:

Dependencies are injected after or during bean instantiation depending on injection style. With constructor injection, dependencies are provided during construction. With field or setter injection, they are injected after object creation.


Question 4

What does @PostConstruct do?

Answer:

@PostConstruct marks a method that Spring should call during bean initialization.


Question 5

When does @PostConstruct run?

Answer:

@PostConstruct runs after the bean has been created and dependencies have been injected.


Question 6

What does @PreDestroy do?

Answer:

@PreDestroy marks a method that Spring should call before destroying the bean.


Question 7

When does @PreDestroy run?

Answer:

@PreDestroy runs when the bean is being destroyed, usually when the application context closes for singleton beans.


Question 8

What is InitializingBean?

Answer:

InitializingBean is a Spring interface with the method afterPropertiesSet(), which runs after dependencies and properties have been set.


Question 9

What is DisposableBean?

Answer:

DisposableBean is a Spring interface with the method destroy(), which runs when the bean is destroyed.


Question 10

When should I use @Bean(initMethod = "...")?

Answer:

Use @Bean(initMethod = "...") when creating a bean from a third-party class or a class where I cannot add @PostConstruct, and the class has an initialization method.


Question 11

When should I use @Bean(destroyMethod = "...")?

Answer:

Use @Bean(destroyMethod = "...") when the bean has a cleanup method like close(), shutdown(), or stop() that should run when the context closes.


Question 12

What is BeanPostProcessor?

Answer:

BeanPostProcessor is a Spring extension point that can inspect, modify, or wrap beans before and after initialization.


Question 13

What can a BeanPostProcessor do?

Answer:

A BeanPostProcessor can modify bean properties, inspect beans, replace beans, or wrap beans with proxies.


Question 14

How is BeanPostProcessor related to AOP?

Answer:

AOP proxies are often created by BeanPostProcessors. A post-processor can wrap a bean with a proxy that adds behavior such as transactions, security, caching, or logging.


Question 15

Does Spring automatically destroy prototype beans?

Answer:

No. Spring creates and initializes prototype beans, but it does not automatically manage their destruction.


Question 16

Why is @PostConstruct often better than constructor initialization?

Answer:

@PostConstruct runs after dependency injection, so dependencies are ready. The constructor runs while the object is still being created.


Question 17

Why should I be careful calling @Transactional methods from @PostConstruct?

Answer:

Because calling another method in the same class is self-invocation and may bypass the Spring proxy. Proxy-based features such as @Transactional may not apply.


Question 18

When does @PostConstruct run for a lazy bean?

Answer:

For a lazy bean, @PostConstruct runs when the bean is actually created, which may be later than application startup.


Question 19

What are Aware interfaces?

Answer:

Aware interfaces are Spring interfaces that allow a bean to receive Spring infrastructure objects, such as its bean name, the ApplicationContext, or the environment.


Question 20

Should normal business code often use ApplicationContextAware?

Answer:

Usually no. Normal business code should prefer constructor injection instead of directly depending on the ApplicationContext.

Final Memory Sentences

  • Bean lifecycle means create, inject, initialize, use, and destroy.
  • @PostConstruct runs after dependency injection.
  • @PreDestroy runs before bean destruction.
  • InitializingBean.afterPropertiesSet() is an initialization callback.
  • DisposableBean.destroy() is a destruction callback.
  • @Bean(initMethod = "...") is useful for third-party initialization.
  • @Bean(destroyMethod = "...") is useful for third-party cleanup.
  • BeanPostProcessor can modify or wrap beans before and after initialization.
  • AOP proxies are often created by BeanPostProcessors.
  • A bean injected into another bean may actually be a proxy.
  • Spring manages destruction callbacks for singleton beans.
  • Spring does not automatically destroy prototype beans.
  • Lazy beans are initialized when first requested.
  • Avoid relying on proxy-based behavior during self-invocation in @PostConstruct.