Harvesting Efficiency: Mastering Spring Beans for Robust Application Development

Marcelo Domingues
10 min readDec 17, 2023

--

Reference Image

Introduction

In this article, we immerse ourselves in the foundational aspects of Spring Beans, the bedrock of the Spring Framework. Our comprehensive exploration traverses the intricacies of Spring Beans, encompassing their lifecycle, scopes, injection methods, advanced configurations, and finer intricacies.

Whether you’re a novice seeking fundamental insights or an expert developer aiming for advanced understanding, this resource offers comprehensive knowledge to harness Spring Beans effectively. Anticipate detailed explanations, practical examples, and a hands-on approach to master the pivotal role Spring Beans play in crafting resilient and efficient applications.

Section 1: Understanding Spring Beans

Bean Lifecycle

Spring Beans undergo a series of stages:

  1. Instantiation: Creation of the bean instance.
  2. Property Population: Setting values for properties.
  3. Initialization: Execution of initialization logic.
  4. Destruction: Potential cleanup or releasing resources.
Reference Image

Bean Scopes:

  • Singleton: A single instance shared across the entire application. It’s the default scope for Spring Beans. (This one is the default one)
  • Prototype: A new instance created whenever requested, providing a fresh object on each request.
  • Request: A new instance for each HTTP request, suitable for maintaining request-specific data.
  • Session: A single instance per user session in a web application, preserving state across multiple HTTP requests from the same user.
  • Global Session: Similar to session scope but specifically for portlet-based web applications, maintaining state across all portlets.

Understanding these scopes is crucial in managing bean lifecycles and resource utilization within different contexts of an application.

import org.springframework.stereotype.Component;

@Component
public class MyBean {
private String data;

public void setData(String data) {
this.data = data;
}

public String getData() {
return data;
}
}

Unit Testing for this Simple Spring Bean example:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class MyBeanTest {

@Test
public void testDataSetting() {
MyBean myBean = new MyBean();
myBean.setData("Test Data");
assertEquals("Test Data", myBean.getData());
}
}

Code Example with Unit Test: Defining a Simple Spring Bean

Definition and Importance

Spring Beans are Java objects managed by the Spring IoC (Inversion of Control) container. They form the backbone of applications developed using the Spring Framework, facilitating loose coupling and providing a flexible environment for creating software components.

Spring IoC (Inversion of Control) Container

The Spring IoC container is the core of the Spring Framework, facilitating the Inversion of Control principle. It manages the creation, configuration, and lifecycle of beans, allowing developers to focus on business logic rather than managing object creation and dependency injection.

  • Inversion of Control (IoC): In traditional programming, objects are responsible for creating and managing their dependencies. With IoC, this control is inverted, and the container manages object creation, wiring dependencies, and their lifecycle.

Code Example: Defining a Simple Spring Bean

import org.springframework.stereotype.Component;

@Component
public class MyBean {
// Bean implementation goes here
}

Section 2: Injection Methods in Spring

Setter Injection

Setter Injection facilitates the injection of dependencies by defining setter methods in a class, each corresponding to a specific dependency that the class requires. The @Autowired annotation marks these setter methods, indicating to the Spring container that it should supply the necessary dependencies when the bean is initialized.

For instance, consider a class AnotherBean that requires a dependency of type MyBean. Setter Injection would involve defining a setter method for this dependency and annotating it with @Autowired:

Using @Autowired for setter-based injection:

@Component
public class AnotherBean {
private MyBean myBean;

@Autowired
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
}

Unit Test for Setter Injection:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class AnotherBeanTest {

@Test
public void testMyBeanInjection() {
AnotherBean anotherBean = new AnotherBean();
MyBean myBean = new MyBean();
anotherBean.setMyBean(myBean);
assertNotNull(anotherBean.getMyBean());
}
}

When the Spring container initializes AnotherBean, it scans for beans matching the type of MyBean. If a bean of MyBeantype is available, it injects it into AnotherBeanby invoking the annotated setMyBean() method.

Advantages of Setter Injection:

  1. Flexibility: Setter Injection allows for dynamic modification of dependencies after the bean is instantiated. By providing setter methods, developers can change or reset dependencies during the application’s runtime, offering more flexibility in managing dependencies.
  2. Readable Configuration: Setter Injection provides clear and explicit methods for setting dependencies, improving the readability and maintainability of the code. This separation between the setting of dependencies and the class logic aids in understanding and maintaining the configuration.
  3. Easier Unit Testing: Setter Injection facilitates easier mocking or substitution of dependencies during unit testing. By exposing setter methods, developers can inject mock dependencies, enabling comprehensive testing of individual components or behaviors without relying on actual implementations.

Best Practices for Setter Injection:

  • Use Setter Injection for optional dependencies or scenarios where dependencies might change after the bean is created.
  • Avoid excessive use of setters to prevent making too many class members mutable, compromising object immutability.

In summary, Setter Injection in Spring provides a flexible and explicit approach to inject dependencies into a class through annotated setter methods, offering advantages in flexibility, readability, and testability of code. It remains a valuable technique in Spring Dependency Injection, particularly when dynamic dependency management is essential.

Constructor Injection

Constructor Injection involves in providing dependencies to a class through its constructor. It is a widely-used approach where dependencies are declared as parameters in the constructor, and the Spring Framework automatically resolves and provides these dependencies when creating an instance of the class.

Consider a class ConstructorInjectedBean requiring a dependency of type MyBean. Constructor Injection would involve defining a constructor that accepts MyBean as a parameter and annotating it with @Autowired:

Injecting dependencies through a class constructor:

@Component
public class ConstructorInjectedBean {
private MyBean myBean;

@Autowired
public ConstructorInjectedBean(MyBean myBean) {
this.myBean = myBean;
}
}

Unit Test for Constructor Injection:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class ConstructorInjectedBeanTest {

@Test
public void testConstructorInjection() {
MyBean myBean = new MyBean();
ConstructorInjectedBean constructorInjectedBean = new ConstructorInjectedBean(myBean);
assertNotNull(constructorInjectedBean.getMyBean());
}
}

When the Spring container initializes ConstructorInjectedBean, it identifies the constructor annotated with @Autowired. It then resolves the required MyBeanbean and injects it into ConstructorInjectedBeanby invoking this constructor.

Advantages of Constructor Injection:

  1. Immutability: Encourages immutable objects by setting dependencies once during object creation, promoting better encapsulation.
  2. Compile-Time Safety: Leverages compiler checks to ensure that all required dependencies are provided at the time of object instantiation.
  3. Testability: Facilitates easier unit testing by allowing dependencies to be provided explicitly through the constructor.

Best Practices for Constructor Injection:

  • Prefer Constructor Injection for mandatory dependencies required for the class to function properly.
  • Avoid having multiple overloaded constructors with different sets of dependencies, as it can lead to ambiguity.

Field Injection

Field Injection involves directly injecting dependencies into class fields using the @Autowired annotation. This method allows dependencies to be injected directly into the class fields, bypassing the need for setter methods or constructors.

Field injection using @Autowired (not recommended for testability reasons):

@Component
public class FieldInjectedBean {
@Autowired
private MyBean myBean;
}

Unit Test for Field Injection (using Mockito):

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class FieldInjectedBeanTest {

@Test
public void testFieldInjection() {
MyBean mockMyBean = mock(MyBean.class);
FieldInjectedBean fieldInjectedBean = new FieldInjectedBean();
assertNotNull(fieldInjectedBean.getMyBean());
}
}

Field Injection relies on the @Autowired annotation directly on the class fields requiring dependency injection. During bean initialization, Spring scans the class for fields annotated with @Autowired and injects the appropriate dependencies into these fields.

Advantages of Field Injection:

  1. Conciseness: Reduces boilerplate code by directly injecting dependencies into fields without the need for setter methods or constructors.
  2. Ease of Use: Simplifies the code structure by declaring dependencies directly where they are used.
  3. Convenience: Handy for quick prototyping or simple scenarios with fewer dependencies.

Best Practices for Field Injection:

  • Avoid excessive use of Field Injection, especially in larger applications, as it can reduce class testability and maintainability.
  • Consider using Constructor Injection over Field Injection for better testability and explicit dependency declaration.

Section 3: Advanced Bean Configuration

  • XML vs. Annotation-Based Configuration: Discuss the pros and cons of each configuration style.
  • Java-Based Configuration: How to configure beans using Java config.
  • Bean Wiring: Explore dependency injection and autowiring.

XML Configuration vs. Annotations

XML-based Configuration:

Pros:

  • Readability: Clear structure and explicit configuration visible in XML files.
  • Separation of Concerns: Configuration separated from business logic, aiding in maintaining a clean separation between code and configuration.
  • Ease of Understanding: Provides a visual representation of bean wiring and dependencies.

Cons:

  • Verbosity: XML configurations can become verbose and lengthy, especially in large-scale applications, leading to readability issues.
  • Maintenance Overhead: Manually managing XML files can be cumbersome, especially when dealing with frequent changes or additions to configurations.
  • Limited Type Safety: XML configurations lack compile-time checks, making it prone to runtime errors if configuration mismatches occur.

Annotation-based Configuration:

Pros:

  • Conciseness: Annotations reduce boilerplate code, making configurations concise and easier to read.
  • Simplified Development: Configurations closer to code reduce context switching and enhance developer productivity.
  • Compile-time Safety: Utilizes compiler checks, minimizing errors due to mistyped configurations.

Cons:

  • Mixing Concerns: Annotations might clutter business logic with configuration details, blurring the line between code and configuration.
  • Reduced Visibility: Configurations are scattered across code, potentially making it harder to visualize the entire configuration at a glance.
  • Limited Flexibility: Annotations can be limiting in scenarios requiring dynamic or conditional bean configurations.

XML configuration (example for bean definition):

<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myBean" class="com.example.MyBean" />
</beans>

Unit Test for XML Configuration (using Spring’s testing framework):

import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class XmlConfigTest {
@Test
public void testXmlBeanLoading() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);
assertNotNull(myBean);
context.close();
}
}

Section 4: Working with Bean Lifecycle Callbacks

Customizing Bean Initialization and Destruction

Using annotations such as @PostConstruct and @PreDestroy allows developers to define methods that will be invoked automatically during a bean's lifecycle.

  • @PostConstruct: The method annotated with @PostConstruct is executed after the bean has been instantiated and initialized, enabling developers to perform initialization tasks.
  • @PreDestroy: Annotating a method with @PreDestroy ensures that it's called just before the bean is destroyed by the container, allowing for cleanup or resource release operations.
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class LifecycleBean {
@PostConstruct
public void init() {
// Initialization logic
}

@PreDestroy
public void destroy() {
// Destruction logic
}
}

Implementing InitializingBean and DisposableBean Interfaces

Spring also provides interfaces, InitializingBean and DisposableBean, allowing finer control over a bean's initialization and destruction phases by implementing their respective methods, afterPropertiesSet() and destroy().

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

@Component
public class LifecycleBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() {
// Initialization logic
}

@Override
public void destroy() {
// Destruction logic
}
}

Unit Test for Bean Lifecycle Methods

When testing bean lifecycle methods, Mockito can be employed to verify the invocation of these methods without triggering the actual logic within them. The following example demonstrates using Mockito to verify method invocations:

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;

public class LifecycleBeanTest {

@Test
public void testLifecycleMethods() {
LifecycleBean lifecycleBean = Mockito.spy(new LifecycleBean());
doNothing().when(lifecycleBean).init();
doNothing().when(lifecycleBean).destroy();

lifecycleBean.init();
verify(lifecycleBean, times(1)).init();

// Perform operations with the bean

lifecycleBean.destroy();
verify(lifecycleBean, times(1)).destroy();
}
}

This test sets up the LifecycleBean using Mockito to ensure that the init() and destroy() methods are invoked exactly once, validating their lifecycle behavior without executing their actual logic. This approach provides a way to assert the correct invocation of these lifecycle methods within a test environment.

Section 5: Advanced Topics

BeanFactory vs. ApplicationContext

  • BeanFactory serves as the fundamental interface for accessing Spring’s bean container. It offers basic functionalities for managing beans and their dependencies. However, it focuses primarily on bean instantiation and access, lacking advanced features like event handling and AOP.
  • ApplicationContext, an extension of BeanFactory, provides a more robust container that includes all BeanFactory functionalities while offering additional features like internationalization, event propagation, AOP support, and resource handling. It’s preferred for most applications due to its broader functionality and ease of use.

Bean Proxies and AOP

Spring utilizes proxies to implement AOP, enabling modularization of cross-cutting concerns like logging, security, and transactions. Proxies intercept method invocations, allowing additional behaviors to be injected before, after, or around the actual method execution. This approach promotes cleaner and more maintainable code by separating concerns.

Example:

@Aspect
@Component
public class LoggingAspect {

@Before("execution(* com.example.MyService.*(..))")
public void beforeMethodExecution(JoinPoint joinPoint) {
// Logging logic before method execution
}

// Other advice methods for different join points
}

Conditional Beans

Conditional bean creation allows beans to be instantiated based on specific conditions. This feature is beneficial when certain beans should be created or omitted based on environment variables, system properties, or custom conditions.

Code Example: Conditional Bean Creation

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConditionalConfig {
@Bean
@Conditional(MyCondition.class)
public MyBean conditionalBean() {
return new MyBean();
}
}

Unit Test for Conditional Bean Creation (testing when condition is met):

import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class ConditionalConfigTest {

@Test
public void testConditionalBeanCreation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionalConfig.class);
MyBean conditionalBean = context.getBean(MyBean.class);
assertNotNull(conditionalBean);
context.close();
}
}

In the provided example, MyCondition is a custom condition class implementing Condition interface, evaluating whether the condition is met for creating conditionalBean.

Understanding the differences between BeanFactory and ApplicationContext help in selecting the appropriate container based on application needs. Employing AOP through bean proxies enhances code maintainability, while conditional beans offer flexibility in creating beans based on specified conditions, contributing to a more dynamic and adaptable application architecture.

Conclusion

In conclusion, this exploration of Spring Beans elucidates their pivotal role in Spring Framework development. From basic lifecycle understanding to advanced strategies like conditional bean creation and lifecycle callbacks, this guide equips developers with practical insights.

Embrace these principles in crafting adaptable, scalable applications that meet modern development demands. Experiment, adapt best practices, and continue exploring Spring Beans’ potential to architect robust software solutions. Mastering Spring Beans is about wielding a versatile toolset to create resilient, efficient, and future-proof applications.

Special Note:

Special Thanks to ChatGPT for giving me this is an excellent title, since I was out of ideas and my creative level isn’t great. 🙂

ChatGPT

Based on the style and focus of your previous titles, here’s a suggestion for your new Medium article title about Spring Beans:

“Harvesting Efficiency: Mastering Spring Beans for Robust Application Development”

Explore More on Spring and Java Development:

Enhance your skills with our selection of articles:

  • Spring Beans Mastery (Dec 17, 2023): Unlock advanced application development techniques. Read More
  • JSON to Java Mapping (Dec 17, 2023): Streamline your data processing. Read More
  • Spring Rest Tools Deep Dive (Nov 15, 2023): Master client-side RESTful integration. Read More
  • Dependency Injection Insights (Nov 14, 2023): Forge better, maintainable code. Read More
  • Spring Security Migration (Sep 9, 2023): Secure your upgrade smoothly. Read More
  • Lambda DSL in Spring Security (Sep 9, 2023): Tighten security with elegance. Read More
  • Spring Framework Upgrade Guide (Sep 6, 2023): Navigate to cutting-edge performance. Read More

--

--

Marcelo Domingues

🚀 Senior Software Engineer | Crafting Code & Words | Empowering Tech Enthusiasts ✨ 📲 LinkedIn: https://www.linkedin.com/in/marcelogdomingues/