Unlocking Spring’s Design Patterns: From Proxy to Adapter Explained
This article explores how Spring implements classic design patterns—Proxy, Strategy, Decorator, Singleton, Simple Factory, Factory Method, Observer, Template, and Adapter—detailing their purpose, core code snippets, and the way Spring leverages them to provide flexible, extensible, and maintainable backend functionality.
Preface
A reader asked which design patterns are used in the Spring framework, prompting this comprehensive overview.
Proxy Pattern
Proxy objects share the same interface as the target, allowing clients to interact through the proxy, which can add pre‑ and post‑processing. Spring AOP uses dynamic proxies to generate proxy classes at runtime, inserting interceptors to enhance method behavior.
Strategy Pattern
Spring’s dynamic proxy choice (JDK proxy or CGLIB) is a classic use of the Strategy pattern. The AopProxy interface defines the strategy, with JdkDynamicAopProxy and CglibAopProxy as concrete implementations. The AopProxyFactory (default DefaultAopProxyFactory) creates the appropriate proxy based on runtime conditions.
The factory decides which strategy to apply, with line 10 of the source code containing the conditional logic.
Decorator Pattern
To keep cache writes consistent with database transactions, Spring uses the Decorator pattern. TransactionAwareCacheDecorator implements Cache and delegates operations to the target cache while adding transactional behavior.
Singleton Pattern
Spring beans are singleton by default. The framework implements this via a singleton registry backed by a ConcurrentHashMap:
public class DefaultSingletonBeanRegistry {
// Thread‑safe container for singleton objects
private final Map singletonObjects = new ConcurrentHashMap();
protected Object getSingleton(String beanName) {
// Retrieve from map
Object singletonObject = singletonObjects.get(beanName);
// If absent, create via reflection and store
if (singletonObject == null) {
singletonObjects.put(beanName, Class.forName(beanName).newInstance());
}
// Return the instance
return singletonObjects.get(beanName);
}
}Simple Factory Pattern
Spring’s BeanFactory exemplifies the Simple Factory pattern, providing a central method getBean that creates beans via reflection:
BeanFactory bf = new ClassPathXmlApplicationContext("spring.xml");
FlyFish flyFishBean = (FlyFish) bf.getBean("flyfishBean");Factory Method Pattern
Spring’s FactoryBean follows the Factory Method pattern, allowing custom creation logic inside getObject. Implementations such as FlyFishFactoryBean produce FlyFish instances while the container returns the product, not the factory itself.
BeanFactory bf = new ClassPathXmlApplicationContext("spring.xml");
FlyFish flyFishBean = (FlyFish) bf.getBean("flyfishBean");Observer Pattern
Spring implements the Observer pattern with three components: an Event (message), a Listener (observer), and a Publisher (subject). The framework registers listeners in the ApplicationContext, and event dispatch is handled by ApplicationEventMulticaster, supporting both synchronous and asynchronous delivery.
// Event definition
public class DemoEvent extends ApplicationEvent {
private String message;
public DemoEvent(Object source, String message) { super(source); this.message = message; }
public String getMessage() { return this.message; }
}
// Listener implementation
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println(event.getMessage());
}
}
// Publisher usage
@Component
public class DemoPublisher {
@Autowired private ApplicationContext ctx;
public void publishEvent(DemoEvent e) { ctx.publishEvent(e); }
}Template Pattern
The Spring bean creation process follows the Template pattern, allowing users to plug in custom steps (e.g., initialization callbacks) without modifying core framework code.
Adapter Pattern
Spring MVC adapts various controller definitions (annotation‑based, Controller interface, or HttpServlet) to a common handler interface via the Adapter pattern. Different HandlerAdapter implementations (e.g., AnnotationMethodHandlerAdapter, SimpleControllerHandlerAdapter, SimpleServletHandlerAdapter) translate specific controller methods into a uniform handle() call used by DispatcherServlet.
// Annotation‑based controller
@Controller
public class DemoController {
@RequestMapping("/FlyFish")
public ModelAndView getEmployeeName() {
ModelAndView model = new ModelAndView("FlyFish");
model.addObject("message", "FlyFish");
return model;
}
}
// Interface‑based controller
public class DemoController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
ModelAndView model = new ModelAndView("FlyFish");
model.addObject("message", "FlyFish");
return model;
}
}
// Servlet‑based controller
public class DemoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello World.");
}
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
