大家好,我是大华! 今天基于AOP
改了系统里一个日志记录的功能,顺便记录和分享一下。
在我的项目中,有好几个地方需要记录用户操作日志。每个方法里都手动写日志代码,这样写不仅代码重复,而且一旦要改,就得改一堆地方。
这时候 AOP(面向切面编程)
就可以用来解决这个问题了。只要打个注解,日志自动记录。
核心步骤
1、定义注解 标记哪些方法要记日志。
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String module();
String action();
}
2、加注解 在需要的方法上加上@OperationLog
。
java
@OperationLog(module = "订单", action = "更新")
public void update() { ... }
3、写切面 用AOP
拦截带注解的方法,通过反射获取信息,自动记日志。
java
@Around("@annotation(log)")
public Object around(ProceedingJoinPoint joinPoint, OperationLog log) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
// 记录日志
return joinPoint.proceed();
}
下面来看我的实际项目例子
在我的项目中,有这样一段代码:
java
@Transactional(rollbackFor = Exception.class)
@Override
public void updateProductProcurementSummary(ProductProcurementSummarySaveReqVO updateReqVO) {
// 校验存在
ProductProcurementSummaryDO summaryDO = productProcurementSummaryMapper.selectById(updateReqVO.getId());
if (summaryDO == null) {
throw exception(PRODUCT_PROCUREMENT_SUMMARY_NOT_EXISTS);
}
// 更新
ProductProcurementSummaryDO updateObj = BeanUtils.toBean(updateReqVO, ProductProcurementSummaryDO.class);
productProcurementSummaryMapper.updateById(updateObj);
// 记录日志
ProductProcurementLogSaveReqVO logVO = new ProductProcurementLogSaveReqVO();
logVO.setPlatformType(updateReqVO.getPlatformType());
logVO.setPlatformName(updateReqVO.getPlatformName());
logVO.setSku(updateReqVO.getSku());
logVO.setReplenishmentSuggestion(summaryDO.getReplenishmentSuggestion());
logVO.setSkuGroup(updateReqVO.getSkuGroup());
logVO.setOperationManager(updateReqVO.getOperationManager());
logVO.setStatus(updateReqVO.getStatus());
logVO.setModuleType(PrpductProcurementLogEnum.MODULE_TYPE_1.getCode());
logVO.setOperationType(PrpductProcurementLogEnum.OPERATION_TYPE_2.getCode());
logVO.setNewReplenishmentSuggestion(updateReqVO.getReplenishmentSuggestion());
String nickName = getLoginUserNickname();
logVO.setRemark(nickName + "修改了补货数量");
logVO.setSourceId(updateReqVO.getId());
logVO.setPeriods(summaryDO.getPeriods());
productProcurementLogService.createProductProcurementLog(logVO);
}
这段代码中,业务逻辑和日志记录的逻辑直接混合在一起了,会存在下面的问题:
代码冗余
:如果其他方法也需要记录日志,得在每个方法里都写类似的日志代码。维护困难
:如果日志记录逻辑需要修改,需要修改所有相关的方法。
AOP解决方案
1、创建常量类
首先,我们创建一个常量类,用于定义模块类型和操作类型:
java
public class ProductProcurementLogConstant {
/**
* 模块类型 - 备货汇总模块
*/
public static final int MODULE_TYPE_1 = 1;
/**
* 操作类型 - 修改操作
*/
public static final int OPERATION_TYPE_2 = 2;
}
2、创建注解
然后,我们创建一个自定义注解,用于标记需要记录日志的方法:
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
/**
* 模块类型
*/
int moduleType() default ProductProcurementLogConstant.MODULE_TYPE_1;
/**
* 操作类型
*/
int operationType() default ProductProcurementLogConstant.OPERATION_TYPE_2;
/**
* 操作描述
*/
String description() default "";
}
3、实现切面
java
@Aspect
@Component
public class OperationLogAspect {
@Resource
private ProductProcurementLogService productProcurementLogService;
@Resource
private ProductInventoryPlatformMapper productInventoryPlatformMapper;
@AfterReturning("@annotation(cn.iocoder.yudao.module.system.framework.productinventory.core.annotation.OperationLog)")
public void doAfterReturning(JoinPoint joinPoint) {
// 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLog operationLog = method.getAnnotation(OperationLog.class);
// 获取方法参数
Object[] args = joinPoint.getArgs();
if (args.length == 0) {
return;
}
// 创建日志对象
ProductProcurementLogSaveReqVO logVO = new ProductProcurementLogSaveReqVO();
logVO.setModuleType(operationLog.moduleType());
logVO.setOperationType(operationLog.operationType());
// 根据参数类型处理不同的请求
if (args[0] instanceof ProductProcurementSummarySaveReqVO) {
handleProcurementSummary((ProductProcurementSummarySaveReqVO) args[0], logVO, operationLog);
} else if (args[0] instanceof ProductInventoryPlatformSaveReqVO) {
handleInventoryPlatform((ProductInventoryPlatformSaveReqVO) args[0], logVO, operationLog);
} else {
return; // 不支持的参数类型
}
// 设置备注
String description = operationLog.description();
if (description.isEmpty()) {
description = getLoginUserNickname() + "修改了补货数量";
} else {
description = getLoginUserNickname() + description;
}
logVO.setRemark(description);
// 创建日志
productProcurementLogService.createProductProcurementLog(logVO);
}
/**
* 处理备货汇总日志
*/
private void handleProcurementSummary(ProductProcurementSummarySaveReqVO updateReqVO,
ProductProcurementLogSaveReqVO logVO,
OperationLog operationLog) {
// 设置日志属性...
}
/**
* 处理多平台补货管理日志
*/
private void handleInventoryPlatform(ProductInventoryPlatformSaveReqVO updateReqVO,
ProductProcurementLogSaveReqVO logVO,
OperationLog operationLog) {
// 从数据库中查询原始数据
ProductInventoryPlatformDO platformDO = productInventoryPlatformMapper.selectById(updateReqVO.getId());
if (platformDO == null) {
return; // 如果找不到原始数据,则不记录日志
}
// 设置日志属性...
}
}
4、使用注解
最后,我们在需要记录日志的方法上添加@OperationLog
注解:
java
@Override
@OperationLog(moduleType = ProductProcurementLogConstant.MODULE_TYPE_1,
operationType = ProductProcurementLogConstant.OPERATION_TYPE_2,
description = "修改了多平台补货数量")
public void updateProductInventoryPlatform(ProductInventoryPlatformSaveReqVO updateReqVO) {
// 校验存在
validateProductInventoryPlatformExists(updateReqVO.getId());
// 更新
ProductInventoryPlatformDO updateObj = BeanUtils.toBean(updateReqVO, ProductInventoryPlatformDO.class);
productInventoryPlatformMapper.updateById(updateObj);
}
OK完成
使用AOP的优点
- 关注点分离:业务和日志各管各的,代码更清晰。
- 减少重复:加个注解就行,不用到处写日志代码。
- 统一维护:所有日志逻辑集中在切面里,改一次全生效。
- 无侵入:老代码不用动,加个注解就能用。
使用建议
- 注解设计要实用,比如加上模块、操作类型、描述等。
- 注意性能:切面里别做耗时操作,比如复杂查询。
本文首发于微信公众号「刘大华的开发笔记」我是大华,专注分享前后端开发的实战笔记。 关注我,少走弯路,一起进步! 觉得有用的话,点个爱心吧!