Spring

Li

Li Wei

April 26, 202610 min read

Title: Spring

Overview

Spring is a layered full‑stack lightweight open‑source framework for JavaSE/EE applications.

Advantages of Spring:

  • Facilitates decoupling and simplifies development
  • Easy integration of various frameworks
  • Simplifies testing
  • Provides support for AOP programming (which can be difficult)
  • Supports declarative transactions
  • Reduces the difficulty of using JavaEE APIs

 

Architecture:

 

IoC

Basic Overview

IoC (Inversion of Control) means that Spring reverses control of the external resources an application needs. All resources managed by Spring are placed in the Spring container, which is called the IoC container (it stores instance objects).

 

Official website: https://spring.io/ → Projects → spring‑framework → LEARN → Reference Doc

  • Coupling: The degree of tightness between the technologies used during coding; it measures how inter‑connected modules are in software.
  • Cohesion: The internal relatedness of the components within a single module; it measures how functionally related the parts of a module are.
  • Coding goal: High cohesion, low coupling. Elements within the same module should be tightly related, while dependencies between modules should be loose.

 

Bean

Scope

Overview
In Spring’s IoC container, beans are singletons by default. If you want a bean to be created as a new instance each time, set the scope attribute on the <bean> tag to prototype. Spring will then create a new bean object every time getBean() is called.

The scope attribute has more than two values—there are eight options in total:

  • singleton: Default; a single instance.
  • prototype: A new instance each time getBean() is called (or each injection).
  • request: One bean per HTTP request. Only for web applications.
  • session: One bean per HTTP session. Only for web applications.
  • global session: Specific to portlet applications. In a regular servlet web app, it behaves like session. (Portlets and servlets are both specifications; servlets run in a servlet container such as Tomcat, while portlets run in a portlet container.)
  • application: One bean per web application. Only for web applications.
  • websocket: One bean per WebSocket lifecycle. Only for web applications.
  • custom scope: Rarely used.

 

Custom Scope
You can define a custom scope, for example a thread‑level scope where beans retrieved within the same thread are the same instance, but different threads get different instances (shown here for illustration only):

  • Implement a custom scope by implementing the Scope interface.
  • Spring already provides a thread‑scope class: org.springframework.context.support.SimpleThreadScope, which can be used directly.
  • Register the custom scope with the Spring container.

 

Lifecycle

Overview
Spring is essentially a factory that manages bean objects—it handles creation, destruction, etc.

The lifecycle is the whole process from bean creation to final destruction. In practice, it means “at which point we invoke which method of which class.”

Understanding the special lifecycle points lets you insert custom code exactly where you need it; when the lifecycle reaches that point, your code runs automatically.

 

Five‑step Bean Lifecycle
You can follow Spring’s source code, e.g., the doCreateBean() method in AbstractAutowireCapableBeanFactory, to see the details.

The lifecycle can be roughly divided into five major steps:

  1. Instantiate the bean
  2. Populate bean properties
  3. Initialize the bean
  4. Use the bean
  5. Destroy the bean

 

Difference between Instantiation and Initialization

  • Instantiation:
    The phase where the bean instance is created. In Spring this can happen via a constructor, a factory method, or other mechanisms. When the container receives a request to create a bean, it reads the configuration and calls the appropriate constructor or factory method.

  • Initialization:
    After the bean has been instantiated, Spring performs additional setup before the bean is ready for use. This includes dependency injection, calling custom init methods, etc. An init method can be defined directly in the bean class or specified with the init-method attribute in XML. Spring invokes these after instantiation to finish any required setup. Once initialization is complete, the bean is managed by the container and ready for other components.

 

Test program example

  • Define a bean
    • XML configuration
    • Test class

Result:

 

Things to note

  • The bean’s destroy method is called only when the Spring container is shut down normally.
  • Only ClassPathXmlApplicationContext provides a close() method.
  • In the XML, init-method specifies the init method, and destroy-method specifies the destroy method.

 

Seven‑step Bean Lifecycle
If you want to add code before and after the initialization step, you can use a BeanPostProcessor.

Create a class that implements BeanPostProcessor and override the postProcessBeforeInitialization and postProcessAfterInitialization methods. Register this processor in spring.xml.

Important: A BeanPostProcessor defined in spring.xml applies to all beans defined in that file.

Running the test shows a seven‑step lifecycle when a post‑processor is present.

 

Ten‑step Bean Lifecycle
By tracing the source code more finely, you can split the process into ten steps.

The Aware‑related interfaces include: BeanNameAware, BeanClassLoaderAware, BeanFactoryAware.

  • If a bean implements BeanNameAware, Spring injects the bean’s name.
  • If it implements BeanClassLoaderAware, Spring injects the class loader that loaded the bean.
  • If it implements BeanFactoryAware, Spring injects the owning BeanFactory.

Test this by letting a User class implement five interfaces:

  • BeanNameAware
  • BeanClassLoaderAware
  • BeanFactoryAware
  • InitializingBean
  • DisposableBean

Sample code includes LogBeanPostProcessor.

Results

  • Methods from InitializingBean run before the init-method.
  • Methods from DisposableBean run before the destroy-method.

For most practical purposes, mastering the seven‑step lifecycle is sufficient.

 

Management Modes

Overview
Spring chooses a management strategy based on a bean’s scope.

  • For singleton beans, Spring knows exactly when the bean is created, initialized, and destroyed.
  • For prototype beans, Spring only creates the instance; after that, the client code is responsible for its lifecycle, and the container no longer tracks it.

 

Custom Bean Registration
Sometimes you may have an object you created with new and want Spring to manage it. The following code demonstrates how to register such an object with the container.

Result:

 

Circular Dependencies

Overview
What is a circular dependency?
Bean A has a reference to Bean B, and Bean B has a reference back to Bean A. It’s a “I depend on you, you depend on me” situation.

Example: a Husband class referencing a Wife object, and the Wife class referencing a Husband object.

 

Singleton + setter injection
Create a test program to see whether Spring can resolve a circular dependency when both beans are singletons and use setter injection.

Result:

Conclusion: With singleton beans and setter injection, Spring resolves the circular dependency without issue.

 

Prototype + setter injection
Now test the same scenario with prototype beans.

Result: When all beans involved have scope="prototype", Spring cannot resolve the circular dependency and throws a BeanCurrentlyInCreationException.

You can verify that if one bean is singleton and the other prototype, the problem does not occur. This is because a singleton bean is created once and kept for the whole application, while a prototype bean is created anew on each request. If a singleton bean A depends on a prototype bean B, the instance of B injected into A is created once when A is first instantiated, so no circular reference arises later.

Note: Although a singleton bean depending on a prototype bean usually avoids circular‑dependency issues, you still need to be careful. If the singleton holds onto the prototype reference and shares it across multiple places, you may get unexpected behavior because each request for the prototype would normally produce a new instance.

 

Why does it fail when both beans are prototype?
When a prototype bean is injected into another bean, Spring does not retain a reference to the original prototype instance; it creates a fresh instance on each request. During injection, this prevents Spring from breaking the circular reference, leading to the exception described above.

 

Singleton + constructor injection
Test singleton beans that use constructor injection.

Result: The program throws BeanCurrentlyInCreationException, the same as the previous test. Constructor injection prevents Spring from separating instantiation from property population, so the circular dependency cannot be resolved.

Root cause: Constructor injection ties object creation and property assignment together, leaving no opportunity for Spring to expose a partially constructed bean.

 

How Spring solves setter‑based circular dependencies
Spring can resolve circular dependencies for singleton beans with setter injection because it can separate bean instantiation from property population:

  1. Instantiate the bean using a no‑arg constructor (no properties set yet). At this point the bean can be “exposed” to other beans.
  2. Later, when properties are set, Spring calls the appropriate setter methods.

These two steps can occur at different times. Spring first creates all singleton instances and stores them in a cache (often called the singleton cache). After all singletons are instantiated, Spring proceeds to populate their properties via setters, which resolves the circular references.

Implementation details
In the core Spring class (shown in the source), three important caches are used:

  • Cache of singleton objects: bean name → bean instance (first‑level cache).
  • Cache of early singleton objects: bean name → early bean reference (second‑level cache).
  • Cache of singleton factories: bean name → ObjectFactory that can create the bean (third‑level cache).

These caches are essentially three Maps. A method addSingletonFactory() is used to expose an ObjectFactory for a bean early.

Analyzing the source code shows that Spring first looks in the first‑level cache; if the bean isn’t there, it checks the second‑level cache; if still not found, it retrieves the ObjectFactory from the third‑level cache and obtains the bean instance, thereby breaking the circular dependency.

A flowchart illustrates this process.

Summary

Spring can only resolve circular dependencies for singleton beans that use setter injection. When ClassA depends on ClassB and vice versa, Spring creates ClassA, exposes it early in the cache, then proceeds to resolve ClassB. While resolving ClassB, it finds the already‑exposed ClassA in the cache and reuses it, avoiding a second instantiation.


XML Development

Bean Configuration

Basic attributes<bean> is a child element of <beans>.

Purpose: Define resources managed by Spring.

Common attributes:

  • id – Bean name, accessed via the id attribute (lower‑camel‑case).
  • class – Bean class, specified with the fully‑qualified class name.
  • name – Alias name, set with the name attribute for alternative references.

 

Scope attribute – Defines the bean’s lifecycle scope.

Possible values:

  • singleton – Singleton (default); one shared instance in the container.
  • prototype – Prototype; a new instance each time it’s requested.
  • request、session、application、 websocket – Web‑specific scope (e.g., request, session, etc.) placed in the appropriate web container location.

 

Thread‑safety of beans in the Spring container

  • Prototype beans are created anew for each request, so they are not shared between threads and thus pose no thread‑safety concerns.
  • Singleton beans are shared across all threads; if a singleton is stateless (i.e., it has no mutable shared fields), it is thread‑safe. Otherwise, concurrent access can lead to resource contention.

Solution: Developers can ensure thread safety by changing the bean’s scope from singleton to prototype when appropriate.

 

Creation and Destruction
Spring acts as a factory that creates and destroys bean instances.

Configuration format:

Specify the method name in the bean class that should be called for init or destroy.

You can also implement lifecycle interfaces instead of using XML init-method/destroy-method:

  • Implement InitializingBean for custom initialization logic.
  • Implement DisposableBean for custom destruction logic.

 

Notes

  • When scope="singleton", the Spring container holds exactly one instance, and the init method runs only once when the container starts.
  • When scope="prototype", the container creates multiple instances of the same type, and the init method runs for each instance.

(content truncated)


Originally written by Li Wei (李唯_) and published in Chinese on 后端技术栈全书 (Full-Stack Backend Engineering). Translated and adapted for DriftSeas with permission.

Keep reading

More related articles from DriftSeas.