Unlocking Spring: 9 Essential Design Patterns Every Backend Developer Should Know

This article explains how Spring implements nine classic design patterns—including Simple Factory, Factory Method, Singleton, Adapter, Decorator, Proxy, Observer, Strategy, and Template Method—detailing their implementation mechanisms, core principles, key Spring interfaces, and code examples for each pattern.

Programmer DD
Programmer DD
Programmer DD
Unlocking Spring: 9 Essential Design Patterns Every Backend Developer Should Know

Spring Design Patterns Overview

1. Simple Factory (BeanFactory)

Implementation: BeanFactory – Spring’s simple‑factory embodiment that returns a bean based on a unique identifier.

Principle: a factory class decides which product class to instantiate according to supplied parameters.

Key mechanisms in Spring:

Various *Aware interfaces (e.g., BeanFactoryAware) inject the corresponding BeanFactory instance during bean creation.

BeanPostProcessor allows custom processing after bean instantiation.

InitializingBean and DisposableBean provide lifecycle callbacks.

XML bean definitions are parsed into BeanDefinition objects and registered in the BeanFactory’s internal map.

BeanFactoryPostProcessor (e.g., PropertyPlaceholderConfigurer) can modify definitions before bean instantiation.

Bean container startup phases and instantiation phase (reflection or CGLIB) expose many extension points.

Design significance: achieves loose coupling by delegating dependency resolution to the BeanFactory and enables extra bean processing via Spring‑provided interfaces.

2. Factory Method

Implementation: FactoryBean interface.

Principle: a bean that implements FactoryBean is itself a factory; Spring calls its getObject() method and registers the returned object as the bean instance.

Typical example: MyBatis’s SqlSessionFactoryBean – the bean exposed to the container is the result of getObject(), not the factory itself.

3. Singleton

Spring’s default bean scope is singleton; each bean is created once and shared.

Dependency injection occurs in AbstractBeanFactory#getBean, which delegates to getSingleton using double‑checked locking.

Core code:

public Object getSingleton(String beanName) {
  // allow early reference
  return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  Object singletonObject = this.singletonObjects.get(beanName);
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

Summary: Spring guarantees a single instance per bean and provides global access through the BeanFactory, without enforcing singleton at the constructor level.

4. Adapter

Implementation: Spring MVC’s HandlerAdapter .

Principle: DispatcherServlet obtains a handler from HandlerMapping, then forwards the request to the appropriate HandlerAdapter, which executes the handler and returns a ModelAndView to the servlet.

Significance: Adding new controller types only requires a new handler and a matching adapter, making controller extension straightforward.

5. Decorator

Implementation: Classes whose names contain Wrapper or Decorator.

Essence: dynamically attach additional responsibilities to an object, offering more flexibility than subclassing.

6. Proxy

Implementation: Spring AOP’s dynamic proxy mechanism.

Dynamic proxy: created at runtime without manual proxy class code.

Static proxy: requires a manually written proxy class.

Principle: when an aspect is woven, Spring creates a proxy object that delegates to the target bean while applying advice.

7. Observer

Implementation: Spring’s event‑driven model using ApplicationEvent and ApplicationListener .

Components: event source, event object, listener.

Key interfaces and classes: ApplicationEvent – base class for all events (extends EventObject). ApplicationListener<E extends ApplicationEvent> – listener interface with onApplicationEvent(E event). ApplicationEventPublisher – publishes events via publishEvent. AbstractApplicationContext – registers listeners and delegates event multicasting. ApplicationEventMulticaster – broadcasts events to all registered listeners.

8. Strategy (Resource)

Implementation: Spring’s Resource interface and its concrete strategies. UrlResource – accesses network resources. ClassPathResource – accesses resources on the classpath. FileSystemResource – accesses file‑system resources. ServletContextResource – accesses resources relative to the servlet context. InputStreamResource and ByteArrayResource – stream‑ and byte‑array‑based resources.

Typical methods: getInputStream() – opens a fresh InputStream for the resource. exists() – checks resource existence. isOpen() – indicates if the resource is already opened. getDescription(), getFile(), getURL() – provide descriptive or low‑level access.

Purpose: provide a uniform API for accessing diverse underlying resources.

9. Template Method

Definition: a superclass defines the algorithm skeleton (concrete steps) while allowing subclasses to override specific steps (abstract or hook methods).

Spring’s usage: combines template method with callbacks, e.g., JdbcTemplate.

Core JdbcTemplate class defines execute(String sql) that manages connection, statement, and resource cleanup.

Subclass‑specific work is delegated to executeWithStatement (abstract) or to a StatementCallback implementation.

Code example of the template method:

public abstract class JdbcTemplate {
  public final Object execute(String sql) {
    Connection con = null;
    Statement stmt = null;
    try {
      con = getConnection();
      stmt = con.createStatement();
      Object ret = executeWithStatement(stmt, sql);
      return ret;
    } catch (SQLException e) {
      // handle
    } finally {
      closeStatement(stmt);
      releaseConnection(con);
    }
  }
  protected abstract Object executeWithStatement(Statement stmt, String sql);
}

Callback interface: <code>public interface StatementCallback { Object doWithStatement(Statement stmt); } </code> Using the callback: <code>JdbcTemplate jdbcTemplate = ...; final String sql = ...; StatementCallback callback = new StatementCallback() { public Object doWithStatement(Statement stmt) { // custom logic return ...; } }; jdbcTemplate.execute(callback); </code> Rationale: avoids subclass explosion by passing behavior as a callback object while still leveraging the stable resource‑management logic of JdbcTemplate .

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend Developmentspringdependency-injection
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.