Skip to main content

🎢 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:

  1. Variable Tokens ({name} or {name:regex}):

    • Uses VAR_TOKEN pattern: \\{([^/:}]+)(?::([^}]+))?}
    • Extracts variable name and optional custom regex
    • Defaults to [^/]+ for unconstrained variables
  2. Wildcard Processing:

    • *([^/]+) (single path segment)
    • **(.+?) (multiple segments, non-greedy)
    • ?[^/] (single character)
  3. 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:

  1. Bean Enumeration: Iterates through all beans in the container
  2. Controller Detection: Checks for @Controller annotation
  3. Method Introspection: Examines all public methods for mapping annotations
  4. 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:

  1. Pattern Matching Phase:

    for (PathPattern registeredPattern : mappings.keySet()) {
    if (registeredPattern.matches(path)) {
    // Collect matching handlers
    }
    }
  2. Sorting Phase:

    matchingHandlers.sort(Comparator.comparing(RequestMappingInfo::pattern));
  3. 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

  1. Pattern Ordering: Most specific patterns checked first
  2. Lazy Compilation: Regex compiled only once per pattern
  3. Efficient Data Structures: EnumMap for HTTP methods, ArrayList for ordered collections
  4. 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