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("接收请求-{} 
 请求ID:{} 
 请求地址: {} 
 token: {} 
 入参 : {}  
 出参 : {} 
 请求时间:{} 
 响应时间:{} 
 耗时:{}秒",
                    "", 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<String, String> 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("接收请求:{} 
 请求ID:{} 
 请求地址: {} 
 token: {} 
 入参 : {}  
 出参 : {} 
 请求时间:{} 
 响应时间:{} 
 耗时:{}秒",
                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<String, String> 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<Object> 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<Object> exception(HttpServletRequest req, Exception ex) {
        return formatResult(ErrorResultCode.SYSTEM_ERROR.getErrorCode(), "系统异常:" + ex.getMessage(), ex);
    }

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

    @ResponseBody
    @ExceptionHandler(ApiException.class)
    @ResponseStatus(HttpStatus.OK)
    public Result<Object> 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<Object> 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<Object> 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<Object> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException e) {
        log.error("Message: {}", e.getMessage(), e);
        return formatResult(ErrorResultCode.REQUEST_TYPE_ERROR.getErrorCode(), ErrorResultCode.REQUEST_TYPE_ERROR.getError(), e);
    }

    private <T extends Throwable> Result<Object> formatResult(String errorCode, String errorMsg, T ex) {
        Map<String, String> 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("响应异常-{} 
 请求ID:{} 
 请求url: {} 
 入参 : {}  
 出参 : {} 
 请求时间:{}  
 响应时间: {} 
 耗时:{}秒",
                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<ObjectError> 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<Map<String, Object>> threadLocal = new ThreadLocal() {
        protected Map<String, Object> initialValue() {
            return new HashMap(4);
        }
    };

    public static Map<String, Object> getThreadLocal() {
        return threadLocal.get();
    }

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

    public static <T> 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<String, Object> keyValueMap) {
        Map map = threadLocal.get();
        map.putAll(keyValueMap);
    }

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

    public static <T> 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.

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.

aopspringloggingsecurityThreadLocalexceptionhandling
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

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.