What Are Design Patterns and How Are They Applied in Spring?
Design patterns are reusable solutions to common software design problems, and the Spring Framework implements patterns such as Inversion of Control, Factory, Singleton, Proxy, Template Method, Observer, Adapter, and Decorator to provide a flexible, maintainable, and testable enterprise Java architecture.
Design patterns capture proven best-practice structures, interactions, and responsibilities that help developers build flexible, maintainable, and testable systems. According to the JavaGuide repository, the Spring Framework heavily relies on these patterns to deliver core features such as Inversion of Control (IoC), Dependency Injection (DI), Aspect-Oriented Programming (AOP), and event handling. Understanding how Spring applies these patterns—as documented in docs/system-design/framework/spring/spring-design-patterns-summary.md—is essential for writing idiomatic Spring code.
Inversion of Control and Dependency Injection
The Inversion of Control (IoC) pattern decouples components so that they do not create their own dependencies. Spring implements this through Dependency Injection (DI), where the IoC container creates beans and injects them into dependent objects via @Autowired, XML configuration, or Java-based configuration.
In docs/system-design/framework/spring/spring-design-patterns-summary.md, the repository explains that this pattern is the foundation of Spring's architecture, allowing the framework to manage component wiring through the BeanFactory and ApplicationContext containers.
@Configuration
public class AppConfig {
@Bean // Spring’s factory method
public Service myService(Repository repo) {
return new ServiceImpl(repo);
}
@Bean
public Repository repo() {
return new JdbcRepository(); // concrete implementation hidden from the caller
}
}
The @Bean methods act as a factory, while Spring automatically handles the injection of the Repository dependency into myService.
Factory Pattern
Spring leverages the Factory Pattern to centralize object creation, allowing for interchangeable implementations and lazy loading. The BeanFactory and ApplicationContext interfaces serve as factories that instantiate and configure beans according to their definitions.
According to the JavaGuide documentation, this abstraction allows Spring to manage the complete lifecycle of objects without the calling code being aware of the concrete classes being instantiated.
Singleton Pattern
By default, Spring manages beans as Singletons, ensuring that only one shared instance exists per container for heavy-weight resources such as thread pools, caches, and database connection pools.
As detailed in docs/system-design/framework/spring/spring-design-patterns-summary.md, Spring registers each singleton bean in a ConcurrentHashMap called singletonObjects. The default scope for every bean is singleton, though this can be overridden with @Scope("prototype") or other scope definitions.
@Service
public class CacheService {
// Only one instance per Spring container
}
// Retrieving the bean twice yields the same object
CacheService a = ctx.getBean(CacheService.class);
CacheService b = ctx.getBean(CacheService.class);
assert a == b; // true – singleton semantics
Proxy Pattern
Spring implements the Proxy Pattern to enable method-level interception for cross-cutting concerns such as transactions, security, and logging. Through Aspect-Oriented Programming (AOP), Spring creates JDK dynamic proxies for interface-based beans or CGLIB subclasses for concrete classes.
The JavaGuide repository notes that this dynamic proxy mechanism allows Spring to wrap target objects transparently, adding behavior before and after method execution without modifying the original source code.
@Service
public class OrderService {
@Transactional // Spring creates a proxy around this method
public void placeOrder(Order o) {
// business logic
}
}
Spring generates a runtime proxy that starts a transaction before placeOrder executes and commits or rolls back after it returns.
Template Method Pattern
The Template Method Pattern provides a fixed workflow while allowing subclasses or callbacks to customize specific steps. Spring applies this pattern extensively in its *Template classes such as JdbcTemplate, HibernateTemplate, and RestTemplate.
As documented in the repository, these template classes define the algorithm skeleton—including resource acquisition, exception handling, and cleanup—while client code supplies callbacks to handle specific data mapping or business logic.
@Autowired
private JdbcTemplate jdbcTemplate;
public List<User> findAll() {
String sql = "SELECT * FROM users";
return jdbcTemplate.query(sql, (rs, rowNum) ->
new User(rs.getLong("id"), rs.getString("name")));
}
JdbcTemplate defines the overall query-execution algorithm, while the lambda supplies the row-mapping step.
Observer Pattern
Spring utilizes the Observer Pattern to facilitate loosely coupled event publishing and handling. Through the ApplicationEvent and ApplicationListener mechanism, components can broadcast events to all registered listeners without direct coupling between publisher and consumer.
According to docs/system-design/framework/spring/spring-design-patterns-summary.md, this event-driven model supports both synchronous and asynchronous event processing, making it ideal for implementing domain events and cross-cutting notifications.
// Event definition
public class OrderCreatedEvent extends ApplicationEvent {
private final Order order;
public OrderCreatedEvent(Object source, Order order) {
super(source);
this.order = order;
}
public Order getOrder() { return order; }
}
// Listener
@Component
public class NotificationListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent e) {
// react to the event
System.out.println("Order created: " + e.getOrder().getId());
}
}
// Publisher
@Component
public class OrderService {
@Autowired ApplicationEventPublisher publisher;
public void create(Order o) {
// persist order …
publisher.publishEvent(new OrderCreatedEvent(this, o));
}
}
Adapter Pattern
The Adapter Pattern allows incompatible interfaces to work together without modifying either side. Spring employs this pattern in multiple subsystems, most notably in Spring MVC’s HandlerAdapter and AOP’s AdvisorAdapter.
As detailed in the JavaGuide repository, HandlerAdapter adapts various controller types to a common invocation contract, while AdvisorAdapter adapts different advice types to the MethodInterceptor interface. This decouples the framework's core dispatcher from specific controller implementations.
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello() { return "hello"; }
}
// Internally, DispatcherServlet uses a HandlerAdapter to invoke the above method,
// decoupling the servlet from the concrete controller class.
Decorator Pattern
Spring applies the Decorator Pattern (also known as the Wrapper Pattern) to add responsibilities to objects dynamically without subclassing. This is evident in DataSource wrappers, HttpMessageConverter chains, and various *Wrapper classes throughout the framework.
According to the repository documentation, these decorators augment behavior at runtime while maintaining the same interface, allowing for transparent enhancement of core functionality such as connection pooling, logging, and transaction management.
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:h2:mem:test");
// Wrap with a proxy that adds logging
return new LoggingDataSource(ds);
}
LoggingDataSource implements DataSource and delegates calls to the underlying Hikari instance, adding extra behaviour.
Key Files in the JavaGuide Repository
The following files in the Snailclimb/JavaGuide repository provide detailed explanations and source references for the patterns discussed above:
| File | Purpose | Link |
|---|---|---|
docs/system-design/framework/spring/spring-design-patterns-summary.md |
Full narrative of each Spring‑related pattern (factory, singleton, proxy, template, observer, adapter, decorator, etc.) | View File |
docs/system-design/design-pattern.md |
General overview of design‑pattern interview topics | View File |
docs/java/basis/proxy.md |
Stand‑alone explanation of the Proxy pattern | View File |
docs/java/io/io-design-patterns.md |
Illustrates Decorator & Adapter patterns in core Java I/O | View File |
Summary
- Design patterns provide reusable solutions to common software design problems, capturing proven structures for flexible and maintainable code.
- Inversion of Control and Dependency Injection form the architectural foundation of Spring, decoupling components through container-managed bean creation and wiring.
- Factory Pattern appears in
BeanFactoryandApplicationContext, centralizing object creation and supporting lazy initialization. - Singleton Pattern is the default bean scope, with instances stored in a
ConcurrentHashMapcalledsingletonObjectsto ensure single instances per container. - Proxy Pattern enables AOP through JDK dynamic proxies or CGLIB subclasses, supporting cross-cutting concerns like transactions and security.
- Template Method Pattern appears in
JdbcTemplateand similar classes, defining algorithm skeletons while allowing custom callbacks. - Observer Pattern drives Spring's event system through
ApplicationEventandApplicationListenerfor loose coupling between publishers and consumers. - Adapter Pattern allows Spring MVC to support multiple controller types via
HandlerAdapterand AOP to adapt various advice types toMethodInterceptor. - Decorator Pattern adds dynamic behavior to objects, seen in DataSource wrappers and message converter chains, without modifying original classes.
Frequently Asked Questions
What is the most important design pattern used in Spring?
Inversion of Control (IoC) and Dependency Injection (DI) are the foundational patterns that make Spring distinct. These patterns decouple object creation from business logic, allowing the framework to manage component wiring through the BeanFactory and ApplicationContext containers, as detailed in docs/system-design/framework/spring/spring-design-patterns-summary.md.
How does Spring implement the Singleton pattern?
Spring implements the Singleton Pattern by default for all beans, storing each instance in a ConcurrentHashMap named singletonObjects within the IoC container. This ensures that only one shared instance exists per container for heavy-weight resources, though developers can override this with @Scope("prototype") or other scope definitions when multiple instances are required.
What is the difference between JDK dynamic proxy and CGLIB in Spring AOP?
Spring uses JDK dynamic proxies when the target bean implements at least one interface, creating a proxy that implements the same interfaces. For concrete classes without interfaces, Spring falls back to CGLIB to generate a subclass proxy. Both approaches allow method interception for cross-cutting concerns like transactions and security without modifying the original source code.
When should I use the Template Method pattern in Spring?
Use the Template Method Pattern when you need to execute a standard workflow—such as database access, JMS messaging, or REST communication—while varying specific steps like result mapping or error handling. Spring's JdbcTemplate, HibernateTemplate, and RestTemplate classes embody this pattern by handling resource management, exception translation, and cleanup, while you supply callbacks or lambdas for custom logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →