Unlock Spring’s Power: 9 Essential Design Patterns Every Backend Engineer Should Master

This article explores nine key Spring framework design patterns—including Simple Factory, Factory Method, Singleton, Adapter, Decorator, Proxy, Observer, Strategy, and Template Method—detailing their implementation, underlying principles, and practical code examples, while highlighting how they enable loose coupling and extensibility in backend development.

Programmer DD
Programmer DD
Programmer DD
Unlock Spring’s Power: 9 Essential Design Patterns Every Backend Engineer Should Master

Spring heavily relies on classic design patterns to provide a flexible, extensible, and loosely‑coupled IoC container. The following nine patterns illustrate how Spring implements dependency injection, bean lifecycle management, and extensibility.

1. Simple Factory (BeanFactory)

Implementation: BeanFactory retrieves a bean by a unique identifier.

Essence: a factory class decides which product class to instantiate based on supplied parameters.

Bean creation process:

Read bean XML definitions and convert each <bean> element into a BeanDefinition object.

Register the BeanDefinition in the BeanFactory 's internal ConcurrentHashMap.

Allow custom processing via BeanFactoryPostProcessor (e.g., PropertyPlaceholderConfigurer).

Instantiation phase:

Inject dependencies through Aware interfaces (e.g., BeanFactoryAware).

Apply BeanPostProcessor callbacks before and after initialization.

Support InitializingBean and DisposableBean lifecycle hooks.

2. Factory Method (FactoryBean)

Implementation: beans that implement FactoryBean act as factories.

Principle: getObject() is invoked when getBean() is called, returning the product rather than the factory itself.

Typical example: Spring‑MyBatis integration where SqlSessionFactoryBean returns a SqlSessionFactory instance.

3. Singleton Pattern

Spring beans are singleton by default; creation occurs in AbstractBeanFactory.getBean() via getSingleton().

public Object getSingleton(String beanName) {
    // allow early references
    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);
}

The above method demonstrates Spring's double‑checked locking approach to ensure a true singleton.

4. Adapter Pattern (HandlerAdapter)

Implementation: Spring MVC’s HandlerAdapter selects and invokes the appropriate handler based on mapping rules.

Process: DispatcherServlet obtains a handler from HandlerMapping, forwards the request to the matching HandlerAdapter, which then returns a ModelAndView to the servlet.

Benefit: Adding a new controller type only requires a corresponding adapter.

5. Decorator Pattern

Implementation: Classes whose names contain Wrapper or Decorator act as decorators, dynamically adding responsibilities to a bean.

Advantage: More flexible than subclassing for extending functionality.

6. Proxy Pattern (AOP)

Implementation: Spring AOP builds dynamic proxies (or static proxies) to weave aspects at runtime.

Key interfaces: BeanPostProcessor, ApplicationListener, etc., are proxied to inject cross‑cutting behavior.

Note: "Weaving" means applying an aspect to a target object and creating a new proxy instance.

7. Observer Pattern (Application Events)

Implementation: Spring’s event‑driven model follows the observer pattern.

Event source: ApplicationContext (implements ApplicationEventPublisher).

Event: subclasses of ApplicationEvent (extends EventObject).

Listener: classes implementing ApplicationListener<E extends ApplicationEvent>.

public abstract class ApplicationEvent extends EventObject {
    private final long timestamp;
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }
    public final long getTimestamp() { return this.timestamp; }
}
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}

8. Strategy Pattern (Resource Interface)

Implementation: Spring’s Resource abstraction defines a strategy for accessing various underlying resources (URL, classpath, filesystem, servlet context, etc.).

Key methods: getInputStream(), exists(), isOpen(), getDescription(), getFile(), getURL().

Concrete implementations include UrlResource, ClassPathResource, FileSystemResource, ServletContextResource, InputStreamResource, and ByteArrayResource.

9. Template Method Pattern

Definition: A superclass defines the algorithm skeleton; subclasses provide specific steps.

Common methods are reused; abstract methods must be overridden; hook methods can be optionally overridden.

Spring usage: Most framework extensions (e.g., JdbcTemplate, Hibernate integration) combine template method with callback interfaces.

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;
        } finally {
            closeStatement(stmt);
            releaseConnection(con);
        }
    }
    protected abstract Object executeWithStatement(Statement stmt, String sql);
}
public interface StatementCallback {
    Object doWithStatement(Statement stmt);
}
public final Object execute(StatementCallback callback) {
    Connection con = null;
    Statement stmt = null;
    try {
        con = getConnection();
        stmt = con.createStatement();
        Object ret = callback.doWithStatement(stmt);
        return ret;
    } finally {
        closeStatement(stmt);
        releaseConnection(con);
    }
}

By separating the invariant resource‑management code from the variable business logic, Spring achieves high reusability and clean separation of concerns.

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.

Design PatternsJavaBackend 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.