10 Years In, I Discovered Spring Boot + Decorator Pattern for Elegant Extension

After years of piling logging, caching, and security directly into Spring Boot services, the author shows how applying the Decorator pattern—implemented via dedicated @Service beans and @Primary/@Qualifier wiring—creates a clean, extensible chain that respects the Open/Closed Principle, while outlining practical steps, trade‑offs, and pitfalls.

LuTiao Programming
LuTiao Programming
LuTiao Programming
10 Years In, I Discovered Spring Boot + Decorator Pattern for Elegant Extension

Problem: Bloated Service Layer

When a Service class starts with only core business logic, developers often add logging, caching, permission checks, and monitoring directly inside the same class as requirements grow. The method becomes packed with side‑effects, polluting business logic, increasing modification risk, violating the Open/Closed Principle (OCP), and raising maintenance cost.

// Bloated service example
@Service
public class BloatedProductService implements ProductService {
    private final ProductRepository productRepository;
    private final Logger logger = LoggerFactory.getLogger(BloatedProductService.class);
    private final CacheManager cacheManager;
    private final SecurityService securityService;

    @Override
    public Product getProductById(String id) {
        logger.info("开始查询商品 {}", id);
        securityService.authorize("read:product", id);
        Product product = getFromCache(id);
        if (product != null) {
            return product;
        }
        product = productRepository.findById(id).orElse(null);
        if (product != null) {
            putInCache(id, product);
        }
        return product;
    }
}

Business logic is polluted.

Modification risk increases.

Violates OCP.

Maintenance cost skyrockets.

Open/Closed Principle

Software should be "open for extension, closed for modification".

Decorator Pattern

The Decorator pattern enhances an object's behavior without changing its source code by wrapping the original object with one or more decorator classes, each adding a specific concern.

Enhance functionality while keeping the original object unchanged.

The chain can be visualised as Original Service → Logging Decorator → Caching Decorator .

Decorator chain diagram
Decorator chain diagram

Step‑by‑Step Implementation in Spring Boot

1. Define Core Interface and Simple Implementation

// src/main/java/com/icoderoad/product/domain/Product.java
package com.icoderoad.product.domain;

public record Product(String id, String name, double price) {}
// ProductService.java
package com.icoderoad.product.service;

import java.util.List;

public interface ProductService {
    Product getProductById(String id);
    List<Product> getAllProducts();
}
// SimpleProductService.java
package com.icoderoad.product.service.impl;

import com.icoderoad.product.service.ProductService;
import org.springframework.stereotype.Service;

@Service("baseProductService")
public class SimpleProductService implements ProductService {
    private final ProductRepository productRepository;

    public SimpleProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Override
    public Product getProductById(String id) {
        System.out.println("查询数据库: " + id);
        return productRepository.findById(id).orElse(null);
    }

    @Override
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }
}

2. Implement Logging Decorator

// LoggingProductServiceDecorator.java
package com.icoderoad.product.decorator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("loggingProductService")
public class LoggingProductServiceDecorator implements ProductService {
    private final ProductService delegate;
    private static final Logger log = LoggerFactory.getLogger(LoggingProductServiceDecorator.class);

    public LoggingProductServiceDecorator(@Qualifier("baseProductService") ProductService delegate) {
        this.delegate = delegate;
    }

    @Override
    public Product getProductById(String id) {
        log.info("开始查询商品: {}", id);
        Product product = delegate.getProductById(id);
        log.info("查询结束: {}", product);
        return product;
    }

    @Override
    public List<Product> getAllProducts() {
        log.info("查询全部商品");
        return delegate.getAllProducts();
    }
}

3. Implement Caching Decorator

// CachingProductServiceDecorator.java
package com.icoderoad.product.decorator;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
@Primary
public class CachingProductServiceDecorator implements ProductService {
    private final ProductService delegate;
    private final Map<String, Product> cache = new ConcurrentHashMap<>();

    public CachingProductServiceDecorator(@Qualifier("loggingProductService") ProductService delegate) {
        this.delegate = delegate;
    }

    @Override
    public Product getProductById(String id) {
        if (cache.containsKey(id)) {
            return cache.get(id);
        }
        Product product = delegate.getProductById(id);
        if (product != null) {
            cache.put(id, product);
        }
        return product;
    }

    @Override
    public List<Product> getAllProducts() {
        return delegate.getAllProducts();
    }
}

4. Let Spring Assemble the Chain

Using @Primary on the caching decorator marks it as the entry point. @Qualifier on each constructor selects the bean to wrap, resulting in the call order: Cache → Logging → Original Service.

Spring bean wiring diagram
Spring bean wiring diagram
productService.getProductById("1");
// Execution order: Cache → Logging → Original Service

Benefits of the Decorator Approach

Responsibilities are cleanly separated: logging, caching, and core business logic reside in independent classes.

Complies with the Open/Closed Principle; new features are added by creating additional decorators without modifying existing code.

Explicit ordering via @Qualifier makes the call sequence transparent.

Configuration Class for Complex Chains

// ProductServiceConfig.java
package com.icoderoad.product.config;

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

@Configuration
public class ProductServiceConfig {
    @Bean
    public ProductService baseProductService(ProductRepository repository) {
        return new SimpleProductService(repository);
    }

    @Bean
    public ProductService loggingProductService(ProductService base) {
        return new LoggingProductServiceDecorator(base);
    }

    @Bean
    @Primary
    public ProductService cachingProductService(ProductService logging) {
        return new CachingProductServiceDecorator(logging);
    }
}

Clear ordering.

Strong controllability.

Scales better for large systems.

Common Pitfalls

Too many decorators: long chains make debugging difficult and increase the JVM call stack; keep layers typically between 3 and 5.

State pollution: only the caching decorator should hold state; core business logic must remain stateless.

Performance overhead: each layer adds a method call, so high‑frequency interfaces should be decorated cautiously.

Decorator vs. AOP

Decorator is suitable when precise control over invocation order, targeting a specific bean, or an explicit chain structure is required.

AOP is suitable for global cross‑cutting concerns such as logging or transaction management that should be woven uniformly and remain completely decoupled from business code.

AOP solves "cross‑cutting"; Decorator solves "compositional enhancement".
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.

JavaSpring Bootdecorator-patternopen-closed-principleservice-layeraop-comparison
LuTiao Programming
Written by

LuTiao Programming

LuTiao Programming is a friendly community offering free programming lessons. We inspire learners to explore new ideas and technologies and quickly acquire job-ready skills.

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.