🍃 Section 5 · Question #4

What is a Bean in Spring

In the Spring Framework, a Bean is any Java object that is instantiated, assembled, configured, and managed throughout its entire lifecycle by the Spring Inversion of Control (I...


🟢 Junior Level

1. What is a Spring Bean?

In the Spring Framework, a Bean is any Java object that is instantiated, assembled, configured, and managed throughout its entire lifecycle by the Spring Inversion of Control (IoC) Container (represented by ApplicationContext).

In traditional Java programming, developers manage object instantiation and lifecycle manually:

// Regular Java Object (POJO): Developer manually controls instantiation via `new`
User user = new User("Alice");

In Spring, object creation and wiring are inverted and delegated to the framework:

// Spring Bean: Managed, instantiated, and wired by the IoC Container
@Service
public class UserService {
    private final UserRepository repository;

    public UserService(UserRepository repository) {
        this.repository = repository;
    }
}

2. Regular Java Object (POJO) vs Spring Bean

A Spring Bean is technically a standard Java class, but the container enhances it with enterprise capabilities:

Feature Regular Java Object (POJO) Spring Managed Bean
Creation Explicitly by developer via new Automatically by Spring IoC Container
Dependency Resolution Manual argument passing Automatic Dependency Injection (@Autowired)
Lifecycle Hooks Manual method calls Automated callbacks (@PostConstruct, @PreDestroy)
Enterprise Aspects None (pure plain code) Enhanced via Dynamic Proxies (@Transactional, @Async)
Scope Management Controlled by variable reference scope Managed by container (singleton, prototype, request)

🟡 Middle Level

1. The Blueprint: BeanDefinition

Before a bean physically exists in the JVM heap, Spring scans application configuration and constructs a metadata blueprint called BeanDefinition:

Configuration Source (@Component, @Bean, or XML)
                      │
                      ▼
               [BeanDefinition]
  ├── Target Class Name (`com.example.service.UserService`)
  ├── Scope (`singleton`, `prototype`, `request`, etc.)
  ├── Constructor Arguments & Injected Property Values
  ├── Primary & Qualifier Tags
  ├── Initialization & Destruction Method Names
  └── Lazy Initialization Flag (`lazyInit = true/false`)
                      │
                      ▼
            [Instantiation Phase] ──► Real Bean Instance in Heap

BeanDefinition acts as a manufacturing specification. The BeanFactory reads this specification to instantiate and wire the physical bean. Developers can inspect or alter these specifications prior to instantiation by implementing BeanFactoryPostProcessor (e.g., PropertySourcesPlaceholderConfigurer which resolves ${...} property placeholders).


2. The 3 Primary Ways to Declare Beans

  1. Stereotype Annotations (Classpath Scanning):
    • @Component: Generic foundational stereotype.
    • @Service: Business logic and transaction orchestration layer.
    • @Repository: Persistence layer (includes automatic SQL exception translation into Spring’s DataAccessException hierarchy).
    • @Controller / @RestController: Web presentation layer for Spring MVC and REST endpoints.
  2. Explicit Java-Based Configuration (@Configuration + @Bean):
    • Used for instantiating and configuring third-party library classes that cannot be directly annotated with @Component (e.g., ObjectMapper, DataSource, WebClient):
      @Configuration
      public class JacksonConfig {
        @Bean
        public ObjectMapper objectMapper() {
            return new ObjectMapper().findAndRegisterModules();
        }
      }
      
  3. Programmatic Functional Registration (Spring 5+):
    • Programmatically registers beans at runtime via functional lambdas using GenericApplicationContext.registerBean(...).

🔴 Senior Level

1. Internal Storage: DefaultListableBeanFactory

The engine powering Spring’s IoC container is DefaultListableBeanFactory. It stores metadata and instances in high-performance thread-safe structures (ConcurrentHashMap):

  • beanDefinitionMap (Map<String, BeanDefinition>): Stores bean metadata specifications.
  • Three-Level Singleton Cache (DefaultSingletonBeanRegistry):
    1. singletonObjects (Level 1 Cache): Fully initialized, wired, and proxied ready-to-use singleton beans.
    2. earlySingletonObjects (Level 2 Cache): Unpopulated raw or early-proxied instances used to resolve circular references.
    3. singletonFactories (Level 3 Cache): ObjectFactory<?> instances that lazily generate early references and AOP wrappers.

2. The Bean as an AOP Dynamic Proxy: The final Hazard

In enterprise Spring applications, an injected bean is almost never a raw instance of your class; it is wrapped in an AOP Dynamic Proxy:

Client Invocation ──► [ UserService$$SpringCGLIB$$0 (Proxy) ] ──► [ UserService (Target) ]
                            │
                            ├── Intercept Call (MethodInterceptor)
                            ├── Open Transaction (@Transactional)
                            ├── Invoke Real Target Method
                            └── Commit / Rollback Transaction

Spring Boot 2.x and 3.x default to CGLIB proxying (spring.aop.proxy-target-class=true), which creates a runtime subclass of the target bean.

The final Method Catastrophe:

Because CGLIB relies on class inheritance and method overriding:

  1. final Class: If a class marked with @Transactional is declared final, CGLIB cannot generate a subclass. Spring crashes at startup with BeanCreationException: Cannot subclass final class.
  2. final Method: If a method on a bean is declared final:
    @Service
    public class PaymentService {
        @Transactional
        public final void processPayment() { // DANGER: final method!
            // Money transfer logic
        }
    }
    

    Catastrophic Consequence: The application compiles and starts without error! However, Java forbids overriding final methods. When a client calls processPayment(), the CGLIB proxy cannot intercept the call. The transaction is never opened, and database mutations execute without transaction boundaries or rollback protection!


3. What Objects Should NEVER Be Spring Beans?

A critical hallmark of a senior engineer is knowing when not to use a framework feature:

  1. JPA Entities (@Entity): Entities represent database table rows (User, Order) with mutable state. They have their own lifecycle managed by Hibernate/JPA EntityManager and must be created via ORM queries or new.
  2. Data Transfer Objects (DTOs) and Records: DTOs are passive data carriers designed for serialization/deserialization. Making a DTO a Spring bean causes memory bloat and concurrency corruption.
  3. Value Objects: Small domain objects (e.g., Money, Coordinates, EmailAddress) that represent immutable values.
  4. Short-Lived Request Payloads: Transient objects meant to be quickly garbage-collected should never be managed by the Spring IoC container.

[!NOTE] Spring Beans should be stateless architectural components: services, repositories, controllers, configuration factories, and infrastructure clients.


4 Tricky Questions

1. What is the fundamental difference between an object created via new UserService() and a UserService bean retrieved from the Spring context?

Answer: An object created via new is a standalone instance in the JVM heap:

  • Its internal @Autowired dependencies remain null (unless manually passed into a constructor).
  • Lifecycle callbacks (@PostConstruct, InitializingBean) are completely ignored.
  • Enterprise aspect annotations (@Transactional, @Async, @Cacheable, @PreAuthorize) will not function because the object is not wrapped in a Spring AOP dynamic proxy.

A bean retrieved from the ApplicationContext is fully wired, has executed all initialization lifecycle hooks, and is wrapped in an AOP proxy interceptor that transparently manages transactions, security, and metrics.


2. What happens if you mark a method annotated with @Transactional with the final keyword in Spring Boot?

Answer: Spring Boot starts successfully without throwing any exception. However, because Spring Boot uses CGLIB dynamic subclassing for AOP proxies by default, the generated proxy subclass cannot override the final method.

When client code invokes this method:

  • The invocation bypasses the CGLIB proxy interceptor entirely and executes directly against the raw target class.
  • The TransactionInterceptor is never triggered; no database transaction is opened.
  • Any exceptions thrown during execution will not trigger a rollback, silently breaking transactional integrity in production.

3. What is a BeanDefinition, when is it generated, and how can it be modified prior to bean creation?

Answer: A BeanDefinition is an internal Spring interface representing the configuration metadata of a bean (class name, scope, autowiring rules, constructor arguments, properties, and lifecycle method names).

It is generated during the Configuration Scanning Phase (via ASM bytecode inspection of annotated classes or parsing of @Configuration classes), before any bean instances are allocated in the heap.

It can be intercepted and modified dynamically before bean instantiation by implementing the BeanFactoryPostProcessor interface. A prominent example is PropertySourcesPlaceholderConfigurer, which resolves ${property.name} tokens inside BeanDefinition values using environment properties.


4. Why is making JPA Entities (@Entity) Spring Beans considered an antipattern?

Answer:

  1. Lifecycle Conflict: JPA entities have their own complex lifecycle states (Transient, Managed, Detached, Removed) managed by the Hibernate EntityManager / Persistence Context, which clashes with Spring’s singleton bean lifecycle.
  2. Statefulness & Concurrency: JPA entities are stateful representations of specific database rows. Spring Beans are by default singletons shared across all threads; making an entity a singleton bean causes catastrophic data contamination and concurrency race conditions.
  3. Memory Bloat: An application may process millions of entity rows. Registering each entity in Spring’s DefaultListableBeanFactory would cause severe memory leaks and garbage collection thrashing.

🎯 Interview Cheat Sheet

30-Second Elevator Pitch

“A Spring Bean is a standard Java object (POJO) whose complete lifecycle—instantiation, dependency injection, configuration, and destruction—is managed by the Spring IoC Container (ApplicationContext). The container constructs beans based on BeanDefinition metadata blueprints. Beans are typically stateless enterprise components (Services, Repositories, Controllers) and are frequently wrapped in CGLIB dynamic proxies to enable cross-cutting concerns like @Transactional and @Async. Domain entities, DTOs, and value objects must never be registered as Spring Beans.”


Red Flags & Pitfalls (DO NOT Say)

  • ❌ “A Spring Bean is a special class that inherits from a Spring base class.” (Beans are plain POJOs; Spring is non-invasive).
  • ❌ “Every class in my project should be annotated with @Component.” (Entities, DTOs, and records should never be Spring beans).
  • ❌ “I make my @Transactional methods final to protect them from being altered.” (CGLIB cannot override final methods, which completely disables transaction interceptors).
  • ❌ “Instantiating a service with new MyService() works fine in Spring.” (Creating a bean with new bypasses Spring DI, @PostConstruct, and AOP proxies).

Architectural Mental Model

From Source Code to Managed Bean:
┌──────────────────────────┐
│  Source Code / Config    │  @Service, @Configuration + @Bean
└─────────────┬────────────┘
              │ 1. Scan & Parse
              ▼
┌──────────────────────────┐
│      BeanDefinition      │  Metadata Blueprint (Scope, Class, Properties)
└─────────────┬────────────┘
              │ 2. BeanFactoryPostProcessor modification
              ▼
┌──────────────────────────┐
│    Target Object Inst.   │  Constructor.newInstance()
└─────────────┬────────────┘
              │ 3. DI & Lifecycle Hooks (@PostConstruct)
              ▼
┌──────────────────────────┐
│    AOP Proxy Wrapper     │  CGLIB / JDK Dynamic Proxy (@Transactional)
└─────────────┬────────────┘
              │ 4. Stored in Singleton Pool
              ▼
┌──────────────────────────┐
│ ApplicationContext Ready │  Available for injection into controllers/services
└──────────────────────────┘