How it works
The explanation, with examples you can run.
Filters: the outermost layer
@Componentclass RequestIdFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException { String id = Optional.ofNullable(req.getHeader("X-Request-Id")) .orElse(UUID.randomUUID().toString()); MDC.put("requestId", id); res.setHeader("X-Request-Id", id); try { chain.doFilter(req, res); // everything else happens inside this call } finally { MDC.remove("requestId"); } }}Spring Boot registers any Filter bean with the embedded server, mapped to /*. Use FilterRegistrationBean to limit the URL patterns or set the order. OncePerRequestFilter guarantees one run per request even when the request is forwarded or dispatched again for an error page. Spring Security itself is a filter.
Interceptors: inside Spring MVC
@Componentclass TimingInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) { req.setAttribute("start", System.nanoTime()); return true; // false stops the request here } public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) { /* record duration */ }} @Configurationclass WebConfig implements WebMvcConfigurer { private final TimingInterceptor timing; WebConfig(TimingInterceptor timing) { this.timing = timing; } public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(timing).addPathPatterns("/api/**"); }}The handler argument is usually a HandlerMethod, so an interceptor can read annotations on the target controller method. postHandle is of limited use for REST controllers: the JSON has already been written.
AOP proxies: around beans
@Aspect@Componentclass SlowCallLogger { @Around("@annotation(com.example.Timed)") Object time(ProceedingJoinPoint pjp) throws Throwable { long t0 = System.nanoTime(); try { return pjp.proceed(); } finally { log(pjp.getSignature(), System.nanoTime() - t0); } }}When a bean matches a pointcut, a post-processor replaces it with a proxy. Spring Boot creates class-based (CGLIB) proxies by default (spring.aop.proxy-target-class=true): a generated subclass whose methods run the advice and then call your real object. @Transactional, @Cacheable, @Async and method security all work this way.
The catch is that only calls through the proxy are intercepted. Inside the bean, this.someMethod() calls the real object directly, and the advice is silently skipped. Final methods can't be overridden by the subclass, so they can't be advised either.
Choosing
| Need | Use |
|---|---|
| Every request, including errors and static files | Filter |
| Only controller requests, needs the handler | Interceptor |
| Behaviour around service methods, from any caller | Aspect or a Spring annotation |
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.