Patterns in Spring¶
Spring uses and enables many patterns, but an annotation is not itself a design pattern. Understanding the collaboration and runtime mechanism prevents cargo- cult use of stereotypes.
Pattern map¶
| Spring mechanism | Related pattern or principle | Important distinction |
|---|---|---|
| IoC container and constructor injection | Dependency Injection / IoC | DI is not the same as Dependency Inversion |
BeanFactory and FactoryBean |
Factory | A FactoryBean creates another exposed object |
| Bean scopes | Managed lifecycle | Spring singleton is per bean definition per container |
| AOP advice | Proxy / interceptor chain | Calls must pass through the applicable proxy |
JdbcTemplate and similar helpers |
Template plus callback strategy | Resource/error workflow is fixed; operation varies |
Spring MVC DispatcherServlet |
Front Controller | One entry coordinates request dispatch |
| Handler adapters | Adapter | Different handler styles fit one dispatch process |
| Application events | Observer | In-process events are not a durable message broker |
| Spring Data repositories | Repository | Persistence access is expressed as a collection-like role |
| Security filter chain | Chain of Responsibility / intercepting filter | Order and short-circuit behavior matter |
Dependency Injection¶
Spring's container creates beans and supplies collaborators. Constructor injection makes required dependencies explicit and supports fully initialized, immutable component references.
@Service
final class PlaceOrder {
private final OrderRepository orders;
private final PricingPolicy pricing;
PlaceOrder(OrderRepository orders, PricingPolicy pricing) {
this.orders = orders;
this.pricing = pricing;
}
}
The pattern does not require an interface for every class. Introduce an abstraction when clients need a stable role, multiple implementations, an architectural boundary, or a meaningful test substitute.
Singleton scope is not the GoF Singleton¶
The default Spring singleton scope asks one container to manage one instance for
a bean definition. The class need not have a private constructor or global
getInstance() method, and another application context can manage another
instance. Singleton-scoped beans must not store unsynchronized request-specific
mutable state.
Proxy-based cross-cutting behavior¶
Spring AOP can apply transactions, caching, method security, async execution, and custom advice through proxies. Depending on configuration and target type, Spring uses interface-based or class-based proxies.
sequenceDiagram
participant Caller
participant Proxy
participant Advice
participant Target
Caller->>Proxy: method call
Proxy->>Advice: before / around
Advice->>Target: proceed
Target-->>Advice: result or exception
Advice-->>Proxy: transformed outcome
Proxy-->>Caller: result or exception
In ordinary proxy mode, self-invocation does not cross the proxy, so calling one
method on this does not activate separate advice declared only on the invoked
method. Private methods cannot be advised through subclass overriding, and final
types/methods constrain class-based proxies. Design and test transaction,
security, caching, and async boundaries as behavior—not as decorative annotations.
For caching specifically, key design, freshness, invalidation, capacity, and
stampede handling remain explicit application-cache decisions.
Template and callback¶
Template-style Spring APIs centralize repeated workflow such as resource acquisition, cleanup, and exception translation. A callback, lambda, or strategy provides the operation-specific step. This differs from the inheritance-based GoF Template Method while retaining the invariant algorithm skeleton.
Events and domain communication¶
Application events can decouple an in-process publisher from listeners. Define whether delivery is synchronous or asynchronous, how listener failures affect the publisher, and how transaction boundaries interact. For durable integration events, use a broker/outbox design rather than assuming an in-memory event will survive process failure. The transactional outbox guide shows the durability boundary and why consumers still handle duplicates.
Patterns to use carefully¶
- Service Locator through direct
ApplicationContext#getBeancalls hides dependencies. - A repository per table may expose persistence structure rather than domain intent.
- An interface with exactly one implementation is not automatically wrong, but creating it only because “Spring requires interfaces” is unnecessary.
- Excessive application events can obscure control flow and failure ownership.
- A generic base service/controller can erase domain contracts for superficial reuse.