代理模式--通过SpringAOP切面技术和自定义日志注解,实现在应用中记录请求日志

张开发
2026/4/20 14:59:15 15 分钟阅读

分享文章

代理模式--通过SpringAOP切面技术和自定义日志注解,实现在应用中记录请求日志
引言如标题所述本文记录了通过SpringAOP即自定义注解实现请求的记录功能其中要做如下几个方面的准备1.数据库建表SQL2.POJO实体及对应的 service mapper类3.日志注解准备好上述文件后即可实现AOP切面4.AOP切面最后可进行测试5.测试案例注SpringAOP在某些场景下可能会失效例如续SpringAOP失效场景准备工作建表语句CREATE TABLE T_SYS_LOG ( ID varchar(64) COLLATE pg_catalog.default NOT NULL, URL varchar(500) COLLATE pg_catalog.default, METHOD varchar(32) COLLATE pg_catalog.default, PARAM text COLLATE pg_catalog.default, RESULT text COLLATE pg_catalog.default, DESCRIPTION text COLLATE pg_catalog.default, EXECUTION_TIME varchar(255) COLLATE pg_catalog.default, CREATE_TIME timestamp(6) DEFAULT CURRENT_TIMESTAMP, CREATE_USER_ID varchar(64) COLLATE pg_catalog.default, CREATE_USER_NAME varchar(64) COLLATE pg_catalog.default ) ; COMMENT ON COLUMN T_SYS_LOG.ID IS 主键ID; COMMENT ON COLUMN T_SYS_LOG.URL IS 请求路径; COMMENT ON COLUMN T_SYS_LOG.METHOD IS 请求方法; COMMENT ON COLUMN T_SYS_LOG.PARAM IS 请求入参; COMMENT ON COLUMN T_SYS_LOG.RESULT IS 请求结果; COMMENT ON COLUMN T_SYS_LOG.DESCRIPTION IS 自定义描述; COMMENT ON COLUMN T_SYS_LOG.EXECUTION_TIME IS 执行时长; COMMENT ON COLUMN T_SYS_LOG.CREATE_TIME IS 审计字段创建时间; COMMENT ON COLUMN T_SYS_LOG.CREATE_USER_ID IS 审计字段创建人Id; COMMENT ON COLUMN T_SYS_LOG.CREATE_USER_NAME IS 审计字段创建人; -- ---------------------------- -- Primary Key structure for table T_SYS_LOG -- ---------------------------- ALTER TABLE T_SYS_LOG ADD CONSTRAINT T_SYS_LOG_pkey PRIMARY KEY (ID);POJO实体及对应服务实体类package cn.xxxx.xxxx; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.css.slw.db.annotation.DbField; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.Date; Data TableName(value T_SYS_LOG) Schema(description请求日志表) public class SysLogEntity { TableId(ID) private String id; // 主键ID DbField(URL) private String url; // 请求路径 DbField(METHOD) private String method; // 请求方法 DbField(PARAM) private String param; // 请求入参 DbField(RESULT) private String result; // 请求结果 DbField(DESCRIPTION) private String description; // 自定义描述 DbField(EXECUTION_TIME) private String executionTime; // 执行时长 DbField(CREATE_TIME) private Date createTime; // 创建时间 DbField(CREATE_USER_ID) private String createUserId; // 创建人Id DbField(CREATE_USER_NAME) private String createUserName; // 创建人 }mapperpackage cn.xxxx.xxxxx; import cn.gov.caac.issp.sdr.system.domain.model.entity.SysLogEntity; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; /** * author 陇西行 * version 1.0.0 * ClassName SysLogMapper.java * Description 针对【T_SYS_LOG】请求日志表的数据库操作接口 * createTime 2026年03月30日 20:39:00 */ Mapper public interface SysLogMapper extends BaseMapperSysLogEntity { }servicepackage cn.xxxxx.xxxxx; import cn.gov.caac.issp.sdr.system.domain.model.entity.SysLogEntity; import com.baomidou.mybatisplus.extension.service.IService; /** * author 陇西行 * version 1.0.0 * ClassName SysLogService.java * Description 请求日志服务接口 * createTime 2026年03月30日 20:37:00 */ public interface SysLogService extends IServiceSysLogEntity { }serviceImplpackage cn.xxxxxx.xxxxx import cn.gov.caac.issp.sdr.system.domain.dao.SysLogMapper; import cn.gov.caac.issp.sdr.system.domain.model.entity.SysLogEntity; import cn.gov.caac.issp.sdr.system.service.SysLogService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * author 陇西行 * version 1.0.0 * ClassName SysLogServiceImpl.java * Description 请求日志服务实现类 * createTime 2026年03月30日 20:38:00 */ Service Slf4j public class SysLogServiceImpl extends ServiceImplSysLogMapper, SysLogEntity implements SysLogService { }日志注解package cn.xxxx.xxxxx; import java.lang.annotation.*; /** * author 陇西行 * version 1.0.0 * ClassName SysLog.java * Description 请求日志记录注解 * createTime 2026年03月30日 20:32:00 */ Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface SysLog { String value() default ; String url() default ; }切面实现package cn.xxxx.xxxxx; import cn.gov.caac.issp.sdr.constants.UserContext; import cn.gov.caac.issp.sdr.system.domain.annotations.SysLog; import cn.gov.caac.issp.sdr.system.domain.model.entity.SysLogEntity; import cn.gov.caac.issp.sdr.system.service.SysLogService; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.css.slw.utils.uuid.UuidUtil; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; /** * author 陇西行 * version 1.0.0 * ClassName SysLogAspect.java * Description 请求日志切面 * createTime 2026年03月30日 20:36:00 */ Slf4j Aspect Component public class SysLogAspect { Autowired private SysLogService sysLogService; Pointcut(annotation(cn.gov.caac.issp.sdr.system.domain.annotations.SysLog)) public void logPointCut() { // 该方法不需要实现 } Around(logPointCut()) Transactional public Object around(ProceedingJoinPoint point) throws Throwable { long beginTime System.currentTimeMillis(); // 执行方法 Object result point.proceed(); // 执行时长(毫秒) long time System.currentTimeMillis() - beginTime; // 构建请求日志对象 SysLogEntity sysLogEntity buildSysLog(point); sysLogEntity.setResult(JSONObject.toJSONString(result)); SimpleDateFormat sdf new SimpleDateFormat(HH:mm:ss); Date date new Date(time); String formattedTime sdf.format(date); sysLogEntity.setExecutionTime(formattedTime); // 保存系统日志到数据库 sysLogService.save(sysLogEntity); return result; } Transactional public SysLogEntity buildSysLog(ProceedingJoinPoint joinPoint) { MethodSignature signature (MethodSignature) joinPoint.getSignature(); Method method signature.getMethod(); SysLogEntity sysLog new SysLogEntity(); sysLog.setId(UuidUtil.getUuid()); SysLog logAnnotation method.getAnnotation(SysLog.class); if (logAnnotation ! null) { // 注解上的描述 sysLog.setDescription(logAnnotation.value()); // 手动设置url sysLog.setUrl(logAnnotation.url()); } // 请求方法 sysLog.setMethod(signature.getMethod().getName()); // 请求的参数 Object[] args joinPoint.getArgs(); try { String params JSON.toJSONString(args[0]); sysLog.setParam(params); } catch (Exception e) { log.error(请求日志切面获取参数异常{}, e.getMessage()); } // url未手动设定时获取请求的URI if(StringUtils.isEmpty(sysLog.getUrl())){ HttpServletRequest request ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String requestURI request.getRequestURI(); sysLog.setUrl(requestURI); } // 当前用户 String userName UserContext.getUserId(); String userId UserContext.getUserId(); sysLog.setCreateUserName(userName); sysLog.setCreateTime(new Date()); sysLog.setCreateUserId(userId); return sysLog; } }测试案例package cn.gov.caac.issp.sdr.system.service; import cn.gov.caac.issp.sdr.system.domain.annotations.SysLog; import cn.gov.caac.issp.sdr.util.HttpUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import restclient.util.RestConfig; import java.util.HashMap; import java.util.Map; /** * author 陇西行 * version 1.0.0 * ClassName UserCenterService.java * Description 用户中心服务接口 * createTime 2026年03月31日 10:51:00 */ Service Slf4j public class UserCenterService { private final String userCenterUrl RestConfig.getInstance().getRestUrl(app.config.rest.userCenterUrl); /** * 更新用户角色关联关系 */ SysLog(value 用户角色同步至用户中心, url /bpc/sync/v2/data) public JSONObject updSUserRoleCode(MapString, Object params){ // 接口请求路径 String url userCenterUrl xxxxx; // 构建请求头 MapString, String headers new HashMap(); headers.put(Content-Type, application/json); log.info(用户角色同步至用户中心异常请求参数{}, params); // 发送请求用户角色同步至用户中心 JSONObject response JSON.parseObject(HttpUtil.post(url, headers, params)); if (response.get(code) ! null Integer.parseInt(response.get(code).toString()) ! 200) { log.error(用户角色同步至用户中心异常response{},response); } return response; } }通过Controller调用此updSUserRoleCode方法时就会经由AOP代理执行切面中的请求记录逻辑续章常见SpringAOP失效场景1.非Spring管理的对象SpringAOP是基于spring容器实现的针对非spring容器管理的bean对象自然也就无法实现代理扩展2.同一个Bean内部方法调用如果一个Bean内部的方法直接调用同一个Bean内部的另一个方法AOP将无法拦截这个内部方法调用。因为AOP是基于代理的只有通过代理对象才能触发AOP拦截3.静态方法、final方法、被private修饰的方法此类方法是不可重写的因此AOP无法生成代理对象来拦截这些方法4.异步方法异步方法在运行时会创建新的线程或使用线程池AOP拦截器无法跟踪到这些新线程中的方法调用

更多文章