SoFunction
Updated on 2025-05-08

6 common SpringBoot interceptor usage scenarios and implementation methods

When building enterprise-level web applications, we often need to execute some common logic at different stages of request processing, such as permission verification, logging, performance monitoring, etc.

Spring MVC's Interceptor mechanism provides an elegant way to implement these cross-cutting concerns without having to repeat the same code in each controller.

This article will introduce 6 common interceptor usage scenarios and their implementation methods in SpringBoot.

Interceptor basics

What is an interceptor

Interceptors are a mechanism provided by the Spring MVC framework to execute specific logic before and after the controller processes a request.

The difference between interceptor and filter

1. Different belongings: Filters belong to the Servlet specification, and interceptors belong to the Spring framework.

2. Intercept range: The filter can intercept all requests, and the interceptor can only intercept Spring MVC requests.

3. Execution order: The request passes through the filter first and then is processed by the interceptor.

Interceptor lifecycle method

Interceptor is implemented byHandlerInterceptorDefined by an interface, the interface contains three core methods:

1. preHandle(): Called before the controller method is executed, return true to continue execution, return false to interrupt request.

2. postHandle(): Called after the controller method is executed and before the view is rendered.

3. afterCompletion(): Called after the entire request is completed, regardless of whether an exception occurs.

Scenario 1: User Authentication Interceptor

Use scenarios

User authentication interceptors are mainly used for:

  • Verify that the user is logged in
  • Check whether the user has permission to access a specific resource
  • Implementing JWT token verification for stateless API

Implement code

@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    
    @Autowired
    private UserService userService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        // Skip the processing of non-controller methods        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        
        // Check whether there is @PermitAll annotation, and if there is, the authentication will be skipped        PermitAll permitAll = ();
        if (permitAll != null) {
            return true;
        }
        
        // Get token from request header        String token = ("Authorization");
        if (token == null || !("Bearer ")) {
            (HttpServletResponse.SC_UNAUTHORIZED);
            ().write("{"error": "Not authorized, please log in first"}");
            return false;
        }
        
        token = (7); // Remove the "Bearer" prefix        
        try {
            // Verify token            if (!(token)) {
                (HttpServletResponse.SC_UNAUTHORIZED);
                ().write("{"error": "Token has expired, please log in again"}");
                return false;
            }
            
            // Get user information from token and set it into request attribute            String username = (token);
            User user = (username);
            
            if (user == null) {
                (HttpServletResponse.SC_UNAUTHORIZED);
                ().write("{"error": "The user does not exist"}");
                return false;
            }
            
            // Check whether the method has @RequireRole annotation            RequireRole requireRole = ();
            if (requireRole != null) {
                // Check if the user has the required role                String[] roles = ();
                boolean hasRole = false;
                for (String role : roles) {
                    if ((role)) {
                        hasRole = true;
                        break;
                    }
                }
                
                if (!hasRole) {
                    (HttpServletResponse.SC_FORBIDDEN);
                    ().write("{"error": "Insufficient permissions"}");
                    return false;
                }
            }
            
            // Put user information into the request attribute            ("currentUser", user);
            
            return true;
        } catch (Exception e) {
            (HttpServletResponse.SC_UNAUTHORIZED);
            ().write("{"error": "Token verification failed"}");
            return false;
        }
    }
}

Configure registration

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private AuthenticationInterceptor authenticationInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        (authenticationInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/auth/login", "/api/auth/register");
    }
}

Custom annotations

@Target()
@Retention()
public @interface PermitAll {
}

@Target()
@Retention()
public @interface RequireRole {
    String[] value();
}

Best Practices

1. Use annotations to mark interfaces that require authentication or specific permissions

2. Extract the business logic from the interceptor into a special service class

3. Design different path prefixes for APIs of different security levels

4. Add detailed logging to facilitate problem troubleshooting

Scenario 2: Logging Interceptor

Use scenarios

Logging interceptors are mainly used for:

  • Log API requests and response content
  • Track user behavior
  • Collect system usage statistics
  • Auxiliary problem investigation

Implement code

@Component
@Slf4j
public class LoggingInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        // Record the request start time        long startTime = ();
        ("startTime", startTime);
        
        // Record request information        String requestURI = ();
        String method = ();
        String remoteAddr = ();
        String userAgent = ("User-Agent");
        
        // Get the current user (if the authentication interceptor has been passed)        Object currentUser = ("currentUser");
        String username = currentUser != null ? ((User) currentUser).getUsername() : "anonymous";
        
        // Record request parameters        Map<String, String[]> paramMap = ();
        StringBuilder params = new StringBuilder();
        
        if (!()) {
            for (<String, String[]> entry : ()) {
                (())
                      .append("=")
                      .append((",", ()))
                      .append("&");
            }
            
            if (() > 0) {
                (() - 1);
            }
        }
        
        // Record the request body (POST/PUT/PATCH request only)        String requestBody = "";
        if ((method) || 
            (method) || 
            (method)) {
            
            // Use wrapping the request object to read the request body multiple times            ContentCachingRequestWrapper wrappedRequest = 
                    new ContentCachingRequestWrapper(request);
            
            // In order to trigger content cache, we need to get the input stream once            if (() > 0) {
                ().read();
                requestBody = new String((), 
                        ());
            }
        }
        
        (
            "REQUEST: {} {} from={} user={} userAgent={} params={} body={}",
            method,
            requestURI,
            remoteAddr,
            username,
            userAgent,
            params,
            requestBody
        );
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) throws Exception {
        
        // Calculate the request processing time        long startTime = (Long) ("startTime");
        long endTime = ();
        long processingTime = endTime - startTime;
        
        // Record the response status and processing time        int status = ();
        String requestURI = ();
        String method = ();
        
        if (ex != null) {
            (
                "RESPONSE: {} {} status={} time={}ms error={}",
                method,
                requestURI,
                status,
                processingTime,
                ()
            );
        } else {
            (
                "RESPONSE: {} {} status={} time={}ms",
                method,
                requestURI,
                status,
                processingTime
            );
        }
    }
}

Configuration and use

@Bean
public FilterRegistrationBean<ContentCachingFilter> contentCachingFilter() {
    FilterRegistrationBean<ContentCachingFilter> registrationBean = new FilterRegistrationBean<>();
    (new ContentCachingFilter());
    ("/api/*");
    return registrationBean;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
    (loggingInterceptor)
            .addPathPatterns("/**");
}

Custom content cache filter

public class ContentCachingFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
        
        try {
            (wrappedRequest, wrappedResponse);
        } finally {
            ();
        }
    }
}

Best Practices

1. Desensitize sensitive information (such as passwords, credit card numbers, etc.)

2. Set reasonable log levels and rotation strategies

3. For large requests/responders, consider recording only some content or summary

4. Use MDC (Mapped Diagnostic Context) to record the request ID to facilitate tracking of the complete request link

Scenario 3: Performance Monitor Interceptor

Use scenarios

Performance monitoring interceptors are mainly used for:

  • Monitor API response time
  • Identify performance bottlenecks
  • Statistical slow query
  • Provide performance indicators for system optimization

Implement code

@Component
@Slf4j
public class PerformanceMonitorInterceptor implements HandlerInterceptor {
    
    // Slow request threshold, unit milliseconds    @Value("${-request-threshold:500}")
    private long slowRequestThreshold;
    
    @Autowired
    private MetricsService metricsService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            String controllerName = ().getSimpleName();
            String methodName = ().getName();
            
            ("controllerName", controllerName);
            ("methodName", methodName);
            ("startTime", ());
        }
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) throws Exception {
        
        Long startTime = (Long) ("startTime");
        
        if (startTime != null) {
            long processingTime = () - startTime;
            
            String controllerName = (String) ("controllerName");
            String methodName = (String) ("methodName");
            String uri = ();
            
            // Record performance data            (controllerName, methodName, uri, processingTime);
            
            // Record slow requests            if (processingTime &gt; slowRequestThreshold) {
                ("Slow API detected: {} {}.{} - {}ms (threshold: {}ms)",
                        uri, controllerName, methodName, processingTime, slowRequestThreshold);
                
                // Record slow requests to a special monitoring system                (controllerName, methodName, uri, processingTime);
            }
        }
    }
}

Implementation of indicator service

@Service
@Slf4j
public class MetricsServiceImpl implements MetricsService {
    
    // Use sliding window to record recent performance data    private final ConcurrentMap&lt;String, SlidingWindowMetric&gt; apiMetrics = new ConcurrentHashMap&lt;&gt;();
    
    // Slow request log queue    private final Queue&lt;SlowRequestRecord&gt; slowRequests = new ConcurrentLinkedQueue&lt;&gt;();
    
    // Keep the last 1,000 slow request records    private static final int MAX_SLOW_REQUESTS = 1000;
    
    @Override
    public void recordApiPerformance(String controller, String method, String uri, long processingTime) {
        String apiKey = controller + "." + method;
        
        (apiKey, k -&gt; new SlidingWindowMetric())
                  .addSample(processingTime);
        
        // You can add metric records for Prometheus or other monitoring systems here    }
    
    @Override
    public void recordSlowRequest(String controller, String method, String uri, long processingTime) {
        SlowRequestRecord record = new SlowRequestRecord(
            controller, method, uri, processingTime, ()
        );
        
        (record);
        
        // If the queue exceeds the maximum capacity, remove the earliest record        while (() &gt; MAX_SLOW_REQUESTS) {
            ();
        }
    }
    
    @Override
    public List&lt;ApiPerformanceMetric&gt; getApiPerformanceMetrics() {
        List&lt;ApiPerformanceMetric&gt; metrics = new ArrayList&lt;&gt;();
        
        for (&lt;String, SlidingWindowMetric&gt; entry : ()) {
            String[] parts = ().split("\.");
            String controller = parts[0];
            String method =  &gt; 1 ? parts[1] : "";
            
            SlidingWindowMetric metric = ();
            
            (new ApiPerformanceMetric(
                controller,
                method,
                (),
                (),
                (),
                ()
            ));
        }
        
        return metrics;
    }
    
    @Override
    public List&lt;SlowRequestRecord&gt; getSlowRequests() {
        return new ArrayList&lt;&gt;(slowRequests);
    }
    
    // Sliding window indicator class    private static class SlidingWindowMetric {
        private final LongAdder count = new LongAdder();
        private final LongAdder sum = new LongAdder();
        private final AtomicLong min = new AtomicLong(Long.MAX_VALUE);
        private final AtomicLong max = new AtomicLong(0);
        
        public void addSample(long value) {
            ();
            (value);
            
            // Update the minimum value            while (true) {
                long currentMin = ();
                if (value &gt;= currentMin || (currentMin, value)) {
                    break;
                }
            }
            
            // Update the maximum value            while (true) {
                long currentMax = ();
                if (value &lt;= currentMax || (currentMax, value)) {
                    break;
                }
            }
        }
        
        public long getCount() {
            return ();
        }
        
        public double getAvg() {
            long countValue = ();
            return countValue &gt; 0 ? (double) () / countValue : 0;
        }
        
        public long getMin() {
            return () == Long.MAX_VALUE ? 0 : ();
        }
        
        public long getMax() {
            return ();
        }
    }
}

Entity class definition

@Data
@AllArgsConstructor
public class ApiPerformanceMetric {
    private String controllerName;
    private String methodName;
    private double avgProcessingTime;
    private long minProcessingTime;
    private long maxProcessingTime;
    private long requestCount;
}

@Data
@AllArgsConstructor
public class SlowRequestRecord {
    private String controllerName;
    private String methodName;
    private String uri;
    private long processingTime;
    private LocalDateTime timestamp;
}

Metric service interface

public interface MetricsService {
    void recordApiPerformance(String controller, String method, String uri, long processingTime);
    
    void recordSlowRequest(String controller, String method, String uri, long processingTime);
    
    List<ApiPerformanceMetric> getApiPerformanceMetrics();
    
    List<SlowRequestRecord> getSlowRequests();
}

Performance Monitoring Controller

@RestController
@RequestMapping("/admin/metrics")
public class MetricsController {
    
    @Autowired
    private MetricsService metricsService;
    
    @GetMapping("/api-performance")
    public List<ApiPerformanceMetric> getApiPerformanceMetrics() {
        return ();
    }
    
    @GetMapping("/slow-requests")
    public List<SlowRequestRecord> getSlowRequests() {
        return ();
    }
}

Best Practices

1. Use sliding window statistics to avoid unlimited memory growth

2. Set different performance thresholds for different APIs

3. Export performance data to professional monitoring systems (such as Prometheus)

4. Set up an alarm mechanism to detect performance problems in a timely manner

5. Only monitor important interfaces in detail to avoid performance overhead caused by over-monitoring

Scenario 4: Interface current limit interceptor

Use scenarios

Interface current limit interceptor is mainly used for:

  • Prevent the interface from being called frequently maliciously
  • Protect system resources to avoid overloading
  • Implement API access control
  • Prevent DoS attacks

Implement code

@Component
@Slf4j
public class RateLimitInterceptor implements HandlerInterceptor {
    
    @Autowired
    private RedisTemplate&lt;String, Object&gt; redisTemplate;
    
    @Value("${:true}")
    private boolean enabled;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        if (!enabled) {
            return true;
        }
        
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        
        // Get current limit annotation        RateLimit rateLimit = ();
        if (rateLimit == null) {
            // No current limit annotation is configured, no current limit is performed            return true;
        }
        
        // Get the current limit type        RateLimitType limitType = ();
        
        // Get the current limit key according to the current limit type        String limitKey = getLimitKey(request, limitType);
        
        // Get current limit configuration        int limit = ();
        int period = ();
        
        // Perform current limit check        boolean allowed = checkRateLimit(limitKey, limit, period);
        
        if (!allowed) {
            // If the current limit is exceeded, return the status code 429            (HttpStatus.TOO_MANY_REQUESTS.value());
            (MediaType.APPLICATION_JSON_VALUE);
            ().write("{"error":"Too many requests","message":"The request frequency exceeds the limit, please try again later"}");
            return false;
        }
        
        return true;
    }
    
    private String getLimitKey(HttpServletRequest request, RateLimitType limitType) {
        String key = "rate_limit:";
        
        switch (limitType) {
            case IP:
                key += "ip:" + getClientIp(request);
                break;
            case USER:
                // Get user ID from authentication information                Object currentUser = ("currentUser");
                String userId = currentUser != null ? 
                        (((User) currentUser).getId()) : "anonymous";
                key += "user:" + userId;
                break;
            case API:
                key += "api:" + ();
                break;
            case IP_API:
                key += "ip_api:" + getClientIp(request) + ":" + ();
                break;
            case USER_API:
                Object user = ("currentUser");
                String id = user != null ? 
                        (((User) user).getId()) : "anonymous";
                key += "user_api:" + id + ":" + ();
                break;
            default:
                key += "global";
        }
        
        return key;
    }
    
    private boolean checkRateLimit(String key, int limit, int period) {
        // Use Redis's atomic operation for current limit check        Long count = (connection -&gt; {
            // Increment counter            Long currentCount = ().incr(());
            
            // If it is the first increment, set the expiration time            if (currentCount != null &amp;&amp; currentCount == 1) {
                ().expire((), period);
            }
            
            return currentCount;
        }, true);
        
        return count != null &amp;&amp; count &lt;= limit;
    }
    
    private String getClientIp(HttpServletRequest request) {
        String ipAddress = ("X-Forwarded-For");
        
        if (ipAddress == null || () || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = ("Proxy-Client-IP");
        }
        
        if (ipAddress == null || () || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = ("WL-Proxy-Client-IP");
        }
        
        if (ipAddress == null || () || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = ();
            if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
                // Get the IP configured by the network card                try {
                    InetAddress inet = ();
                    ipAddress = ();
                } catch (UnknownHostException e) {
                    ("Failed to obtain native IP", e);
                }
            }
        }
        
        // For multiple proxy cases, the first IP is the real IP of the client        if (ipAddress != null &amp;&amp; (",")) {
            ipAddress = (0, (","));
        }
        
        return ipAddress;
    }
}

Current limit annotation

@Target()
@Retention()
public @interface RateLimit {
    
    /**
      * Current limit type
      */
    RateLimitType type() default ;
    
    /**
      * Limited times
      */
    int limit() default 100;
    
    /**
      * Time period (seconds)
      */
    int period() default 60;
}

public enum RateLimitType {
    /**
      * Current limit by IP address
      */
    IP,
    
    /**
      * Limit the current by user
      */
    USER,
    
    /**
      * Current limit by interface
      */
    API,
    
    /**
      * Combining current limit by IP and interface
      */
    IP_API,
    
    /**
      * Combining current limit by user and interface
      */
    USER_API,
    
    /**
      * Global current limitation
      */
    GLOBAL
}

Example of usage

@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    @Autowired
    private ProductService productService;
    
    @GetMapping
    @RateLimit(type = , limit = 100, period = 60)
    public List<Product> getProducts() {
        return ();
    }
    
    @GetMapping("/{id}")
    @RateLimit(type = , limit = 200, period = 60)
    public Product getProduct(@PathVariable Long id) {
        return (id)
                .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }
    
    @PostMapping
    @RequireRole("ADMIN")
    @RateLimit(type = , limit = 10, period = 60)
    public Product createProduct(@RequestBody @Valid ProductRequest productRequest) {
        return (productRequest);
    }
}

Best Practices

1. Set different current limit rules according to interface importance and resource consumption

2. Use distributed current limiting solutions such as Redis+Lua scripts

3. Set different flow restriction strategies for specific user groups

4. Provide reasonable retry suggestions in the current limit response

5. Monitor the current limiting situation and adjust the current limiting threshold in a timely manner

Scenario 5: Request parameter verification interceptor

Use scenarios

Request parameter verification interceptor is mainly used for:

  • Unified processing parameter verification logic
  • Provide friendly error information
  • Prevent security issues caused by illegal parameters
  • Reduce duplicate code in the controller

Implement code

@Component
@Slf4j
public class RequestValidationInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        
        // Check whether the method parameters need to be verified        Parameter[] parameters = ().getParameters();
        
        for (Parameter parameter : parameters) {
            // Check if there is annotation for @RequestBody            if (() &amp;&amp; 
                ()) {
                
                // This parameter needs to be verified, and it will be automatically verified in the controller method                // Here you just need to make sure we can handle the verification failure situation                // Handle MethodArgumentNotValidException through global exception handler                
                // Record verification is about to happen                ("Will be correct {}.{} Request body parameters {} Perform verification", 
                        ().getSimpleName(),
                        ().getName(),
                        ());
            }
            
            // Check if there is annotation for @RequestParam            RequestParam requestParam = ();
            if (requestParam != null) {
                String paramName = ().isEmpty() ? 
                        () : ();
                        
                String paramValue = (paramName);
                
                // Check the required parameters                if (() &amp;&amp; (paramValue == null || ())) {
                    (HttpStatus.BAD_REQUEST.value());
                    (MediaType.APPLICATION_JSON_VALUE);
                    ().write(
                            "{"error":"Error parameter","message":"Required parameters are missing: " + paramName + ""}");
                    return false;
                }
                
                // Check the parameter format (if there are comments)                if (() &amp;&amp; paramValue != null) {
                    Pattern pattern = ();
                    if (!(())) {
                        (HttpStatus.BAD_REQUEST.value());
                        (MediaType.APPLICATION_JSON_VALUE);
                        ().write(
                                "{"error":"Error parameter","message":"Parameters" + 
                                paramName + "Incorrect format: " + () + ""}");
                        return false;
                    }
                }
            }
            
            // Check if there is annotation for @PathVariable            PathVariable pathVariable = ();
            if (pathVariable != null) {
                // Verification for PathVariable mainly depends on the regular matching of RequestMappingHandlerMapping                // Additional verification logic can be added here, such as numerical range checking, etc.            }
        }
        
        return true;
    }
}

Global exception handling

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    /**
      * Exception that failed to verify the request body parameter
      */
    @ExceptionHandler()
    public ResponseEntity&lt;Map&lt;String, Object&gt;&gt; handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        
        Map&lt;String, String&gt; errors = new HashMap&lt;&gt;();
        
        ().getAllErrors().forEach(error -&gt; {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = ();
            (fieldName, errorMessage);
        });
        
        Map&lt;String, Object&gt; body = new HashMap&lt;&gt;();
        ("error", "Parameter verification failed");
        ("details", errors);
        
        return ().body(body);
    }
    
    /**
      * Exception that failed to bind request parameters
      */
    @ExceptionHandler()
    public ResponseEntity&lt;Map&lt;String, Object&gt;&gt; handleMissingParams(
            MissingServletRequestParameterException ex) {
        
        Map&lt;String, Object&gt; body = new HashMap&lt;&gt;();
        ("error", "Error parameter");
        ("message", "Required parameters are missing: " + ());
        
        return ().body(body);
    }
    
    /**
      * Handle exceptions with mismatched path parameter types
      */
    @ExceptionHandler()
    public ResponseEntity&lt;Map&lt;String, Object&gt;&gt; handleTypeMismatch(
            MethodArgumentTypeMismatchException ex) {
        
        Map&lt;String, Object&gt; body = new HashMap&lt;&gt;();
        ("error", "Error parameter type");
        ("message", "Parameters" + () + "It should be " + 
                ().getSimpleName() + " type");
        
        return ().body(body);
    }
}

Example of usage

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public List&lt;User&gt; getUsers(
            @RequestParam(required = false) 
            @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "Only include letters and numbers") 
            String keyword,
            
            @RequestParam(defaultValue = "0") 
            @Min(value = 0, message = "The page number cannot be less than 0") 
            Integer page,
            
            @RequestParam(defaultValue = "10") 
            @Min(value = 1, message = "The number of bars per page cannot be less than 1") 
            @Max(value = 100, message = "The number of bars per page cannot be greater than 100")
            Integer size) {
        
        return (keyword, page, size);
    }
    
    @PostMapping
    public User createUser(@RequestBody @Valid UserCreateRequest request) {
        return (request);
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable @Positive(message = "User ID must be a positive integer") Long id) {
        return (id)
                .orElseThrow(() -&gt; new ResourceNotFoundException("User not found"));
    }
}

Custom verification request class

@Data
public class UserCreateRequest {
    
    @NotBlank(message = "Username cannot be empty")
    @Size(min = 4, max = 20, message = "The username must be between 4-20")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Usernames can only contain letters, numbers and underscores")
    private String username;
    
    @NotBlank(message = "Password cannot be empty")
    @Size(min = 6, max = 20, message = "The password must be between 6-20")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$", 
             message = "The password must contain upper and lowercase letters and numbers")
    private String password;
    
    @NotBlank(message = "The email cannot be empty")
    @Email(message = "The email format is incorrect")
    private String email;
    
    @NotBlank(message = "The mobile phone number cannot be empty")
    @Pattern(regexp = "^1[3-9]\d{9}$", message = "Mobile phone number format is incorrect")
    private String phone;
    
    @NotNull(message = "Age cannot be empty")
    @Min(value = 18, message = "Age must be greater than or equal to 18 years old")
    @Max(value = 120, message = "Age must be less than or equal to 120 years old")
    private Integer age;
    
    @NotEmpty(message = "The character cannot be empty")
    private List&lt;String&gt; roles;
    
    @Valid
    private Address address;
}

@Data
public class Address {
    
    @NotBlank(message = "Province cannot be empty")
    private String province;
    
    @NotBlank(message = "Cities cannot be empty")
    private String city;
    
    @NotBlank(message = "The detailed address cannot be empty")
    private String detail;
    
    @Pattern(regexp = "^\d{6}$", message = "The zip code must be 6 digits")
    private String zipCode;
}

Best Practices

1. Combined with the Spring Validation framework for in-depth verification

2. Create custom annotations for common validation rules

3. Provide clear and specific error information

4. Record verification failures and find potential problems

5. Perform stricter parameter verification for sensitive APIs

Scenario 6: Internationalized processing interceptor

Use scenarios

Internationalized processing interceptors are mainly used for:

  • Determine the language based on the request header or user settings
  • Switch the application's localized resources
  • Multilingual support is provided
  • Enhanced user experience

Implement code

@Component
public class LocaleChangeInterceptor implements HandlerInterceptor {
    
    @Autowired
    private MessageSource messageSource;
    
    private final List&lt;Locale&gt; supportedLocales = (
            ,           // en
            Locale.SIMPLIFIED_CHINESE, // zh_CN
            Locale.TRADITIONAL_CHINESE, // zh_TW
            ,          // ja
                         // ko
    );
    
    // Default language    private final Locale defaultLocale = ;
    
    // Language parameter name    private String paramName = "lang";
    
    // HTTP header for detecting language    private List&lt;String&gt; localeHeaders = (
            "Accept-Language",
            "X-Locale"
    );
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        // Try to get language settings from request parameters        String localeParam = (paramName);
        Locale locale = null;
        
        if (localeParam != null &amp;&amp; !()) {
            locale = parseLocale(localeParam);
        }
        
        // If there is no valid language setting in the request parameter, try to get it from the HTTP header        if (locale == null) {
            for (String header : localeHeaders) {
                String localeHeader = (header);
                if (localeHeader != null &amp;&amp; !()) {
                    locale = parseLocaleFromHeader(localeHeader);
                    if (locale != null) {
                        break;
                    }
                }
            }
        }
        
        // If the language cannot be determined, use the default language        if (locale == null) {
            locale = defaultLocale;
        }
        
        // Set the parsed language into LocaleContextHolder        (locale);
        
        // Put language information into the request attribute for easy use in the view        ("currentLocale", locale);
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) throws Exception {
        // Clear the language settings after the request is completed        ();
    }
    
    /**
      * Parsing language parameters
      */
    private Locale parseLocale(String localeParam) {
        Locale requestedLocale = (('_', '-'));
        
        // Check if the requested language is in the supported language list        for (Locale supportedLocale : supportedLocales) {
            if (().equals(())) {
                // If the language matches, but the country may be different, use the full support language                return supportedLocale;
            }
        }
        
        return null;
    }
    
    /**
      * Parsing language from Accept-Language header
      */
    private Locale parseLocaleFromHeader(String headerValue) {
        // parse the Accept-Language header, format such as: "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"        String[] parts = (",");
        
        for (String part : parts) {
            String[] subParts = (";");
            String localeValue = subParts[0].trim();
            
            Locale locale = (('_', '-'));
            
            // Check if it is a supported language            for (Locale supportedLocale : supportedLocales) {
                if (().equals(())) {
                    return supportedLocale;
                }
            }
        }
        
        return null;
    }
}

International configuration

@Configuration
public class LocaleConfig {
    
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver resolver = new SessionLocaleResolver();
        ();
        return resolver;
    }
    
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = 
                new ReloadableResourceBundleMessageSource();
        ("classpath:i18n/messages");
        ("UTF-8");
        (3600); // The cycle of refreshing cache (seconds)        return messageSource;
    }
}

Internationalization tools

@Component
public class I18nUtil {
    
    @Autowired
    private MessageSource messageSource;
    
    /**
      * Get international news
      *
      * @param code message code
      * @return Localized message
      */
    public String getMessage(String code) {
        return getMessage(code, null);
    }
    
    /**
      * Get international news
      *
      * @param code message code
      * @param args Message parameters
      * @return Localized message
      */
    public String getMessage(String code, Object[] args) {
        Locale locale = ();
        try {
            return (code, args, locale);
        } catch (NoSuchMessageException e) {
            return code;
        }
    }
}

Resource file example

# src/main/resources/i18n/messages_en.properties
greeting=Hello, {0}!
=Login successful
=Login failed: {0}
=Username cannot be empty
=Password is too weak, must be at least 8 characters

# src/main/resources/i18n/messages_zh_CN.properties
greeting=Hello,{0}!
=Login successfully
=Login failed:{0}
=Username cannot be empty
=The password is not strong enough,At least it is required8Characters

Use in the controller

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    @Autowired
    private AuthService authService;
    
    @Autowired
    private I18nUtil i18nUtil;
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        try {
            String token = ((), ());
            
            Map<String, Object> response = new HashMap<>();
            ("token", token);
            ("message", (""));
            
            return (response);
        } catch (AuthenticationException e) {
            Map<String, Object> response = new HashMap<>();
            ("error", ("", new Object[]{()}));
            
            return ().body(response);
        }
    }
    
    @GetMapping("/greeting")
    public Map<String, String> greeting(@RequestParam String name) {
        Map<String, String> response = new HashMap<>();
        ("message", ("greeting", new Object[]{name}));
        return response;
    }
}

Get the current language in the front end

@RestController
@RequestMapping("/api/locale")
public class LocaleController {
    
    @GetMapping("/current")
    public Map&lt;String, Object&gt; getCurrentLocale(HttpServletRequest request) {
        Locale currentLocale = (Locale) ("currentLocale");
        
        if (currentLocale == null) {
            currentLocale = ();
        }
        
        Map&lt;String, Object&gt; response = new HashMap&lt;&gt;();
        ("locale", ());
        ("language", ());
        ("country", ());
        
        return response;
    }
    
    @GetMapping("/supported")
    public List&lt;Map&lt;String, String&gt;&gt; getSupportedLocales() {
        List&lt;Map&lt;String, String&gt;&gt; locales = new ArrayList&lt;&gt;();
        
        (createLocaleMap(, "English"));
        (createLocaleMap(Locale.SIMPLIFIED_CHINESE, "Simplified Chinese"));
        (createLocaleMap(Locale.TRADITIONAL_CHINESE, "Traditional Chinese"));
        (createLocaleMap(, "Japanese"));
        (createLocaleMap(, "한국어"));
        
        return locales;
    }
    
    private Map&lt;String, String&gt; createLocaleMap(Locale locale, String displayName) {
        Map&lt;String, String&gt; map = new HashMap&lt;&gt;();
        ("code", ());
        ("language", ());
        ("displayName", displayName);
        return map;
    }
}

Best Practices

1. Use standard international resource document organization methods

2. Cache message resources to avoid frequent loading of resource files

3. Provide international support for all user-visible strings

4. Allow users to switch languages ​​in the interface

5. Save user language preferences in session

6. Use parameterized messages to avoid string stitching

Interceptor best practices

1. Interceptor registration order

The order in which the interceptor is executed is very important and should usually be followed:

  • Authentication/authorization interceptors are executed first
  • The log interceptor should be as high as possible to record the complete information
  • Performance monitoring interceptor packages the entire request processing process
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Autowired
    private AuthenticationInterceptor authInterceptor;
    
    @Autowired
    private LoggingInterceptor loggingInterceptor;
    
    @Autowired
    private PerformanceMonitorInterceptor performanceInterceptor;
    
    @Autowired
    private RateLimitInterceptor rateLimitInterceptor;
    
    @Autowired
    private RequestValidationInterceptor validationInterceptor;
    
    @Autowired
    private LocaleChangeInterceptor localeInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 1. International interceptor        (localeInterceptor)
                .addPathPatterns("/**");
        
        // 2. Log Interceptor        (loggingInterceptor)
                .addPathPatterns("/**");
        
        // 3. Performance monitoring interceptor        (performanceInterceptor)
                .addPathPatterns("/api/**");
        
        // 4. Current limit interceptor        (rateLimitInterceptor)
                .addPathPatterns("/api/**");
        
        // 5. Parameter verification interceptor        (validationInterceptor)
                .addPathPatterns("/api/**");
        
        // 6. Authentication Interceptor        (authInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/auth/login", "/api/auth/register");
    }
}

2. Avoid heavyweight operations in the interceptor

Extract complex logic into a special service class

Use asynchronous method to handle non-critical path operations such as logging

Cache frequently used data

Avoid database operations in interceptors

3. Exception handling

Exceptions in the interceptor may cause the entire request processing chain to be interrupted

Always use try-catch to catch and handle exceptions correctly

Exceptions in critical interceptors (such as authentication) should return the appropriate HTTP status code and error message.

4. Path mode configuration

Exactly specify the paths to intercept to avoid unnecessary performance overhead

Use excludePathPatterns reasonably to exclude paths that do not need to be intercepted

Static resource paths should usually be excluded

5. Combination of interceptors

Design independent, single-responsibilities interceptors

Complex functions are achieved through combination use

Avoid implementing multiple unrelated functions in one interceptor

Summarize

By using these interceptors reasonably, code reusability can be greatly improved, duplicate code can be reduced, and application architecture can be made clearer and more modular.

In practical applications, these interceptors can be selected or combined according to specific needs, and even more types of interceptors can be extended to meet specific business scenarios.

The above are the detailed contents of 6 common SpringBoot interceptor usage scenarios and implementation methods. For more information about SpringBoot interceptors, please pay attention to my other related articles!