10 Essential Spring Design Patterns Every Java Developer Should Master

This article walks through ten core design patterns used in the Spring framework—Template Method, Factory, Proxy, Singleton, Observer, Strategy, Adapter, Decorator, Builder, and Chain of Responsibility—explaining their scenarios, Spring implementations, code examples, and why they improve decoupling, extensibility, and performance.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
10 Essential Spring Design Patterns Every Java Developer Should Master

1. Template Method Pattern – Process Skeleton

Scenario: Repeated workflow with variable details.

Spring usage: JdbcTemplate, RestTemplate, etc.

public abstract class JdbcTemplate {
    public final Object execute(String sql) {
        Connection conn = getConnection();
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(sql);
        Object result = mapResult(rs);
        releaseResources(conn, stmt, rs);
        return result;
    }
    protected abstract Connection getConnection();
    protected abstract Object mapResult(ResultSet rs);
}

Why use it:

Reuse resource management (connection acquisition/release) logic.

Allow subclasses to focus only on business differences, e.g., result mapping.

2. Factory Pattern – Object Creation Manager

Scenario: Decouple object creation from usage.

Spring usage: BeanFactory core interface.

public interface BeanFactory {
    Object getBean(String name);
    <T> T getBean(Class<T> requiredType);
}
public class UserService {
    @Autowired
    private OrderService orderService;
}

Design essence:

Hide complex object initialization (e.g., circular dependencies).

Unified lifecycle management (singleton, prototype, etc.).

3. Proxy Pattern – Invisible Guard

Scenario: Non‑intrusive enhancement of object functionality.

Spring usage: AOP dynamic proxy.

// JDK dynamic proxy example
public class LogProxy implements InvocationHandler {
    private Object target;
    public Object createProxy(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        System.out.println("【Log】Calling method: " + method.getName());
        return method.invoke(target, args);
    }
}
@Aspect
@Component
public class LogAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logMethodCall(JoinPoint jp) {
        System.out.println("Calling method: " + jp.getSignature().getName());
    }
}

Dynamic proxy two‑step:

JDK proxy – based on interfaces.

CGLIB proxy – based on subclassing, works for concrete classes.

4. Singleton Pattern – Global Commander

Scenario: Reduce resource consumption and guarantee global consistency.

Spring implementation: Default bean scope (singleton).

// Simplified AbstractBeanFactory snippet
public Object getBean(String name) {
    Object bean = getSingleton(name); // check cache
    if (bean == null) {
        bean = createBean(name);      // create if absent
        addSingleton(name, bean);     // put into cache
    }
    return bean;
}

Key design:

Three‑level cache solves circular dependencies.

Thread‑safe via synchronized + double‑checked locking; avoid storing state in singleton beans.

5. Observer Pattern – Event Broadcast Network

Scenario: Decouple event producers from consumers.

Spring usage: ApplicationEvent mechanism.

// Define event
public class OrderCreatedEvent extends ApplicationEvent {
    public OrderCreatedEvent(Order source) { super(source); }
}
// Publish event
@Service
public class OrderService {
    @Autowired
    ApplicationEventPublisher publisher;
    public void createOrder(Order order) {
        // business logic...
        publisher.publishEvent(new OrderCreatedEvent(order));
    }
}
// Listener
@Component
public class EmailListener {
    @EventListener
    public void handleOrderEvent(OrderCreatedEvent event) {
        // send email notification
    }
}

Advantages:

Complete decoupling of source and listeners.

Support asynchronous handling via @Async.

6. Strategy Pattern – Algorithm Switcher

Scenario: Dynamically choose algorithm implementation.

Spring usage: Resource loading strategies.

// Resource loading strategy family
Resource res1 = new ClassPathResource("config.xml"); // classpath
Resource res2 = new UrlResource("http://config.com"); // URL
Resource res3 = new FileSystemResource("/opt/config"); // filesystem
InputStream is = res1.getInputStream();

Design highlights:

Resource interface provides unified abstraction.

ResourceLoader automatically selects appropriate strategy (e.g., payment method switching).

7. Adapter Pattern – Interface Converter

Scenario: Adapt incompatible interfaces.

Spring MVC usage: HandlerAdapter.

// Pseudo‑code handling multiple Controllers
public class RequestMappingHandlerAdapter implements HandlerAdapter {
    public boolean supports(Object handler) {
        return handler instanceof Controller;
    }
    public ModelAndView handle(HttpRequest req, HttpResponse res, Object handler) {
        Controller controller = (Controller) handler;
        return controller.handleRequest(req, res);
    }
}

Value:

DispatcherServlet need not know concrete Controller types.

Adding new Controller types only requires a new adapter.

8. Decorator Pattern – Function Enhancer

Scenario: Dynamically add functionality.

Spring usage: HttpServletRequest wrapper.

// Typical use: cache request body
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(rawRequest);
byte[] body = wrappedRequest.getContentAsByteArray();

Design essence: Wrap original object to add features without modifying it.

9. Builder Pattern – Complex Object Assembler

Scenario: Step‑by‑step construction of complex objects.

Spring usage: BeanDefinitionBuilder.

// Build complex Bean definition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(UserService.class);
builder.addPropertyValue("maxRetry", 3);
builder.setInitMethodName("init");
builder.setScope(BeanDefinition.SCOPE_SINGLETON);
registry.registerBeanDefinition("userService", builder.getBeanDefinition());

Comparison with traditional constructors:

Eliminates messy multi‑parameter constructors.

Construction process becomes clearer and more readable.

10. Chain of Responsibility – Interceptor Skeleton

Scenario: Decouple multi‑step processing flow.

Spring usage: HandlerInterceptor chain.

// Simplified interceptor chain execution
public class HandlerExecutionChain {
    private final List<HandlerInterceptor> interceptors = new ArrayList<>();
    public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) {
        for (int i = 0; i < interceptors.size(); i++) {
            HandlerInterceptor interceptor = interceptors.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, i);
                return false;
            }
        }
        return true;
    }
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor()).order(1);
        registry.addInterceptor(new AuthInterceptor()).order(2);
        registry.addInterceptor(new RateLimitInterceptor()).order(3);
    }
}
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws IOException {
        if (!checkToken(req.getHeader("Authorization"))) {
            res.sendError(401);
            return false;
        }
        return true;
    }
}

Design value:

Open‑Closed: new interceptors added without changing existing code.

Single Responsibility: each interceptor handles one concern.

Dynamic ordering via order() method.

Any node can abort or continue the chain.

Typical anti‑pattern: injecting other interceptors inside an interceptor, which breaks chain independence and may cause circular dependencies.

Summary

Decoupling: Factory separates creation, Observer separates event handling.

Extensibility: Strategy supports algorithm extension, Decorator supports feature extension.

Complexity encapsulation: Template Method encapsulates workflow, Builder encapsulates construction.

Performance trade‑offs: Singleton reduces resource usage, Proxy adds functionality on demand.

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 PatternsJavaarchitecturespring
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.