自定义注解+AOP实现日志记录

定义日志表结构

CREATE TABLE `sys_oper_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
  `title` varchar(50) DEFAULT '' COMMENT '模块标题',
  `business_type` varchar(20) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
  `method` varchar(100) DEFAULT '' COMMENT '方法名称',
  `request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
  `operator_type` varchar(20) DEFAULT '0' COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
  `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
  `dept_name` varchar(50) DEFAULT '' COMMENT '部门名称',
  `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
  `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
  `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
  `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
  `status` int DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
  `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
  `oper_time` datetime DEFAULT NULL COMMENT '操作时间',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb3 COMMENT='操作日志记录';

切面类环境搭建

依赖导入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

自定义Log注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {		// 自定义操作日志记录注解

    public String title() ;								// 模块名称
    public OperatorType operatorType() default OperatorType.MANAGE;	// 操作人类别
    public int businessType() ;     // 业务类型(0其它 1新增 2修改 3删除)
    public boolean isSaveRequestData() default true;   // 是否保存请求的参数
    public boolean isSaveResponseData() default true;  // 是否保存响应的参数
    
}

 操作人枚举类定义

public enum OperatorType {		// 操作人类别
    OTHER,		// 其他
    MANAGE,		// 后台用户
    MOBILE		// 手机端用户
}

日志切面类

@Aspect
@Component
@Slf4j
public class LogAspect {            // 环绕通知切面类定义

    @Autowired
    private AsyncOperLogService asyncOperLogService ;

    @Around(value = "@annotation(sysLog)")
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint , Log sysLog) {

        // 构建前置参数
        SysOperLog sysOperLog = new SysOperLog() ;

        LogUtil.beforeHandleLog(sysLog , joinPoint , sysOperLog) ;

        Object proceed = null;
        try {
            proceed = joinPoint.proceed();
            // 执行业务方法
            LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 0 , null) ;
            // 构建响应结果参数
        } catch (Throwable e) {                                 // 代码执行进入到catch中,
            // 业务方法执行产生异常
            e.printStackTrace();                                // 打印异常信息
            LogUtil.afterHandlLog(sysLog , proceed , sysOperLog , 1 , e.getMessage()) ;
            throw new RuntimeException();
        }

        // 保存日志数据
        asyncOperLogService.saveSysOperLog(sysOperLog);

        // 返回执行结果
        return proceed ;
    }
}

 自定义注解实现切面类使用

@Target({ElementType.TYPE})        //作用在类上
@Retention(RetentionPolicy.RUNTIME) //被修饰的注解的保留策略 在运行时依然可以通过反射得到注解中的信息
@Import(value = LogAspect.class)            // 通过Import注解导入日志切面类到Spring容器中
public @interface EnableLogAspect {
    
}

在启动类上添加@EnableLogAspect注解

在要添加日志功能的业务接口方法上添加Log注解,启动服务进行测试

@Log(title = "角色添加",businessType = 0) //添加Log注解,设置属性
@PostMapping(value = "/saveSysRole")
public Result saveSysRole(@RequestBody SysRole SysRole) {
    sysRoleService.saveSysRole(SysRole) ;
    return Result.build(null , ResultCodeEnum.SUCCESS) ;
}

保存日志功能

定义一个与日志表相同的实体类

@Data
public class SysOperLog extends BaseEntity {

	private String title;					// 模块标题
	private String method;					// 方法名称
	private String requestMethod;			// 请求方式
	private String operatorType;			// 操作类别(0其它 1后台用户 2手机端用户)
    private Integer businessType ;			// 业务类型(0其它 1新增 2修改 3删除)
	private String operName;				// 操作人员
	private String operUrl;					// 请求URL
	private String operIp;					// 主机地址
	private String operParam;				// 请求参数
	private String jsonResult;				// 返回参数
	private Integer status;					// 操作状态(0正常 1异常)
	private String errorMsg;				// 错误消息

}

定义日志记录切面工具类 

日志切面类调用工具

public class LogUtil {

    //操作执行之后调用
    public static void afterHandlLog(Log sysLog, Object proceed,
                                     SysOperLog sysOperLog, int status ,
                                     String errorMsg) {
        if(sysLog.isSaveResponseData()) {
            sysOperLog.setJsonResult(JSON.toJSONString(proceed));
        }
        sysOperLog.setStatus(status);
        sysOperLog.setErrorMsg(errorMsg);
    }

    //操作执行之前调用
    public static void beforeHandleLog(Log sysLog,
                                       ProceedingJoinPoint joinPoint,
                                       SysOperLog sysOperLog) {

        // 设置操作模块名称
        sysOperLog.setTitle(sysLog.title());
        sysOperLog.setOperatorType(sysLog.operatorType().name());

        // 获取目标方法信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature() ;
        Method method = methodSignature.getMethod();
        sysOperLog.setMethod(method.getDeclaringClass().getName());

        // 获取请求相关参数
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        sysOperLog.setRequestMethod(request.getMethod());
        sysOperLog.setOperUrl(request.getRequestURI());
        sysOperLog.setOperIp(request.getRemoteAddr());

        // 设置请求参数
        if(sysLog.isSaveRequestData()) {
            String requestMethod = sysOperLog.getRequestMethod();
            if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
                String params = Arrays.toString(joinPoint.getArgs());
                sysOperLog.setOperParam(params);
            }
        }
        sysOperLog.setOperName(AuthContextUtil.get().getUserName());
    }
}

 定义保存日志数据的service接口

//接口类
public interface AsyncOperLogService {			// 保存日志数据
    public abstract void saveSysOperLog(SysOperLog sysOperLog) ;
}


//实现类
@Service
public class AsyncOperLogServiceImpl implements AsyncOperLogService {

    @Autowired
    private SysOperLogMapper sysOperLogMapper;

    @Async      // 异步执行保存日志操作
    @Override
    public void saveSysOperLog(SysOperLog sysOperLog) {
        sysOperLogMapper.insert(sysOperLog);
    }
    
}

持久层接口

@Mapper
public interface SysOperLogMapper {
    public abstract void insert(SysOperLog sysOperLog);
}

 映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.demo.spzx.mapper.SysOperLogMapper">

    <insert id="insert" >
        insert into sys_oper_log (
            id,
            title,
            method,
            request_method,
            operator_type,
            oper_name,
            oper_url,
            oper_ip,
            oper_param,
            json_result,
            status,
            error_msg
        ) values (
                     #{id},
                     #{title},
                     #{method},
                     #{requestMethod},
                     #{operatorType},
                     #{operName},
                     #{operUrl},
                     #{operIp},
                     #{operParam},
                     #{jsonResult},
                     #{status},
                     #{errorMsg}
                 )
    </insert>

</mapper>

在需要添加操作日志的接口方法上添加@Log注解进行测试。

@Log(title = "品牌列表",businessType = 0,operatorType = OperatorType.MANAGE)
//品牌列表(分页)
@GetMapping("/{page}/{limit}")
public Result list(@PathVariable Integer page,
                   @PathVariable Integer limit) {
    PageInfo<Brand> pageInfo = brandService.list(page,limit);
    return Result.build(pageInfo,ResultCodeEnum.SUCCESS);
}

事务失效问题的解决

问题分析

Spring的事务控制是通过aop进行实现的,在框架底层会存在一个事务切面类,当业务方法产生异常以后,事务切面类感知到异常以后事务进行回滚。

当系统中存在多个切面类的时候,Spring框架会按照@Order注解的值对切面进行排序,@Order的值越小优先级越高,@Order的值越大优先级越低。优先级越高的切面类越优先执行,当我们没有给切面类指定排序值的时候,我们自定义的切面类的优先级和aop切面类的优先级相同,那么此时事务切面类的优先级要高于自定义切面类,那么切面类的执行顺序如下所示:

当在自定义切面类中对异常进行了捕获,没有将异常进行抛出,那么此时事务切面类是感知不到异常的存在,因此事务失效。

问题解决

解决方案一:使用@Order注解提高自定义切面类的优先级

解决方案二:在自定义切面类的catch中进行异常的抛出

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中,我们可以使用自定义注解实现AOP(面向切面编程)。AOP是一种编程范型,它允许开发者在程序运行时动态地将代码切入到已有代码的特定位置。 下面是一个简单的示例,演示如何使用自定义注解实现AOP。 首先,我们需要定义一个自定义注解: ``` @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } ``` 这个注解用来标记需要记录日志的方法。它的@Target注解指定了它只能用于方法上,@Retention注解指定了它的生命周期是运行时。 接下来,我们创建一个切面类,用来实现AOP的逻辑: ``` @Aspect @Component public class LoggingAspect { @Before("@annotation(com.example.Loggable)") public void logMethodCall(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("Method " + methodName + " called"); } } ``` 这个类使用Spring AOP框架提供的@Aspect注解来标记它是一个切面类。它的@Before注解指定了它要在被@Loggable注解标记的方法之前执行。JoinPoint参数包含了被拦截的方法的信息,我们可以从中获取方法名等信息。 最后,在需要记录日志的方法上加上@Loggable注解即可: ``` @Component public class MyService { @Loggable public void doSomething() { // do something } } ``` 当doSomething()方法被调用时,LoggingAspect中的logMethodCall()方法会被执行,记录方法调用信息。 这就是使用自定义注解实现AOP的基本步骤。当然,实际应用中会更加复杂,需要更多的切面逻辑和注解参数等。但是这个简单的示例可以帮助你理解如何使用自定义注解实现AOP

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是只菜鸟呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值