🎢 MVC Controller Mapping
Overview
This document provides a technical analysis of Sprout Framework's MVC controller mapping implementation, examining the internal architecture, algorithms, and design patterns used to route HTTP requests to controller methods.
Core Architecture
Component Interaction Flow
HTTP Request → HandlerMapping → RequestMappingRegistry → PathPattern Matching
↓
Controller Discovery ← HandlerMethodScanner ← BeanFactory
↓
Handler Invocation ← HandlerMethodInvoker ← Argument Resolution
Key Components Analysis
1. PathPattern: Advanced Pattern Matching Engine
Implementation Details:
The PathPattern
class implements a sophisticated URL pattern matching system using Java's regex engine with custom parsing logic:
public class PathPattern implements Comparable<PathPattern> {
private final String originalPattern;
private final Pattern regex; // Compiled regex for matching
private final List<String> varNames; // Variable names in order
private final List<Integer> varGroups; // Corresponding regex groups
private final int staticLen; // Length of static content
private final int singleStarCount; // Count of * wildcards
private final int doubleStarCount; // Count of ** wildcards
}
Pattern Compilation Algorithm:
The constructor implements a state machine that processes different token types:
-
Variable Tokens (
{name}
or{name:regex}
):- Uses
VAR_TOKEN
pattern:\\{([^/:}]+)(?::([^}]+))?}
- Extracts variable name and optional custom regex
- Defaults to
[^/]+
for unconstrained variables
- Uses
-
Wildcard Processing:
*
→([^/]+)
(single path segment)**
→(.+?)
(multiple segments, non-greedy)?
→[^/]
(single character)
-
Static Content: Escaped using
Pattern.quote()
Specificity Algorithm:
The compareTo
method implements a multi-criteria sorting algorithm:
public int compareTo(PathPattern other) {
// Priority order (ascending = more specific):
// 1. Fewer ** wildcards
// 2. Fewer * wildcards
// 3. Fewer path variables
// 4. Longer static content
// 5. Lexicographic pattern string
}
This ensures the most specific patterns are matched first, preventing broad patterns from shadowing specific ones.
2. HandlerMethodScanner: Reflection-Based Discovery
Scanning Strategy:
The scanner uses a multi-phase approach:
- Bean Enumeration: Iterates through all beans in the container
- Controller Detection: Checks for
@Controller
annotation - Method Introspection: Examines all public methods for mapping annotations
- Annotation Processing: Handles meta-annotations and inheritance
Annotation Resolution Algorithm:
public RequestMappingInfoExtractor findRequestMappingInfoExtractor(Method method) {
for (Annotation ann : method.getDeclaredAnnotations()) {
// Check direct @RequestMapping or meta-annotated mappings
RequestMapping rm = ann instanceof RequestMapping
? (RequestMapping) ann
: ann.annotationType().getAnnotation(RequestMapping.class);
if (rm == null) continue;
// Extract paths with fallback hierarchy: value() → path() → fallback
String[] paths = extractPaths(ann, rm);
HttpMethod[] methods = rm.method();
return new RequestMappingInfoExtractor(path, methods);
}
return null;
}
Path Combination Logic:
The combinePaths
method handles edge cases in URL construction:
public String combinePaths(String basePath, String methodPath) {
// Handles cases like:
// ("", "/users") → "/users"
// ("/api", "users") → "/api/users"
// ("/api/", "/users") → "/api/users"
// ("/api", "/") → "/api"
}
3. RequestMappingRegistry: Concurrent Mapping Storage
Data Structure Design:
private final Map<PathPattern, Map<HttpMethod, RequestMappingInfo>> mappings
= new ConcurrentHashMap<>();
This nested map structure provides:
- Thread Safety:
ConcurrentHashMap
for concurrent access - Efficient Lookup: O(1) access by HTTP method after pattern matching
- Memory Efficiency:
EnumMap
for HTTP methods reduces memory overhead
Handler Resolution Algorithm:
The getHandlerMethod
implementation uses a three-phase approach:
-
Pattern Matching Phase:
for (PathPattern registeredPattern : mappings.keySet()) {
if (registeredPattern.matches(path)) {
// Collect matching handlers
}
} -
Sorting Phase:
matchingHandlers.sort(Comparator.comparing(RequestMappingInfo::pattern));
-
Selection Phase: Returns the first (most specific) match
Performance Characteristics:
- Time Complexity: O(n * m) where n = registered patterns, m = average regex complexity
- Space Complexity: O(p * h) where p = patterns, h = HTTP methods per pattern
- Optimization: Early termination when no matches found
4. HandlerMethodInvoker: Method Execution Engine
Invocation Pipeline:
public Object invoke(RequestMappingInfo requestMappingInfo, HttpRequest<?> request) {
// 1. Path variable extraction
Map<String, String> pathVariables = pattern.extractPathVariables(request.getPath());
// 2. Argument resolution via composite resolver
Object[] args = resolvers.resolveArguments(handlerMethod, request, pathVariables);
// 3. Reflective method invocation
return handlerMethod.invoke(controller, args);
}
This design separates concerns and allows for extensible argument resolution strategies.
Advanced Technical Features
1. Meta-Annotation Support
The framework supports Spring-style meta-annotations:
@RequestMapping(method = HttpMethod.GET)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetMapping {
String[] value() default {};
String[] path() default {};
}
The scanner detects these through annotation introspection:
RequestMapping rm = ann.annotationType().getAnnotation(RequestMapping.class);
2. Regex Compilation Optimization
Patterns are compiled once during registration and reused for all matching operations:
this.regex = Pattern.compile(re.toString());
This avoids the overhead of repeated pattern compilation during request processing.
3. Variable Group Mapping
The implementation maintains a parallel mapping between variable names and regex capture groups:
private final List<String> varNames; // ["id", "category"]
private final List<Integer> varGroups; // [1, 2]
This enables efficient variable extraction without string parsing.
Performance Analysis
Initialization Performance
Controller Scanning Complexity:
- Time: O(C * M * A) where C = controllers, M = methods per controller, A = annotations per method
- Space: O(P) where P = total registered patterns
- Optimization: Performed once at startup, results cached
Runtime Performance
Request Routing Complexity:
- Best Case: O(1) with unique static patterns
- Average Case: O(log P) with well-distributed patterns
- Worst Case: O(P * R) where P = patterns, R = regex complexity
Memory Usage:
- Pattern storage: ~200-500 bytes per pattern (regex + metadata)
- Registry overhead: ~100 bytes per mapping
- Total: Typically
<1MB
for applications with<1000
endpoints
Optimization Strategies
- Pattern Ordering: Most specific patterns checked first
- Lazy Compilation: Regex compiled only once per pattern
- Efficient Data Structures: EnumMap for HTTP methods, ArrayList for ordered collections
- Short-Circuit Evaluation: Early return on first match
Design Patterns and Principles
1. Strategy Pattern
CompositeArgumentResolver
delegates to specific resolvers- Allows extensible argument resolution without modifying core logic
2. Template Method Pattern
HandlerMethodScanner
defines scanning algorithm- Subclasses can override specific steps
3. Registry Pattern
RequestMappingRegistry
centralizes mapping storage- Provides unified interface for registration and lookup
4. Comparable/Comparator Pattern
PathPattern
implements natural ordering by specificity- Enables automatic sorting without external logic
Error Handling and Edge Cases
1. Ambiguous Mappings
if (cnt != 1) {
System.out.printf("[WARN] %s.%s() - skipped: ambiguous @RequestMapping",
method.getDeclaringClass().getSimpleName(), method.getName());
return null;
}
2. Invalid Pattern Syntax
if (!varMatcher.region(i, pattern.length()).lookingAt()) {
throw new IllegalArgumentException("Invalid variable syntax at index " + i);
}
3. No Handler Found
if (matchingHandlers.isEmpty()) {
return null;
}
The system gracefully handles these scenarios with clear error messages and fallback behavior.
Comparison with Spring MVC
Similarities
- Annotation-based configuration
- Pattern matching with variables and wildcards
- Handler method introspection
- Argument resolution pipeline
Differences
- Simplified Architecture: Fewer abstraction layers
- Regex-based Matching: Direct regex compilation vs Spring's AntPathMatcher
- Reduced Configurability: Focus on common use cases
- Performance Focus: Optimized for speed over flexibility
Extensibility Points
1. Custom Argument Resolvers
Implement ArgumentResolver
interface and register with CompositeArgumentResolver
2. Custom Pattern Matching
Extend PathPattern
to support additional syntax
3. Custom Handler Discovery
Implement alternative HandlerMethodScanner
strategies
4. Custom Mapping Storage
Replace RequestMappingRegistry
with alternative storage mechanisms