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 byHandlerInterceptor
Defined 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 > 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<String, SlidingWindowMetric> apiMetrics = new ConcurrentHashMap<>(); // Slow request log queue private final Queue<SlowRequestRecord> slowRequests = new ConcurrentLinkedQueue<>(); // 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 -> 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 (() > MAX_SLOW_REQUESTS) { (); } } @Override public List<ApiPerformanceMetric> getApiPerformanceMetrics() { List<ApiPerformanceMetric> metrics = new ArrayList<>(); for (<String, SlidingWindowMetric> entry : ()) { String[] parts = ().split("\."); String controller = parts[0]; String method = > 1 ? parts[1] : ""; SlidingWindowMetric metric = (); (new ApiPerformanceMetric( controller, method, (), (), (), () )); } return metrics; } @Override public List<SlowRequestRecord> getSlowRequests() { return new ArrayList<>(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 >= currentMin || (currentMin, value)) { break; } } // Update the maximum value while (true) { long currentMax = (); if (value <= currentMax || (currentMax, value)) { break; } } } public long getCount() { return (); } public double getAvg() { long countValue = (); return countValue > 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<String, Object> 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 -> { // Increment counter Long currentCount = ().incr(()); // If it is the first increment, set the expiration time if (currentCount != null && currentCount == 1) { ().expire((), period); } return currentCount; }, true); return count != null && count <= 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 && (",")) { 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 (() && ()) { // 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 (() && (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 (() && 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<Map<String, Object>> handleValidationExceptions( MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ().getAllErrors().forEach(error -> { String fieldName = ((FieldError) error).getField(); String errorMessage = (); (fieldName, errorMessage); }); Map<String, Object> body = new HashMap<>(); ("error", "Parameter verification failed"); ("details", errors); return ().body(body); } /** * Exception that failed to bind request parameters */ @ExceptionHandler() public ResponseEntity<Map<String, Object>> handleMissingParams( MissingServletRequestParameterException ex) { Map<String, Object> body = new HashMap<>(); ("error", "Error parameter"); ("message", "Required parameters are missing: " + ()); return ().body(body); } /** * Handle exceptions with mismatched path parameter types */ @ExceptionHandler() public ResponseEntity<Map<String, Object>> handleTypeMismatch( MethodArgumentTypeMismatchException ex) { Map<String, Object> body = new HashMap<>(); ("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<User> 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(() -> 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<String> 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<Locale> 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<String> 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 && !()) { 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 && !()) { 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<String, Object> getCurrentLocale(HttpServletRequest request) { Locale currentLocale = (Locale) ("currentLocale"); if (currentLocale == null) { currentLocale = (); } Map<String, Object> response = new HashMap<>(); ("locale", ()); ("language", ()); ("country", ()); return response; } @GetMapping("/supported") public List<Map<String, String>> getSupportedLocales() { List<Map<String, String>> locales = new ArrayList<>(); (createLocaleMap(, "English")); (createLocaleMap(Locale.SIMPLIFIED_CHINESE, "Simplified Chinese")); (createLocaleMap(Locale.TRADITIONAL_CHINESE, "Traditional Chinese")); (createLocaleMap(, "Japanese")); (createLocaleMap(, "한국어")); return locales; } private Map<String, String> createLocaleMap(Locale locale, String displayName) { Map<String, String> map = new HashMap<>(); ("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!