Master Spring Boot: Unified Login, Exception, and Response Handling with AOP and Interceptors

This tutorial walks through implementing unified user login verification, exception handling, and response formatting in Spring Boot using AOP aspects, HandlerInterceptor, @ControllerAdvice, and ResponseBodyAdvice, providing complete code examples and architectural explanations.

Architect's Guide
Architect's Guide
Architect's Guide
Master Spring Boot: Unified Login, Exception, and Response Handling with AOP and Interceptors

Preface

Next is the Spring Boot unified function handling module, which is the practical part of AOP. The course aims to achieve three goals:

Unified user login permission verification

Unified data format return

Unified exception handling

We will examine them one by one.

1. Unified User Login Permission Verification

1.1 Initial User Login Verification

@RestController
@RequestMapping("/user")
public class UserController {
    /**
     * Method 1
     */
    @RequestMapping("/m1")
    public Object method(HttpServletRequest request) {
        // Get session without creating a new one
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            // Already logged in, business logic
            return true;
        } else {
            // Not logged in
            return false;
        }
    }
    /**
     * Method 2
     */
    @RequestMapping("/m2")
    public Object method2(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            return true;
        } else {
            return false;
        }
    }
    // Other methods...
}

The above code repeats the same login verification in each method, which has several drawbacks:

Each method must write its own login verification logic; even if extracted to a common method it still requires parameters and checks.

Adding more controllers increases the number of calls, raising maintenance cost.

The verification code is unrelated to the business logic of each method.

Therefore a common AOP method for unified login verification is needed.

1.2 Spring AOP User Unified Login Verification

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class UserAspect {
    // Define pointcut for all methods under com.example.demo.controller package
    @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    public void pointcut() {}

    // Before advice
    @Before("pointcut()")
    public void doBefore() {}

    // Around advice
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object obj = null;
        System.out.println("Around method start");
        try {
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("Around method end");
        return obj;
    }
}

Using AOP for login verification faces two problems:

Cannot obtain the HttpSession object inside the aspect.

It is difficult to exclude specific methods (e.g., registration and login) from interception.

1.3 Spring Interceptor

Spring provides HandlerInterceptor to solve the above problems. Implementation steps:

Create a custom interceptor that implements the HandlerInterceptor interface and overrides the preHandle method.

Add the interceptor to the WebMvcConfigurer implementation via the addInterceptors method.

1.3.1 Preparation

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    @RequestMapping("/login")
    public boolean login(HttpServletRequest request, String username, String password) {
        if (StringUtils.hasLength(username) && StringUtils.hasLength(password)) {
            if ("admin".equals(username) && "admin".equals(password)) {
                HttpSession session = request.getSession();
                session.setAttribute("userinfo", "admin");
                return true;
            } else {
                return false;
            }
        }
        return false;
    }

    @RequestMapping("/getinfo")
    public String getInfo() {
        log.debug("Executing getinfo method");
        return "Executing getinfo method";
    }

    @RequestMapping("/reg")
    public String reg() {
        log.debug("Executing reg method");
        return "Executing reg method";
    }
}

1.3.2 Custom Interceptor

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Login check logic
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("userinfo") != null) {
            return true;
        }
        log.error("Current user has no access permission");
        response.setStatus(401);
        return false;
    }
}

1.3.3 Register Interceptor

@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // Intercept all requests
                .excludePathPatterns("/user/login") // Exclude login URL
                .excludePathPatterns("/user/reg"); // Exclude registration URL
    }
}

Alternatively, you can register the interceptor directly with new LoginInterceptor() inside addInterceptors.

1.4 Interceptor Implementation Principle

All controller calls go through DispatcherServlet. The doDispatch method invokes applyPreHandle before the controller method is executed. The source of applyPreHandle shows that it iterates over the list of HandlerInterceptor instances and calls their preHandle methods. This is where the login verification logic runs.

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
        HandlerInterceptor interceptor = (HandlerInterceptor) this.interceptorList.get(i);
        if (!interceptor.preHandle(request, response, this.handler)) {
            this.triggerAfterCompletion(request, response, null);
            return false;
        }
    }
    return true;
}

1.5 Extension: Unified Access Prefix

@Configuration
public class AppConfig implements WebMvcConfigurer {
    // Add "api" prefix to all endpoints
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api", c -> true);
    }
}

2. Unified Exception Handling

Implemented with @ControllerAdvice and @ExceptionHandler. The following example handles generic Exception, ArithmeticException, and NullPointerException, returning a HashMap containing a code and message.

package com.example.demo.config;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;

@ControllerAdvice
public class ErrorAdvice {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public HashMap<String, Object> exceptionAdvice(Exception e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", "-1");
        result.put("msg", e.getMessage());
        return result;
    }

    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody
    public HashMap<String, Object> arithmeticAdvice(ArithmeticException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", "-2");
        result.put("msg", e.getMessage());
        return result;
    }
}

When multiple exception handlers exist, Spring matches the most specific one first.

3. Unified Data Return Format

Benefits include easier front‑end parsing, reduced communication cost, consistent maintenance, and standardized backend contracts.

3.1 Implementation

Using @ControllerAdvice together with ResponseBodyAdvice to wrap every response into a uniform JSON structure.

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                 Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("state", 1);
        result.put("msg", "");
        result.put("data", body);
        return result;
    }
}

Special handling is required for String return types because the wrapper would otherwise be converted to a JSON string.

3.2 Soft Constraint with AjaxResult

A utility class AjaxResult provides static methods success and fail to build the standard response map. Controllers can return AjaxResult.success(data) or AjaxResult.fail(code, msg) to keep the response format consistent.

package com.example.demo.common;

import java.util.HashMap;

public class AjaxResult {
    public static HashMap<String, Object> success(Object data) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "");
        result.put("data", data);
        return result;
    }

    public static HashMap<String, Object> success(String msg, Object data) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", msg);
        result.put("data", data);
        return result;
    }

    public static HashMap<String, Object> fail(int code, String msg) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", code);
        result.put("msg", msg);
        result.put("data", "");
        return result;
    }

    public static HashMap<String, Object> fail(int code, String msg, Object data) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", code);
        result.put("msg", msg);
        result.put("data", data);
        return result;
    }
}

3.3 @ControllerAdvice Source Analysis (Overview)

@ControllerAdvice is itself a Spring component. During container initialization, Spring scans for beans annotated with @ControllerAdvice and registers them so that they are invoked automatically when an exception occurs or when a response is about to be written, enabling the unified handling demonstrated above.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

aopSpring BootInterceptorUnified response
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.