Author: JarryLi
Target Audience: Java engineers and students interested in design patterns and framework principles. If you want to learn and apply design patterns, understanding the Spring framework is one of the best approaches.
I remember when I first encountered Spring many years ago, I found it magical: objects could be automatically injected through xml configuration, or even a simple @Autowired annotation could do the job? AOP could add logging and transactions uniformly without invading business code? At that time, the mainstream was still the EJB system, using native Servlet, JSP, and Struts frameworks. Facing seemingly "magical" capabilities like IoC and DI, I couldn't help but want to explore: how are they actually implemented?
From then on, I started learning Spring source code. When I really looked into it, I discovered that these are not magical, but the result of extensive engineering design: layered abstractions, interface decoupling, and reuse of classic design patterns. The source code is not small, and it's difficult to read it all at once, so I could only learn bit by bit with questions.
纸上得来终觉浅,绝知此事要躬行。So I wrote a simplified version of the SpringWind framework by referencing the Spring framework, implementing Spring's core features with the most concise code, making it easy for every developer who wants to deeply understand framework principles to understand.
In this process, not only did I understand core concepts like IoC, DI, and AOP, as well as Bean lifecycle, DispatcherServlet controller and ApplicationContext container systems, but I also deeply understood why Spring is designed this way. Now, I share these practical experiences and insights, hoping to help you quickly grasp Spring's core principles.
Code Repository: https://github.com/microwind/design-patterns/tree/main/practice-projects/springwind
- About SpringWind Framework
- Spring's Design Philosophy
- Spring's Core Features
- Deep Understanding of Spring Framework
- Designing SpringWind Architecture by Referencing Spring Framework
- SpringWind vs Spring MVC Comparison
- What is SpringWind Framework For
- SpringWind Project Structure
- SpringWind Usage Practice
- Quick Start
- SpringWind Improvement Points
SpringWind is a lightweight educational Java Web framework designed to help developers deeply understand the core principles of the Spring framework. By implementing Spring's core mechanisms from scratch (IoC, DI, AOP, three-level cache, MVC, etc.), it transforms the framework from a "black box" into a completely understandable "white box".
SpringWind is not intended to replace Spring Framework, but serves as:
- Learning Tool: The best practice for understanding Spring core principles
- Educational Framework: Helps you gradually understand the Spring framework through a progressive approach
- Quick Prototyping: For small projects, SpringWind is more lightweight and convenient
- Exploration Platform: Can verify new architectural ideas and concepts, continuously innovating
SpringWind implements the following core functionalities of Spring Framework:
| Feature | Description | Corresponding Spring Feature |
|---|---|---|
| IoC Container | Automatically scans, registers, and manages Bean lifecycle | ApplicationContext |
| Dependency Injection | Achieves automatic assembly through @Autowired |
Dependency Injection |
| Component Annotations | @Component, @Service, @Controller, @Repository |
Stereotype Annotations |
| MVC Pattern | Simulates Spring MVC's request handling mechanism | Spring MVC |
| Request Mapping | @RequestMapping implements URL to method mapping |
Request Mapping |
| Parameter Binding | @PathVariable, @RequestParam, @RequestBody |
Parameter Binding |
| AOP Support | Aspect-oriented programming based on JDK/CGLIB dynamic proxies | Spring AOP |
| BeanPostProcessor | Pre and post processing of Beans, supports lifecycle intervention | BeanPostProcessor |
| SmartInstantiationAwareBeanPostProcessor | Supports early proxy creation during Bean instantiation to resolve circular dependencies | SmartInstantiationAwareBeanPostProcessor |
| Three-Level Cache | Elegantly solves circular dependency problems | Circular Dependency Resolution |
| JDBC Template | Template class to simplify database operations | JdbcTemplate |
| JSON Response | Automatically serializes objects to JSON | @ResponseBody |
SpringWind references Spring Framework's design philosophy but makes extensive simplifications:
Similarities:
- Core design philosophy (IoC, DI, AOP)
- Annotation-driven development
- Layered architecture (Controller-Service-Dao)
- Three-level cache to solve circular dependencies
- Template method pattern to encapsulate boilerplate code
Differences:
- Code Scale: SpringWind has about 5000 lines of code, Spring Framework has over 500,000 lines
- Feature Scope: SpringWind focuses on core features, Spring provides comprehensive enterprise-level features
- Complexity: SpringWind removes many abstraction layers, making it easier to understand
- Production Ready: Spring has been extensively validated, SpringWind is mainly for learning
Core Technologies:
├── Java 17+ # Modern Java features
├── Jakarta Servlet API 6.1 # Web container standard
├── CGLIB 3.3.0 # Bytecode enhancement (AOP)
├── SLF4J + Logback # Logging framework
└── Jackson 2.18.2 # JSON serialization
Build Tools:
└── Maven 3.6+
Runtime Environment:
├── Embedded Tomcat 11 # Embedded web server
└── H2 / MySQL # Database (optional)
To reduce learning costs, SpringWind's annotations are consistent with Spring MVC:
| SpringWind Annotation | Spring MVC Annotation | Purpose | Example |
|---|---|---|---|
@Component |
@Component |
Mark generic component | @Component |
@Service |
@Service |
Mark service layer component | @Service |
@Controller |
@Controller |
Mark controller component | @Controller |
@Repository |
@Repository |
Mark data access layer component | @Repository |
@Autowired |
@Autowired |
Auto-inject dependencies | @Autowired private UserDao dao; |
@RequestMapping |
@RequestMapping |
Map HTTP requests | @RequestMapping("/user") |
@PathVariable |
@PathVariable |
Bind path variables | @PathVariable("id") Long id |
@RequestParam |
@RequestParam |
Bind request parameters | @RequestParam("name") String name |
@RequestBody |
@RequestBody |
Bind request body (JSON) | @RequestBody User user |
@ResponseBody |
@ResponseBody |
Return JSON response | @ResponseBody |
@Aspect |
@Aspect |
Mark aspect class | @Aspect |
@Before |
@Before |
Before advice | @Before("com.example.*") |
@After |
@After |
After advice | @After("com.example.*") |
@Around |
@Around |
Around advice | @Around("com.example.*") |
Usage Example:
// Define Controller
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService; // Auto-injection
@RequestMapping("/get")
@ResponseBody
public User getUser(@RequestParam("id") Long id) {
return userService.getUserById(id);
}
@RequestMapping("/detail/{id}")
@ResponseBody
public User getUserDetail(@PathVariable("id") Long id) {
return userService.getUserById(id);
}
@RequestMapping("/create")
@ResponseBody
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
}If you're familiar with Spring MVC, you can understand SpringWind with zero learning cost! Once you understand SpringWind, you'll understand Spring.
Step 1: Quick Experience (10 minutes)
- Run the example project and see the effects
- Visit several API endpoints to understand the request flow
Step 2: Understand Core Principles (1-2 hours)
- Read "Spring's Design Philosophy" and "Spring's Core Features" in this document
- Debug with breakpoints to observe Bean creation and dependency injection process
- Understand how three-level cache solves circular dependencies
Step 3: Deep Dive into Source Code (3-5 hours)
- Read
SpringWindApplicationContext.java(IoC container core) - Read
AspectProcessor.java(AOP proxy creation) - Read
DispatcherServlet.java(MVC request dispatching) - Read
JdbcTemplate.java(JDBC template method)
Step 4: Hands-on Practice (1-2 days)
- Write a small project based on SpringWind
- Try adding new features (like transaction support)
- Compare with Spring Framework source code to understand design differences
Step 5: Contribute to Spring (Advanced)
- After understanding Spring's design philosophy, you can participate in the Spring community
- Submit bug fixes or new feature Pull Requests
Before building the wheel, we need to understand why Spring is designed this way. Many people find the Spring framework complex, but its core philosophy is actually very simple.
Pain Points of Traditional Approach:
// Traditional approach: manage objects yourself
public class UserService {
private UserDao userDao = new UserDaoImpl(); // Hard-coded dependency
private EmailService emailService = new EmailServiceImpl(); // Hard-coded dependency
public void registerUser(User user) {
userDao.save(user);
emailService.sendWelcomeEmail(user);
}
}What's wrong with writing it this way? Actually, this approach is easiest to understand, but not convenient for extension. When the project grows large, subsequent maintenance becomes troublesome.
- High Coupling: UserService strongly depends on specific implementation classes
- Difficult to Test: Cannot replace with Mock objects for unit testing
- Configuration Chaos: Database connections, email configurations are all hard-coded in the code
Spring's Solution:
// Spring approach: dependency injection
@Service
public class UserService {
@Autowired
private UserDao userDao; // Injected by container
@Autowired
private EmailService emailService;
public void registerUser(User user) {
userDao.save(user);
emailService.sendWelcomeEmail(user);
}
}Now, object creation and management are handed over to the IoC container. Business code only needs to declare "what I need" without caring about "how to create it". This is the core idea of Inversion of Control.
Suppose you want to add logging to all Service methods. The traditional approach would be:
public class UserService {
public void registerUser(User user) {
System.out.println("Starting registerUser"); // Logging code
long start = System.currentTimeMillis(); // Performance monitoring code
userDao.save(user); // Real business logic
long end = System.currentTimeMillis();
System.out.println("Completed, took: " + (end - start) + "ms");
}
}Business code is mixed with logging and monitoring code, making it very bloated. Moreover, if there are 100 methods, these codes would have to be repeated 100 times.
AOP Solution:
@Aspect
public class LoggingAspect {
@Before("com.example.service.*.*")
public void logBefore(Method method) {
System.out.println("Starting: " + method.getName());
}
@After("com.example.service.*.*")
public void logAfter(Method method) {
System.out.println("Completed: " + method.getName());
}
}
// Business code remains clean
@Service
public class UserService {
public void registerUser(User user) {
userDao.save(user); // Only focus on business logic
}
}Cross-cutting concerns like logging, transactions, and permission checks are extracted, making business code much cleaner.
Early Spring required writing extensive XML configurations:
<!-- Ancient Spring configuration -->
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.example.UserDaoImpl"/>Now you only need:
@Service
public class UserService {
@Autowired
private UserDao userDao;
}One annotation gets it done. This is the power of convention over configuration.
JDBC operations have a lot of boilerplate code:
// Traditional JDBC: 50+ lines of code
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
stmt.setLong(1, userId);
rs = stmt.executeQuery();
if (rs.next()) {
return new User(rs);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (rs != null) try { rs.close(); } catch (SQLException e) {}
if (stmt != null) try { stmt.close(); } catch (SQLException e) {}
if (conn != null) try { conn.close(); } catch (SQLException e) {}
}Spring JdbcTemplate:
// Concise JdbcTemplate: 2 lines of code
User user = jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
(rs, rowNum) -> new User(rs),
userId
);Getting connections, closing resources - these fixed steps are encapsulated. You only need to focus on SQL and result mapping.
After understanding Spring's design philosophy, let's see how its core features are implemented.
The IoC container is like an object factory, responsible for:
- Scanning Components: Find all classes with
@Component,@Service, etc. annotations - Registering Bean Definitions: Store class metadata
- Instantiating Beans: Create objects through reflection
- Dependency Injection: Inject dependent objects
- Lifecycle Management: Manage Bean creation and destruction
Bean Lifecycle:
---
title: Bean Lifecycle (Complete Flow)
---
graph LR
A[Scan Components] --> B[Register BeanDefinition]
B --> C[Instantiate Bean<br>Call Constructor]
C --> D["Put into Third-Level Cache<br>ObjectFactory<br>Key!"]
D --> E["Dependency Injection<br>@Autowired Fields<br>May Trigger Circular Dependencies"]
E --> F["Pre-processor<br>postProcessBefore<br>Initialization"]
F --> G["Initialization Method<br>@PostConstruct"]
G --> H["Post-processor<br>postProcessAfter<br>Initialization<br>AOP Proxy Creation"]
H --> I["Put into First-Level Cache<br>Clear Second & Third-Level Caches"]
I --> J[Bean Ready<br>Can Be Used]
J --> K["Destroy<br>@PreDestroy"]
style A fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style B fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style C fill:#f1f8e9,stroke:#7cb342,stroke-width:2px
style D fill:#fff8e1,stroke:#ffb300,stroke-width:3px
style E fill:#fce4ec,stroke:#c2185b,stroke-width:2px
style F fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style G fill:#e8f5e8,stroke:#388e3c,stroke-width:2px
style H fill:#ffe0b2,stroke:#e65100,stroke-width:2px
style I fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
style J fill:#a5d6a7,stroke:#00695c,stroke-width:2px
style K fill:#FFE4E1,stroke:#B20E7E,stroke-width:2px
Key Step Explanations:
- Step 4 (Third-Level Cache): This is the key to solving circular dependencies, putting ObjectFactory instead of the Bean itself
- Step 5 (Dependency Injection): If the dependent Bean is being created, it will get early reference from third-level cache, triggering circular dependency resolution mechanism
- Step 8 (Post-processor): AOP proxy is usually created here, but if already created in step 4's getEarlyBeanReference, it will be skipped
In SpringWind, we use a core class SpringWindApplicationContext to implement these features:
public class SpringWindApplicationContext {
// First-level cache: fully initialized Bean objects
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// Second-level cache: early Bean references (for solving circular dependencies)
private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
// Third-level cache: ObjectFactory factory (for delayed generation of early references, supporting AOP)
private Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>();
// Set of Beans currently being created (for detecting circular dependencies)
private Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>());
public SpringWindApplicationContext(Class<?> configClass) {
scanComponents(configClass); // 1. Scan components
createSingletonBeans(); // 2. Create Beans
dependencyInjection(); // 3. Dependency injection
invokeInitMethods(); // 4. Execute initialization methods
}
}This is a common interview question at major companies. Suppose we have this circular dependency:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // A depends on B
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // B depends on A, forming a cycle
}If not handled, it would fall into an infinite loop: Create A → Need B → Create B → Need A → Create A → ...
Spring's Solution: Three-Level Cache
The three-level cache is Spring's most ingenious design for solving circular dependencies. It includes:
- First-level cache (singletonObjects): Stores fully initialized singleton Bean objects (dependency injection completed, initialization methods executed)
- Second-level cache (earlySingletonObjects): Stores early Bean references (instantiated but not yet completed dependency injection and initialization)
- Third-level cache (singletonFactories): Stores ObjectFactory object factories (Note: Not the Bean itself!)
- The factory's role is to delay generation of early references, only calling getObject() when actually needed
- If the Bean needs AOP proxy, it will create the proxy object in ObjectFactory.getObject()
- This ensures that multiple Beans in circular dependency get the same proxy object, not multiple different instances
Specific Flow:
// Process of creating A (corresponds to code SpringWindApplicationContext.java)
1. getBean("beanA") is called
2. Check first-level cache singletonObjects → Not found
3. Check if being created → Not in creation set, continue creation process
4. Mark A as being created: singletonsCurrentlyInCreation.add("beanA")
5. Instantiate A: call constructor, get raw object (dependencies not yet injected)
6. **Key Step**: Put ObjectFactory into third-level cache
singletonFactories.put("beanA", () -> getEarlyBeanReference("beanA", rawBeanA))
// Note: Putting factory object, not Bean itself!
7. Start injecting dependencies for A: doDependencyInjection(beanA)
8. Discover A depends on B, call getBean("beanB")
// Process of creating B (nested within A's creation process)
9. Check first-level cache → B not found
10. Mark B as being created: singletonsCurrentlyInCreation.add("beanB")
11. Instantiate B: get raw object
12. Put ObjectFactory into third-level cache:
singletonFactories.put("beanB", () -> getEarlyBeanReference("beanB", rawBeanB))
13. Start injecting dependencies for B: discover B depends on A, call getBean("beanA")
// Circular dependency detection and resolution (Key!)
14. Check first-level cache → A not found (because A hasn't completed initialization)
15. Discover A in creation set: singletonsCurrentlyInCreation.contains("beanA") = true
16. Check second-level cache earlySingletonObjects → A not found
17. Check third-level cache singletonFactories → Found A's ObjectFactory ✓
18. **Call ObjectFactory.getObject()**
→ Internal call to getEarlyBeanReference("beanA", rawBeanA)
→ If A needs AOP proxy, SmartInstantiationAwareBeanPostProcessor creates proxy here
→ Return A's early reference (could be raw object or proxy object)
19. Put A's early reference into second-level cache: earlySingletonObjects.put("beanA", earlyA)
20. Remove from third-level cache: singletonFactories.remove("beanA")
21. Return A's early reference to B ✓
22. B completes dependency injection (injected with A's early reference)
23. B executes pre-processor: applyBeanPostProcessorsBeforeInitialization
24. B executes @PostConstruct initialization method
25. B executes post-processor: applyBeanPostProcessorsAfterInitialization (AOP proxy might be created here)
26. B completes creation, put into first-level cache: singletonObjects.put("beanB", beanB)
27. Clear B's second and third-level caches
28. Remove from creation set: singletonsCurrentlyInCreation.remove("beanB")
29. getBean("beanB") returns complete B → A successfully injects B
30. A completes dependency injection
31. A executes pre-processor
32. A executes @PostConstruct initialization method
33. A executes post-processor (if A already created proxy in getEarlyBeanReference, this will be skipped)
34. A completes creation, put into first-level cache: singletonObjects.put("beanA", beanA)
35. Clear A's second and third-level caches
36. Remove from creation set: singletonsCurrentlyInCreation.remove("beanA")
37. Complete! Both A and B successfully created, circular dependency resolvedKey Design Points:
-
Why do we need three-level cache? Isn't two enough?
If we only had two-level cache (first level stores complete Beans, second level stores early Beans), there would be a fatal problem:
- When Bean needs AOP proxy, what should be injected to other Beans is the proxy object, not the raw object
- But in circular dependency scenarios, Bean creation doesn't know if it will be referenced by other Beans, nor when to create proxy
- If we put raw object directly in second-level cache, after creating proxy later, other Beans still get raw object, causing inconsistency
Three-level cache (ObjectFactory) perfectly solves this problem:
- Third-level cache stores object factory, not the object itself
- Only when actually needed by other Beans, the factory's getObject() method is called
- In getObject(), call getEarlyBeanReference(), where SmartInstantiationAwareBeanPostProcessor decides whether to return raw object or proxy object
- Once early reference is generated, move from third to second-level cache, ensuring multiple accesses get the same object
-
Delayed nature of ObjectFactory:
// Put into third-level cache when creating Bean (lines 615-621) singletonFactories.put(beanName, new ObjectFactory<Object>() { @Override public Object getObject() { // Only called when needed by dependencies return getEarlyBeanReference(beanName, rawBean); } });
- If Bean has no circular dependencies, ObjectFactory is never called, no overhead
- If there are circular dependencies, getObject() is called to generate early reference
-
Role of SmartInstantiationAwareBeanPostProcessor:
This is a special BeanPostProcessor interface specifically for solving circular dependency + AOP scenarios:
protected Object getEarlyBeanReference(String beanName, Object bean) { Object exposedObject = bean; // Iterate through all BeanPostProcessors for (BeanPostProcessor bp : beanPostProcessors) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; // AOP creates proxy here early, avoiding multiple different proxies in circular dependencies exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
- If Bean needs AOP proxy, AspectProcessor (implements SmartInstantiationAwareBeanPostProcessor) will create proxy here in advance
- This ensures both sides of circular dependency get the same proxy object
- Later in postProcessAfterInitialization, if proxy was already created, it won't be created again
-
Role of Creation Set:
- Record which Beans are being created:
singletonsCurrentlyInCreation - Used for detecting circular dependencies: if getBean finds Bean in this set, circular dependency occurred
- Used for detecting unresolvable circular dependencies: if not found in third-level cache either, throw CircularDependencyException
- Record which Beans are being created:
The core of dependency injection is reflection. SpringWind's implementation:
private void doDependencyInjection(Object bean) {
Class<?> clazz = bean.getClass();
// Scan all fields
for (Field field : clazz.getDeclaredFields()) {
if (!field.isAnnotationPresent(Autowired.class)) {
continue;
}
// Find Bean from container by type
Object dependency = getBean(field.getType());
if (dependency == null) {
throw new BeanNotFoundException("Dependency not found: " + field.getType());
}
// Set field value through reflection
field.setAccessible(true);
field.set(bean, dependency);
}
}The process is simple:
- Find all fields with
@Autowiredannotation - Find corresponding Bean from container by field type
- Inject Bean through reflection
The core of AOP is dynamic proxy. SpringWind supports two proxy methods:
1. JDK Dynamic Proxy (Interface-based)
// If target class implements interface, number of interfaces > 0
if (interfaces.length > 0) {
// Use JDK dynamic proxy to create proxy object
return Proxy.newProxyInstance(
// Specify class loader for loading dynamically generated proxy class
classLoader,
// Interface array that proxy class needs to implement
interfaces,
// Create invocation handler, pass target object and aspect list
// AopInvocationHandler handles aspect logic during method calls
new AopInvocationHandler(target, aspects)
);
}2. CGLIB Proxy (Inheritance-based)
// If target class has no interface, create CGLIB enhancer object for generating proxy class
Enhancer enhancer = new Enhancer();
// Set parent class to inherit (target class), CGLIB creates proxy through inheritance
enhancer.setSuperclass(targetClass);
// Set method interceptor, pass target object and aspect list
// AopMethodInterceptor handles aspect logic before and after method calls
enhancer.setCallback(new AopMethodInterceptor(target, aspects));
// Create and return proxy object
// create() method dynamically generates subclass of target class as proxy
return enhancer.create();Proxy Workflow:
---
title: AOP Method Call Flow
---
flowchart LR
A["Client calls method"] --> B["Proxy object intercepts"]
B --> C["Execute @Before advice<br>(Pre-enhancement)"]
C --> D["Call target method<br>(Real business logic)"]
D --> E["Execute @After advice<br>(Post-enhancement)"]
E --> F["Return result"]
%% Style definitions
style A fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style B fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style C fill:#f1f8e9,stroke:#7cb342,stroke-width:2px
style D fill:#fff8e1,stroke:#ffb300,stroke-width:2px
style E fill:#fce4ec,stroke:#c2185b,stroke-width:2px
style F fill:#e8f5e8,stroke:#388e3c,stroke-width:2px
SpringWind's Web MVC implements the classic front controller pattern:
@WebServlet("/*")
public class DispatcherServlet extends HttpServlet {
protected void service(HttpServletRequest req, HttpServletResponse resp) {
// 1. Get request path
String uri = req.getRequestURI();
// 2. Find handler
HandlerMethod handler = getHandler(uri);
// 3. Resolve parameters
Object[] args = resolveArguments(handler, req, resp);
// 4. Call Controller method
Object result = handler.invoke(args);
// 5. Handle response
handleResult(result, resp);
}
}Request Processing Flow:
graph LR
A[HTTP Request] --> B[DispatcherServlet]
B --> C[URL Mapping]
C --> D[Parameter Resolution]
D --> E[Call Controller]
E --> F{Return Type Check}
F -->|Return View Name| G[View Resolution]
F -->|Return Data| H[Message Conversion]
G --> I[View Rendering]
H --> I
I --> J[HTTP Response]
style A fill:#90EE90,stroke:#333,stroke-width:2px
style B fill:#ADD8E6,stroke:#333,stroke-width:2px
style C fill:#FFB6C1,stroke:#333,stroke-width:2px
style D fill:#DDA0DD,stroke:#333,stroke-width:2px
style E fill:#FFD700,stroke:#333,stroke-width:2px
style F fill:#F0E68C,stroke:#333,stroke-width:2px
style G fill:#87CEEB,stroke:#333,stroke-width:2px
style H fill:#98FB98,stroke:#333,stroke-width:2px
style I fill:#FFA07A,stroke:#333,stroke-width:2px
style J fill:#90EE90,stroke:#333,stroke-width:2px
JdbcTemplate just encapsulates basic JDBC operation steps, very simple to implement, not as complex as ORM frameworks:
public <T> T queryForObject(String sql, RowMapper<T> mapper, Object... args) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = getConnection(); // 1. Get connection
stmt = prepareStatement(sql); // 2. Create statement
setParameters(stmt, args); // 3. Set parameters
rs = stmt.executeQuery(); // 4. Execute query
return mapper.mapRow(rs, 0); // 5. Map results (you only need to care about this)
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
closeResources(rs, stmt, conn); // 6. Close resources
}
}You only need to provide SQL and result mapping logic, everything else is handled by the framework.
Annotations are essentially metadata, used to describe code characteristics. Java can read these annotations at runtime through reflection API:
// Scan annotations on class
Class<?> clazz = Class.forName("com.example.UserService");
if (clazz.isAnnotationPresent(Service.class)) {
// This is a Service, register as Bean
}
// Scan annotations on fields
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
// This field needs dependency injection
}
}Advantages of annotations over XML configuration:
- Proximity Principle: Configuration and code are together, more intuitive
- Type Safety: Compile-time checking, avoids spelling errors
- Refactoring Friendly: IDE can automatically refactor
Spring extensively uses reflection to implement framework features:
// 1. Create objects through reflection
Constructor<?> constructor = clazz.getDeclaredConstructor();
Object bean = constructor.newInstance();
// 2. Set field values through reflection
Field field = clazz.getDeclaredField("userDao");
field.setAccessible(true);
field.set(bean, userDaoBean);
// 3. Call methods through reflection
Method method = clazz.getDeclaredMethod("registerUser", User.class);
method.invoke(bean, user);Reflection performance is indeed slower than direct calls, but Spring optimizes through caching:
// Constructor cache
private Map<Class<?>, Constructor<?>> constructorCache = new ConcurrentHashMap<>();
public Object createBean(Class<?> clazz) {
Constructor<?> constructor = constructorCache.computeIfAbsent(clazz, c -> {
return c.getDeclaredConstructor();
});
return constructor.newInstance();
}Spring framework applies numerous design patterns:
| Design Pattern | Application Scenario | Example |
|---|---|---|
| Factory Pattern | Bean creation | ApplicationContext is a Bean factory |
| Singleton Pattern | Bean scope | Default singleton Beans |
| Proxy Pattern | AOP implementation | JDK dynamic proxy, CGLIB proxy |
| Template Method Pattern | Fixed process encapsulation | JdbcTemplate, RestTemplate |
| Strategy Pattern | Different implementation strategies | RowMapper result mapping |
| Observer Pattern | Event mechanism | BeanPostProcessor |
| Front Controller Pattern | Web MVC | DispatcherServlet |
| Registry Pattern | Bean definition management | BeanDefinitionRegistry |
Learning Spring is also learning how to elegantly apply design patterns.
Spring container is thread-safe, and SpringWind must also ensure this. We use ConcurrentHashMap instead of Hashtable or synchronized HashMap:
// ✓ Recommended: ConcurrentHashMap
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// ✗ Not recommended: Hashtable (method-level lock, poor performance)
private Map<String, Object> singletonObjects = new Hashtable<>();
// ✗ Not recommended: synchronized wrapper (coarse-grained lock)
private Map<String, Object> singlet`ConcurrentHashMap` uses segmented locking, with much better concurrent performance than `Hashtable`.SpringWind adopts a layered architecture, divided into four layers from bottom to top:
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ Business code : Controller, Service, Repository │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Framework Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Web MVC │ │ AOP │ │ JDBC Template │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Core Container Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Bean Def │ │ Dependency │ │ Lifecycle │ │
│ │ Management │ │ Injection │ │ Management │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Singleton │ │ Circular │ │ BeanPost │ │
│ │ Pool │ │ Dependency │ │ Processor │ │
│ │ │ │ Resolution │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ Reflection API | Class Loader | Dynamic Proxy │
│ Annotation Processing | Concurrent Utils │
└─────────────────────────────────────────────────────────────┘
Core Class: SpringWindApplicationContext
Main Responsibilities:
- Component scanning and registration
- Bean creation and caching
- Dependency injection
- Lifecycle management
- Circular dependency resolution (three-level cache)
Key Code (SpringWindApplicationContext.java):
public class SpringWindApplicationContext {
// Bean definition map (using ConcurrentHashMap for better concurrent performance)
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
// Three-level cache mechanism (core of circular dependency resolution)
// First-level cache: fully initialized Bean objects
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// Second-level cache: early Bean references
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
// Third-level cache: ObjectFactory factory objects (Key!)
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>();
// Set of Beans currently being created
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>());
// Bean post-processor list
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
// Constructor cache (performance optimization)
private final Map<Class<?>, Constructor<?>> constructorCache = new ConcurrentHashMap<>();
public SpringWindApplicationContext(Class<?> configClass) {
scanComponents(configClass); // 1. Scan components
createSingletonBeans(); // 2. Create singleton Beans
dependencyInjection(); // 3. Dependency injection
invokeInitMethods(); // 4. Execute initialization methods
}
}Core Classes: AspectProcessor, SmartInstantiationAwareBeanPostProcessor, AopInvocationHandler, AopMethodInterceptor
Main Responsibilities:
- Aspect registration and management
- Proxy object creation (JDK dynamic proxy/CGLIB bytecode enhancement)
- Pointcut matching and advice execution
- Early proxy creation (solving circular dependency + AOP combination)
Core Classes: DispatcherServlet, HandlerMapping, PathMatcher
Main Responsibilities:
- Request routing
- Parameter resolution and binding
- Controller invocation
- View resolution
Core Classes: JdbcTemplate, RowMapper
Main Responsibilities:
- Connection management
- SQL execution
- Result set mapping
- Resource cleanup
| Feature | SpringWind | Spring MVC | Description |
|---|---|---|---|
| IoC Container | Basic implementation | Complete implementation | SpringWind implements core container functionality |
| Dependency Injection | Field, method injection | Field, method, constructor injection | SpringWind doesn't support constructor injection yet |
| Circular Dependency Resolution | Three-level cache | Three-level cache + multiple strategies | Both use same core mechanism |
| AOP Support | JDK/CGLIB proxy | AspectJ integration | SpringWind implements basic AOP |
| Web MVC | Basic implementation | Complete MVC stack | SpringWind implements core request handling |
| JDBC Support | JdbcTemplate | JdbcTemplate + JPA/Hibernate | SpringWind implements core JDBC functionality |
| Code Size | ~5000 lines | ~500,000 lines | SpringWind is much more concise |
| Learning Difficulty | Easy | Difficult | SpringWind is easier to understand |
SpringWind is not meant to replace Spring, but to learn Spring and master Spring.
Common interview questions, SpringWind has answers:
-
How does IoC container work? Look at
SpringWindApplicationContextimplementation, 200+ lines of code can be understood. -
How are circular dependencies resolved? Look at three-level cache implementation, code is clear and concise.
-
How is AOP implemented? Look at how
AspectProcessorcreates proxy objects.
SpringWind only has a few core dependencies:
<dependencies>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>Applicable Scenarios:
- Small web applications
- Internal tool systems
- Learning projects
- Demo demonstrations
SpringWind's code organization is clear, with well-defined module divisions:
springwind/
├── pom.xml # Maven configuration
├── src/
│ ├── main/
│ │ └── java/com/github/microwind/springwind/
│ │ ├── SpringWindApplication.java # Framework startup class
│ │ ├── annotation/ # Annotation definitions
│ │ ├── core/ # Core IoC container
│ │ ├── aop/ # AOP implementation
│ │ ├── jdbc/ # JDBC template
│ │ ├── web/ # Web MVC
│ │ ├── exception/ # Exception definitions
│ │ └── util/ # Utility classes
│ └── test/ # Test code
└── examples/ # Example projects
├── user-demo/ # User management example
└── web-demo/ # Web application example
The entire framework core code is about 5000 lines, implementing Spring's core functionality with concise yet complete code.
This is a database-based user management system that showcases SpringWind's IoC, DI, Web MVC, and JDBC functionality.
Typical three-tier architecture: Controller → Service → Dao
user-demo/
├── controller/
│ └── UserController.java # Handle HTTP requests
├── service/
│ └── UserService.java # Business logic
├── dao/
│ └── UserDao.java # Data access (using JdbcTemplate)
├── model/
│ └── User.java # Entity class
├── config/
│ └── DataSourceConfig.java # Data source configuration
└── utils/
├── ApiResponse.java # Unified response wrapper
└── PageResult.java # Pagination result
public class User {
private Long id;
private String name;
private String email;
private String phone;
private Long createdTime;
private Long updatedTime;
// getter/setter omitted
}@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// Query all users
public List<User> findAll() {
return jdbcTemplate.query(
"SELECT * FROM users ORDER BY id DESC",
this::mapRow
);
}
// Query by ID
public User findById(Long id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
this::mapRow,
id
);
}
// Create user
public int insert(User user) {
return jdbcTemplate.update(
"INSERT INTO users (name, email, phone, created_time, updated_time) " +
"VALUES (?, ?, ?, ?, ?)",
user.getName(), user.getEmail(), user.getPhone(),
System.currentTimeMillis(), System.currentTimeMillis()
);
}
// Result set mapping
private User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
user.setPhone(rs.getString("phone"));
user.setCreatedTime(rs.getLong("created_time"));
user.setUpdatedTime(rs.getLong("updated_time"));
return user;
}
}See? Originally 50+ lines of JDBC code, now only needs 2 lines. JdbcTemplate automatically handles connection management, resource cleanup, exception handling.
@Service
public class UserService {
@Autowired
private UserDao userDao;
public List<User> getAllUsers() {
return userDao.findAll();
}
public User getUserById(Long id) {
return userDao.findById(id);
}
public boolean createUser(User user) {
return userDao.insert(user) > 0;
}
}@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// Get user list
@RequestMapping("/list")
@ResponseBody
public ViewResult list(HttpServletRequest request) {
try {
List<User> users = userService.getAllUsers();
return new JsonResult(
ApiResponse.success(users, "获取用户列表成功").toMap()
);
} catch (Exception e) {
return new JsonResult(
ApiResponse.failure("获取用户列表失败: " + e.getMessage()).toMap()
);
}
}
// Create user
@RequestMapping("/create")
@ResponseBody
public ViewResult create(@RequestBody User user) {
try {
boolean success = userService.createUser(user);
if (success) {
return new JsonResult(
ApiResponse.success(user, "创建用户成功").toMap()
);
} else {
return new JsonResult(
ApiResponse.failure("创建用户失败").toMap()
);
}
} catch (Exception e) {
return new JsonResult(
ApiResponse.failure("创建用户失败: " + e.getMessage()).toMap()
);
}
}
}# Get user list
curl "http://localhost:8080/user/list"
# Create user
curl -X POST http://localhost:8080/user/create \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com","phone":"13800138000"}'SpringWind currently implements Spring's core functionality, but there are many areas for improvement.
- IoC container (component scanning, Bean management)
- Dependency injection (field injection, method injection)
- Circular dependency resolution (three-level cache)
- AOP (JDK dynamic proxy, CGLIB proxy)
- Web MVC (request mapping, parameter binding, JSON response)
- JDBC template (query, update, batch operations)
- Pagination support (PageResult)
- Unified response wrapper (ApiResponse)
Current Status: No transaction support, needs manual management
Improvement Plan:
// Declarative transaction
@Service
public class UserService {
@Transactional
public void registerUser(User user) {
userDao.insert(user);
emailService.sendWelcomeEmail(user); // If email fails, user creation also rolls back
}
}
// Implementation思路: Use AOP to intercept @Transactional methods
@Aspect
public class TransactionAspect {
@Around("@annotation(Transactional)")
public Object aroundTransaction(Method method, Object[] args) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // Start transaction
Object result = method.invoke(args); // Execute business method
conn.commit(); // Commit transaction
return result;
} catch (Exception e) {
if (conn != null) {
conn.rollback(); // Rollback transaction
}
throw e;
}
}
}Current Status: Only supports field injection and method injection
Improvement Plan:
// Support constructor injection
@Service
public class UserService {
private final UserDao userDao; // final ensures immutability
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}Current Status: Only supports singleton
Improvement Plan:
@Service
@Scope("prototype") // Create new instance each time
public class PrototypeService {
// ...
}Current Improvements:
- Support pointcut expressions:
@Before("execution(* com.example.service.*.*(..))") // Support AspectJ expressions
public void logBefore(JoinPoint joinPoint) {
// ...
}- Support pointcut combination:
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Pointcut("@annotation(Transactional)")
public void transactionalMethods() {}
@Before("serviceMethods() && transactionalMethods()")
public void beforeServiceTransaction() {
// Satisfy both pointcuts
}Current Status: Mainly relies on annotations
Improvement Plan:
# application.yml
springwind:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
web:
port: 8080
context-path: /apiBy implementing SpringWind framework from scratch (~5000 lines of code), we have thoroughly understood Spring's core principles:
- IoC Container: Manages complete lifecycle through reflection scanning components, registering Bean definitions
- Dependency Injection: Implements automatic assembly through reflection, supports field and method injection
- Three-level Cache (most ingenious design):
- First-level cache: Stores fully initialized Beans
- Second-level cache: Stores early Bean references
- Third-level cache: Stores ObjectFactory factories (Key!)
- Why three levels needed: Delayed generation of early references, supports early AOP proxy creation, ensures singleton consistency
- AOP: Implements separation of cross-cutting concerns through dynamic proxies
- Web MVC: Unified HTTP request processing through front controller pattern
- JDBC Template: Encapsulates boilerplate code through template method pattern
The best way to learn is through imitation. Only by completely hand-crafting a web framework can you thoroughly understand the core mechanisms of frameworks.
SpringWind is not meant to replace Spring, but to deeply understand Spring. It implements Spring's core functionality with the most concise code (~4000 lines), making it understandable for every developer who wants to deeply understand framework principles.
By learning SpringWind, you will master Spring framework core knowledge points:
- Spring IoC container working mechanism
- Ingenious design of three-level cache solving circular dependencies
- AOP dynamic proxy implementation principles
- Complete Bean lifecycle flow
- Practical application of design patterns in frameworks
- Deep reasons why Spring is designed this way
Final Words:
Frameworks are not black magic; they just encapsulate common code patterns. When you understand the principles, you can advance from "using frameworks" to "mastering frameworks" and even "designing frameworks".
Hope SpringWind can help you open the door to Spring. Happy learning!
- Java 17+
- Maven 3.6+
- MySQL 8.0+ (if using User Demo)
# 1. Clone project
git clone https://github.com/microwind/design-patterns.git
cd practice-projects/springwind
# 2. Compile and install
mvn clean install -DskipTests=true
# 3. Run User Demo
cd examples/user-demo
mvn exec:java -Dexec.args="--web"
# 4. Run Web Demo
cd examples/web-demo
mvn exec:java -Dexec.args="--web"- Project Homepage: https://github.com/microwind/design-patterns/tree/main/practice-projects/springwind
- Spring Official Documentation: https://docs.spring.io/spring-framework/docs/current/reference/html/
- Issue Feedback: https://github.com/microwind/design-patterns/issues
- Spring Framework Official Documentation
- "Spring Source Code Deep Analysis" book and related reviews
- "Design Patterns: Elements of Reusable Object-Oriented Software"
- "Effective Java"
Hope you enjoy learning SpringWind!