Pollito Dev
November 25, 2024

Let's talk Java: Beans

Posted on November 25, 2024  •  7 minutes  • 1381 words  • Other languages:  Español

Bean… It’s one of those terms that we Java developers toss around so much, we sometimes forget to pause and appreciate its elegance. Let’s discover what a Spring bean really is.

What is a Bean?

In the Spring framework, a bean is simply an object that is managed by the Spring IoC (Inversion of Control) container. It’s the backbone of Spring’s dependency injection (DI) mechanism.

To break it down:

Key characteristics of a Spring Bean

1. Defined in the context

2. Singleton by default

3. Dependency Injection

4. Lifecycle management

Example

Here’s a simple bean:

@Component
public class CoffeeMaker {
    public String brew() {
        return "Brewing a fresh cup of Java!";
    }
}

And another bean that depends on it:

@Service
public class CoffeeShop {
    private final CoffeeMaker coffeeMaker;

    public CoffeeShop(CoffeeMaker coffeeMaker) {
        this.coffeeMaker = coffeeMaker;
    }

    public void openShop() {
        System.out.println(coffeeMaker.brew());
    }
}

Spring’s container detects the @Component and @Service annotations, creates beans for these classes, and wires them up.

Dependency Injection to wire beans together

Dependency Injection (DI) is a design pattern where an object’s dependencies are provided by an external source (the Spring IoC container) rather than the object itself creating them.

This is the best video out there explaining the topic:

In Spring, DI is used to wire beans together, making applications loosely coupled and easier to test and maintain.

Injection methods

1. Field Injection: Use @Autowired directly on a field.

@Autowired
private MyService myService;

2. Constructor Injection (Preferred): Dependencies are provided via the constructor.

@Service
public class MyService {
    private final MyDependency dependency;

    public MyService(MyDependency dependency) {
        this.dependency = dependency;
    }
}

3. Setter Injection: Dependencies are injected via a setter method.

private MyDependency dependency;

@Autowired
public void setDependency(MyDependency dependency) {
    this.dependency = dependency;
}

Why use Dependency Injection?

Constructor injection is typically preferred because it ensures dependencies are immutable and mandatory for the object’s operation.

Why Singleton by Default?

1. Efficiency

2. Consistency

3. Dependency Injection

4. Thread-Safety

When would you want to change Singleton behavior?

The singleton model isn’t always appropriate. Here are some situations where changing it makes sense:

1. Stateful Beans (e.g., Request/Session Scoped)

@Component
@RequestScope
public class ShoppingCart {
private final List<Item> items = new ArrayList<>();

    public void addItem(Item item) {
        items.add(item);
    }

    public List<Item> getItems() {
        return items;
    }
}

2. Prototype Scope for per-instance behavior

@Component
@Scope("prototype")
public class TemporaryWorker {
    private final String id = UUID.randomUUID().toString();

    public String getId() {
        return id;
    }
}

When to stick to Singletons?

Spring defaults to singleton because it fits the most common use case: stateless, shared services. You should change the scope when state or lifespan becomes critical to the bean’s function (e.g., per-request/session behavior, task-specific data).

If you find yourself asking whether you need a non-singleton, always ask:

The lifecycle of a Spring Bean

1. Instantiation

2. Dependency Injection

3. Post-Initialization Hooks

4. Ready for use

5. Destruction

Lifecycle hooks in detail

1. Initialization hooks

@Component
public class ExampleBean {
    @PostConstruct
    public void initialize() {
        System.out.println("Bean is initialized!");
    }
}

2. Destruction hooks

@Component
public class ExampleBean {
    @PreDestroy
    public void cleanup() {
        System.out.println("Bean is being destroyed!");
    }
}

Full lifecycle in code

@Component
public class ExampleBean implements InitializingBean, DisposableBean {
    public ExampleBean() {
        System.out.println("1. Bean is instantiated.");
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("2. @PostConstruct: Bean is initialized.");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("3. afterPropertiesSet(): Custom initialization logic.");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("4. @PreDestroy: Cleanup before destruction.");
    }

    @Override
    public void destroy() {
        System.out.println("5. destroy(): Final cleanup.");
    }
}

Output:

1. Bean is instantiated.
2. @PostConstruct: Bean is initialized.
3. afterPropertiesSet(): Custom initialization logic.
4. @PreDestroy: Cleanup before destruction.
5. destroy(): Final cleanup.

Bean lifecycle with scopes

When would you use these hooks?

Initialization

Destruction:

Spring gives you fine-grained control over a bean’s lifecycle. While hooks like @PostConstruct and @PreDestroy are the most modern and widely used, the lifecycle is flexible enough to accommodate custom logic as needed.

Conclusion

Hey, check me out!

You can find me here