Understanding the Open‑Closed Principle Through Spring’s Extension Hooks
The article explains the Open‑Closed Principle, shows how Spring leaves empty hook methods like onRefresh() for extensions, demonstrates the concept with a Java demo, compares inheritance‑based and interface‑based extension approaches, and discusses trade‑offs and practical guidelines for designing extension points.
Why avoid modifying existing source code?
Adding new functionality by directly editing a class is simple but risky because many parts of the codebase may depend on that class; changing it can affect all callers and require extensive regression testing.
Spring’s built‑in extension point
During container startup Spring executes a fixed sequence defined in AbstractApplicationContext.refresh(). One step, onRefresh(), is deliberately left empty for subclasses to override:
protected void onRefresh() throws BeansException {
// default does nothing, for subclasses to implement
}Spring Boot’s web application subclass overrides this method to create the embedded Tomcat server without altering the core startup flow.
@Override
protected void onRefresh() {
super.onRefresh();
createWebServer();
}This keeps the original process unchanged for non‑web projects while automatically starting a web server for web projects, embodying the Open‑Closed Principle.
Demo code that mimics Spring’s mechanism
A base class defines a final start() method that prints messages and calls the hook onRefresh(). The hook does nothing by default.
class AppContext {
public final void start() {
System.out.println("准备环境");
onRefresh();
System.out.println("启动完成");
}
// default hook, does nothing
protected void onRefresh() {}
}A subclass overrides the hook to insert its own behavior:
class WebAppContext extends AppContext {
@Override
protected void onRefresh() {
System.out.println("创建Web服务器");
}
}
public static void main(String[] args) {
new AppContext().start();
new WebAppContext().start();
}Running on Java 17 produces:
基类启动:
准备环境
启动完成
子类启动:
准备环境
创建Web服务器
启动完成The subclass’s output is inserted exactly between the two base‑class messages, showing how a fixed process can be extended without altering the original code.
Beyond inheritance – interface‑based extension
Inheritance is only one way to satisfy the Open‑Closed Principle. Spring also supports interface‑based extensions, exemplified by BeanPostProcessor, which allows custom logic before and after bean creation without modifying the container source.
The two approaches differ in where the extension point lives. Hook methods require a subclass and allow only one active implementation, suitable for stable skeletons with few insertion points. Interface extensions can have multiple implementations registered, fitting scenarios with many interchangeable plug‑ins.
Choosing an extension strategy
Direct source change : modify the original class. Simple but forces all dependents to retest; third‑party libraries become unusable.
Inheritance override : subclass and override a hook. Works when the base class defines a fixed flow with a single extension point, but couples the subclass to the parent’s internals and becomes hard to maintain with deep hierarchies.
Interface extension : implement an interface and register it. Allows many independent extensions and free placement, but requires upfront interface design and higher abstraction cost.
Composition/delegation : hold an existing object and wrap it. Useful for enhancing behavior without a parent‑child relationship, though it adds forwarding code and an extra layer to maintain.
In practice, prefer an existing hook or interface first; if none exists and you need to augment an object, consider composition; use inheritance for internal, stable flows; treat direct source changes as a last resort.
The cost of extension points
Leaving a hook is not free. Making a method protected or fixing a process with final commits future extensions to that shape. If the anticipated direction is wrong, the hook becomes dead code that cannot be removed safely.
Spring’s hooks were often added after real‑world changes revealed the need. A pragmatic approach is to first change the code to solve the immediate problem, then, when the same change recurs, refactor the code to extract a reusable extension point.
Conclusion
The real difficulty of the Open‑Closed Principle lies in deciding where to expose extension points. Correctly placed hooks make future extensions effortless; misplaced ones incur maintenance overhead. Anticipating stable extension points requires experience from repeated business‑driven modifications.
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.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
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.
