Home / Spring Boot / Spring Boot Internals / Design Patterns Used Inside Spring

Design Patterns Used Inside Spring

Spring is a very good example of design patterns in real code. Knowing where each pattern lives helps you understand the framework and answer senior level interview questions.

Pattern map

PatternWhere Spring uses itWhy
Singleton (per container)Default bean scopeShare one instance and save memory
Factory / Factory MethodBeanFactory, FactoryBean, @Bean methodsHide how objects are created
Dependency Injection / IoCThe whole containerLoose coupling, easy testing
ProxyAOP, @Transactional, @Cacheable, @Async, Spring Data repositoriesAdd behavior around a bean without changing it
Template MethodJdbcTemplate, RestTemplate, JmsTemplate, TransactionTemplateFixed steps (open, execute, close), you provide only the variable part
ObserverApplicationEvent and @EventListenerPublish events to many listeners
StrategyPasswordEncoder, HttpMessageConverter, Resource, AuthenticationProviderSwap algorithms without changing callers
Front ControllerDispatcherServletOne entry point for all web requests
Chain of ResponsibilitySecurity filter chain, servlet filters, HandlerInterceptor chainEach element handles or passes on
AdapterHandlerAdapter, JpaVendorAdapterMake different interfaces work together
DecoratorBeanWrapper, HttpServletRequestWrapperAdd features by wrapping
BuilderUriComponentsBuilder, HttpSecurity, RestClient.Builder, SpringApplicationBuilderReadable step-by-step creation

Proxy in action (what @Transactional really does)

caller ──► [ CGLIB proxy of OrderService ] ──► real OrderService
                 │  1. begin transaction
                 │  2. call real method
                 │  3. commit or rollback
                 └─ the caller never sees the difference

Template Method: JdbcTemplate

JdbcTemplate does the boring work (get connection, create statement, handle exceptions, close resources) and calls your code only for the variable part.

@Repository
public class UserDao {
    private final JdbcTemplate jdbc;
    public UserDao(JdbcTemplate jdbc) { this.jdbc = jdbc; }

    public List<String> findNames(int minAge) {
        return jdbc.query("select name from users where age >= ?",
                (rs, rowNum) -> rs.getString("name"),      // your part: map one row
                minAge);
    }
}

Strategy: pick behavior by name with Map injection

public interface DiscountStrategy { BigDecimal apply(BigDecimal price); }

@Component("regular")
class RegularDiscount implements DiscountStrategy { public BigDecimal apply(BigDecimal p) { return p; } }

@Component("festival")
class FestivalDiscount implements DiscountStrategy { public BigDecimal apply(BigDecimal p) { return p.multiply(new BigDecimal("0.80")); } }

@Service
public class PricingService {
    private final Map<String, DiscountStrategy> strategies;      // key = bean name

    public PricingService(Map<String, DiscountStrategy> strategies) { this.strategies = strategies; }

    public BigDecimal price(String type, BigDecimal price) {
        return strategies.getOrDefault(type, strategies.get("regular")).apply(price);
    }
}

Factory: FactoryBean

@Component
public class HttpClientFactoryBean implements FactoryBean<HttpClient> {
    @Override public HttpClient getObject() { return HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build(); }
    @Override public Class<?> getObjectType() { return HttpClient.class; }
}
// The bean named "httpClientFactoryBean" gives you an HttpClient (not the factory).
// Use "&httpClientFactoryBean" to get the factory itself.

Observer: events

public record UserRegistered(String email) { }

@Service
class RegistrationService {
    private final ApplicationEventPublisher publisher;
    RegistrationService(ApplicationEventPublisher publisher) { this.publisher = publisher; }
    void register(String email) { /* save user */ publisher.publishEvent(new UserRegistered(email)); }
}

@Component
class WelcomeMailListener { @EventListener void on(UserRegistered e) { System.out.println("Welcome mail to " + e.email()); } }

@Component
class AnalyticsListener { @Async @EventListener void on(UserRegistered e) { System.out.println("Track " + e.email()); } }

Common mistake: Learning patterns only as textbook diagrams. Open the framework source (for example JdbcTemplate.execute or DispatcherServlet.doDispatch) once, and you will remember the pattern for life.

Interview questions

Which patterns explain how @Transactional works?

Proxy (the call is intercepted), Template Method (TransactionTemplate style), and Chain of Responsibility (the advisor chain in the interceptor).

Is a Spring singleton the Singleton pattern?

No. The Singleton pattern guarantees one instance per class loader. A Spring singleton is one instance per bean definition per container, and you can have several beans of the same class.

Where do you see Strategy in Spring Security?

PasswordEncoder, AuthenticationProvider, AuthorizationManager and SecurityContextRepository are interfaces with swappable implementations.