Spring Boot path · A request's journey · Topic 6 of 12

Filters, interceptors and AOP proxies

Not started

Spring gives you three places to put cross-cutting code. A servlet **filter** wraps the whole request before Spring MVC has decided anything. A **HandlerInterceptor** runs inside the DispatcherServlet once a controller method has been chosen. An **AOP proxy** wraps a bean, so its advice runs around method calls no matter whether they came from a web request, a scheduled job or a message listener.

Why it matters: Logging, auth, tenancy and timing all need to wrap requests or method calls; choosing the wrong layer means it runs too late, too often or not at all.

  1. 1Filters wrap everything

    Servlet filters see every request, including static files and errors, before Spring MVC has chosen a handler.

  2. 2Interceptors know the handler

    HandlerInterceptors run inside the DispatcherServlet, only for mapped requests, with the chosen controller method in hand.

  3. 3Proxies wrap beans

    Spring AOP puts a generated proxy in front of a bean, so advice runs around method calls from any caller, web or not.

  4. 4Only calls through the proxy count

    A method calling another method on this skips the proxy and its advice.

1

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

NeedUse
Every request, including errors and static filesFilter
Only controller requests, needs the handlerInterceptor
Behaviour around service methods, from any callerAspect or a Spring annotation
2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.You need to add a response header to every request, including ones that end in a 404 or hit static files. Which do you use?
  2. 2.A @RestController method returns an object. Can an interceptor's postHandle add a header to that response?
  3. 3.An @Around aspect is placed on InvoiceService.total(). Inside the same class, render() calls this.total(). Does the aspect run for that call?

0 of 3 right so far. Get all three to finish this topic.