Backend Development 23 min read

Enterprise Java Core Features: Full‑Chain Logging, Unified Exception Handling, Permission Interceptor, and Thread Context Management

This article presents a comprehensive guide to implementing full‑chain log tracing, unified exception handling, permission interception, request latency monitoring, and standardized log formats in enterprise‑level Java projects using Spring AOP, thread‑local context, and Redis caching.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Enterprise Java Core Features: Full‑Chain Logging, Unified Exception Handling, Permission Interceptor, and Thread Context Management

Overview: This article introduces core enterprise‑level Java solutions for full‑chain log tracing, unified exception handling, permission interception, request latency monitoring, and standardized log format.

1. Core modules

1.1 Global log aspect (WebControllerAop)

Implemented using Spring AOP around advice to format request parameters, calculate execution time, record request and response details, and manage thread‑local context.

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import ****.infrastructure.general.constants.Constants;
import ****.infrastructure.general.constants.ReqUrlConstants;
import ****.infrastructure.general.util.LogUtil;
import ****.infrastructure.general.util.ThreadLocalUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author guohong
 */
@Slf4j
@Aspect
@Component
public class WebControllerAop {
    @Pointcut("execution(public * ****.apis.controller.*.*(..)) || execution(public * ****.apis.*.controller.*.*(..))")
    public void webLog() {}

    @Around("webLog()")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        //获取目标方法的参数信息
        Signature signature = joinPoint.getSignature();
        String args;
        if (ReqUrlConstants.FILE_URL_MAP.containsKey(signature.getDeclaringTypeName())) {
            args = "【文件流类型】";
        } else {
            args = formatArgs(joinPoint);
        }
        //AOP代理类的类(class)信息
        signature.getDeclaringType();
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            Object o = joinPoint.proceed();
            //记录日志
            log.info("接收请求-{} \n 请求ID:{} \n 请求地址: {} \n token: {} \n 入参 : {}  \n 出参 : {} \n 请求时间:{} \n 响应时间:{} \n 耗时:{}秒",
                    "", LogUtil.idEnd(), "", args, formatRet(o), DateUtil.date(startTime), DateUtil.now(), (System.currentTimeMillis() - startTime) / 1000d);
            ThreadLocalUtil.remove();
            return o;
        }
        HttpServletRequest req = attributes.getRequest();
        String logId = req.getHeader(Constants.PROCESS_ID);
        if (StringUtils.isBlank(logId)) {
            LogUtil.createId();
        } else {
            ThreadLocalUtil.set(Constants.THREAD_NO, logId);
        }
        String token = req.getHeader(Constants.CAR_TOKEN);
        String eventName = getEventName(req.getRequestURL().toString());
        String reqName = "系统接口:" + eventName + " " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName() + "()";
        String reqUrl = req.getMethod() + " " + req.getRequestURL().toString();
        HashMap
map = Maps.newHashMap();
        map.put(Constants.THREAD_REQ_NAME, reqName);
        map.put(Constants.THREAD_REQ_URL, reqUrl);
        map.put(Constants.THREAD_REQ_PARAM, args);
        map.put(Constants.THREAD_REQ_TIME, startTime + "");
        ThreadLocalUtil.set(Constants.THREAD_REQ_PARAM, map);
        //执行目标方法
        Object o = joinPoint.proceed();
        //记录日志
        log.info("接收请求:{} \n 请求ID:{} \n 请求地址: {} \n token: {} \n 入参 : {}  \n 出参 : {} \n 请求时间:{} \n 响应时间:{} \n 耗时:{}秒",
                eventName, LogUtil.idEnd(), reqUrl, token, args, formatRet(o), DateUtil.date(startTime), DateUtil.now(), (System.currentTimeMillis() - startTime) / 1000d);
        //移除所有线程map,防止线程复用导致的变量错乱及内存溢出
        ThreadLocalUtil.remove();
        return o;
    }

    private String formatArgs(JoinPoint joinPoint) {
        String args = Arrays.toString(joinPoint.getArgs());
        try {
            StringBuilder argsBuild = new StringBuilder();
            Object[] argsArray = joinPoint.getArgs();
            if (argsArray.length >= 1) {
                argsBuild.append(JSON.toJSONString(argsArray[0]));
            }
            if (StringUtils.length(argsBuild.toString()) > 0) {
                return argsBuild.toString();
            }
        } catch (Exception ignored) {}
        return args;
    }

    private Object formatRet(Object ret) {
        try {
            String retJson = JSON.toJSONString(ret);
            if (StringUtils.isNotBlank(retJson)) {
                return retJson;
            }
        } catch (Exception ignored) {}
        return ret;
    }

    private static String getEventName(String requestURL) {
        if (StringUtils.isBlank(requestURL)) {
            return "未知URL";
        }
        for (Map.Entry
entry : ReqUrlConstants.MAP.entrySet()) {
            if (StringUtils.contains(requestURL, entry.getKey())) {
                return entry.getValue();
            }
        }
        return "未配置事件";
    }
}

1.2 Unified exception handling (GlobalExceptionHandler)

Provides specific handlers for file upload errors, parameter validation errors, business exceptions, and a generic fallback, returning a uniform JSON response with error code and message.

import cn.hutool.core.date.DateUtil;
import ****.infrastructure.general.constants.Constants;
import ****.infrastructure.general.util.LogUtil;
import ****.infrastructure.general.util.ThreadLocalUtil;
import ****commons.base.Result;
import ****commons.exception.ApiException;
import ****commons.exception.ErrorResultCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import javax.servlet.http.HttpServletRequest;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

@Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
    @Value("${spring.servlet.multipart.max-file-size}")
    String singleMaxFileSize;
    @Value("${spring.servlet.multipart.max-request-size}")
    String maxRequestSize;

    @ResponseBody
    @ExceptionHandler(MultipartException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result
MultipartExceptionHandler(HttpServletRequest req, MultipartException ex) {
        if (ex.getCause().getCause() instanceof FileSizeLimitExceededException) {
            return formatResult(ErrorResultCode.CLIENT_DATA_EXE.getErrorCode(), "单个文件上传大小不能超过" + singleMaxFileSize, ex);
        } else if (ex.getCause().getCause() instanceof SizeLimitExceededException) {
            return formatResult(ErrorResultCode.CLIENT_DATA_EXE.getErrorCode(), "请求的总上传文件大小不能超过" + maxRequestSize, ex);
        } else {
            return formatResult(ErrorResultCode.CLIENT_DATA_EXE.getErrorCode(), "上传文件失败", ex);
        }
    }

    @ResponseBody
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    public Result
exception(HttpServletRequest req, Exception ex) {
        return formatResult(ErrorResultCode.SYSTEM_ERROR.getErrorCode(), "系统异常:" + ex.getMessage(), ex);
    }

    @ResponseBody
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result
runtimeExceptionHandler(HttpServletRequest req, RuntimeException ex) {
        return formatResult(ErrorResultCode.SYSTEM_ERROR.getErrorCode(), "执行异常:" + ex.getMessage(), ex);
    }

    @ResponseBody
    @ExceptionHandler(ApiException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result
ApiExceptionHandler(ApiException e) {
        log.error("Message: {}", e.getMessage(), e);
        return formatResult(e.getResultCode().getErrorCode(), e.getResultCode().getError(), e);
    }

    @ResponseBody
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result
missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e) {
        log.error("Message: {}", e.getMessage(), e);
        return formatResult(ErrorResultCode.PARAM_REQUIRED.getErrorCode(), String.format(ErrorResultCode.PARAM_REQUIRED.getError(), e.getParameterName()), e);
    }

    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result
methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        log.error("Message: {}", e.getMessage(), e);
        return formatResult(ErrorResultCode.PARAM_REQUIRED.getErrorCode(), String.format(ErrorResultCode.PARAM_REQUIRED.getError(), this.getBindingResultErrors(e.getBindingResult())), e);
    }

    @ResponseBody
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result
httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException e) {
        log.error("Message: {}", e.getMessage(), e);
        return formatResult(ErrorResultCode.REQUEST_TYPE_ERROR.getErrorCode(), ErrorResultCode.REQUEST_TYPE_ERROR.getError(), e);
    }

    private
Result
formatResult(String errorCode, String errorMsg, T ex) {
        Map
reqMap = ThreadLocalUtil.get(Constants.THREAD_REQ_PARAM);
        long startTime = System.currentTimeMillis();
        String reqName = "内部处理";
        String url = "";
        String param = "";
        if (!CollectionUtils.isEmpty(reqMap)) {
            if (StringUtils.isNotBlank(reqMap.get(Constants.THREAD_REQ_TIME))) {
                startTime = Long.parseLong(reqMap.get(Constants.THREAD_REQ_TIME));
            }
            if (StringUtils.isNotBlank(reqMap.get(Constants.THREAD_REQ_NAME))) {
                reqName = reqMap.get(Constants.THREAD_REQ_NAME);
            }
            if (StringUtils.isNotBlank(reqMap.get(Constants.THREAD_REQ_URL))) {
                url = reqMap.get(Constants.THREAD_REQ_URL);
            }
            if (StringUtils.isNotBlank(reqMap.get(Constants.THREAD_REQ_PARAM))) {
                param = reqMap.get(Constants.THREAD_REQ_PARAM);
            }
        }
        StringJoiner err = new StringJoiner("/n/r ");
        StackTraceElement[] stackTrace = ex.getStackTrace();
        if (stackTrace != null) {
            for (int i = 0; i < Math.min(stackTrace.length, 100); i++) {
                err.add(stackTrace[i].toString());
            }
        }
        log.info("响应异常-{} \n 请求ID:{} \n 请求url: {} \n 入参 : {}  \n 出参 : {} \n 请求时间:{}  \n 响应时间: {} \n 耗时:{}秒",
                reqName, LogUtil.idEnd(), url, param, "异常代码:" + errorCode + " 异常信息:" + errorMsg + "-" + ex.getMessage() + "/n/r" + err,
                startTime, DateUtil.now(), (System.currentTimeMillis() - startTime) / 1000);
        ThreadLocalUtil.remove();
        return new Result(errorCode, errorMsg);
    }

    private String getBindingResultErrors(BindingResult bindingResult) {
        StringBuilder sb = new StringBuilder();
        if (bindingResult.hasErrors()) {
            List
list = bindingResult.getAllErrors();
            for (ObjectError error : list) {
                sb.append(error.getDefaultMessage()).append(",");
            }
        }
        return sb.toString();
    }
}

1.3 Permission interceptor (RequestInterceptor)

Validates token existence, retrieves user information from Redis, falls back to a remote permission service when needed, supports token renewal (60 days) and marks external users.

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import ****.apis.controller.BaseController;
import ****.client.model.UserAuthDto;
import ****.client.model.UserInfoDto;
import ****.infrastructure.general.constants.Constants;
import ****.infrastructure.redis.RedisUtil;
import ****.util.JsonUtils;
import ****commons.exception.ApiException;
import ****commons.exception.ErrorResultCode;
import ****skyauth.dto.R;
import ****skyauth.dto.UserAuthBaseDto;
import ****skyauth.service.AuthService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class RequestInterceptor implements HandlerInterceptor {
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    BaseController baseController;
    @Autowired
    AuthService authService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        boolean result = false;
        String token = request.getHeader(Constants.TOKEN);
        if (ObjectUtil.isEmpty(token)) {
            ApiException.newThrow(ErrorResultCode.TOKEN_IS_NULL);
        }
        //从缓存中查找此token有没有存在
        UserInfoDto userInfo = JSONObject.parseObject(String.valueOf(redisUtil.getCache(token)), UserInfoDto.class);
        if (ObjectUtil.isEmpty(userInfo)) {
            R r = authService.userPermission(Constants.SKYAUTH_APPID, token, new UserAuthBaseDto());
            Integer code = (Integer) r.get("code");
            if (****.infrastructure.general.constants.HttpStatus.SUCCESS == code) {
                Object data = r.get("data");
                if (ObjectUtil.isNotEmpty(data)) {
                    UserAuthDto userAuthDto = JSON.parseObject(JSON.toJSONString(data), UserAuthDto.class);
                    userInfo = new UserInfoDto();
                    //添加外部用户权限标识
                    ObjectMapper objectMapper = new ObjectMapper();
                    JsonNode jsonNode = objectMapper.readTree(userAuthDto.getDepts().toString());
                    if (JsonUtils.containsFuncNormalList(jsonNode, Constants.IS_EXT_USER_RIGHTS)) {
                        userInfo.setIsExternal("1");
                    }
                    BeanUtils.copyProperties(userAuthDto, userInfo);
                    //feign调用成功且token依然有效
                    redisUtil.setCacheWithExpiration(token, JSONObject.toJSONString(userInfo), 60, TimeUnit.DAYS);
                    request.setAttribute(Constants.USER_NAME, userInfo.getUserName());
                    request.setAttribute(Constants.USER_ID, userInfo.getUserId());
                    request.setAttribute(Constants.IS_EXTERNAL, userInfo.getIsExternal());
                    result = true;
                } else {
                    //这里是调用feign成功但是token校验失败了 返回401
                    ApiException.newThrow(ErrorResultCode.LOGIN_FAILED, "登陆已超时,请重新登录");
                }
            } else {
                //feign调用失败
                ApiException.newThrow(ErrorResultCode.CLIENT_DATA_EXE, (String) r.get("msg"));
            }
        } else {
            request.setAttribute(Constants.USER_NAME, userInfo.getUserName());
            request.setAttribute(Constants.USER_ID, userInfo.getUserId());
            request.setAttribute(Constants.IS_EXTERNAL, userInfo.getIsExternal());
            result = true;
        }
        return result;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

1.4 Thread context management (ThreadLocalUtil & LogUtil)

ThreadLocalUtil provides thread‑safe storage for request metadata, while LogUtil generates unique trace IDs (format yyyyMMddHHmmss+16‑digit random) and supports multi‑thread ID inheritance.

import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 线程共享类
 */
public class ThreadLocalUtil {
    private static ThreadLocal
> threadLocal = new ThreadLocal() {
        protected Map
initialValue() {
            return new HashMap(4);
        }
    };

    public static Map
getThreadLocal() {
        return threadLocal.get();
    }

    public static
T get(String key) {
        Map map = threadLocal.get();
        return (T) map.get(key);
    }

    public static
T get(String key, T defaultValue) {
        Map map = threadLocal.get();
        return map.get(key) == null ? defaultValue : (T) map.get(key);
    }

    public static void set(String key, Object value) {
        Map map = threadLocal.get();
        map.put(key, value);
    }

    public static void set(Map
keyValueMap) {
        Map map = threadLocal.get();
        map.putAll(keyValueMap);
    }

    public static void remove() {
        threadLocal.remove();
    }

    public static
T remove(String key) {
        Map map = threadLocal.get();
        return (T) map.remove(key);
    }
}

import cn.hutool.core.date.DateUtil;
import ****.infrastructure.general.constants.Constants;
import org.apache.commons.lang3.StringUtils;
import java.util.Date;

/**
 * @Description: 线程日志处理类
 */
public class LogUtil {
    public static String id() {
        String logId = ThreadLocalUtil.get(Constants.THREAD_NO);
        if (StringUtils.isEmpty(logId)) {
            logId = DateUtil.format(new Date(), "yyyyMMddHHmmss") + java.util.UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
        }
        Long seq = ThreadLocalUtil.get(Constants.THREAD_SEQ);
        if (seq == null) {
            seq = 1L;
            ThreadLocalUtil.set(Constants.THREAD_SEQ, seq);
        } else {
            seq = seq + 1;
            ThreadLocalUtil.set(Constants.THREAD_SEQ, seq);
        }
        return logId + "^" + seq + "-";
    }

    public static String idEnd() {
        return StringUtils.removeEnd(id(), "-");
    }

    public static void createId() {
        String logId = ThreadLocalUtil.get(Constants.THREAD_NO);
        if (StringUtils.isEmpty(logId)) {
            logId = DateUtil.format(new Date(), "yyyyMMddHHmmss") + java.util.UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
            ThreadLocalUtil.set(Constants.THREAD_NO, logId);
        }
    }
}

Design highlights include a unique TraceID generation rule (timestamp + 16‑digit random), multi‑thread ID inheritance (parent thread ID + child thread ID), and log elements covering request time, sequence, response time, latency, parameters, results, and truncated exception stacks.

Deployment notes: configure Redis connection details, permission service endpoint, and multipart upload size limits (e.g., spring.servlet.multipart.max-file-size=10MB , spring.servlet.multipart.max-request-size=100MB ).

Conclusion: The presented solution delivers traceability, stability, and security for enterprise Java applications, and can be extended to integrate with APM tools for richer monitoring.

JavaAOPSpringLoggingSecuritythreadlocalExceptionHandling
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.