前言:自定义注解的魅力
在Java开发中,注解(Annotation)是一种强大的元数据形式,它能够为程序元素(如类、方法、变量等)提供附加信息。自定义注解允许我们根据需要创建特定用途的标签,从而实现特定功能,比如日志记录、权限控制等。通过自定义注解来记录日志,可以让我们的代码逻辑更加专注于业务本身,而将日志记录这样的横切关注点从业务代码中分离出来。
目标:实现一个简单的日志记录注解
我们的目标是创建一个名为@SysLog
的自定义注解,它可以应用于任何方法上,当这个方法被调用时,自动记录下方法的执行信息,如方法名、参数值和执行时间。为了实现上述目标,我采用的方案是:自定义注解@SysLog
与Spring AOP技术结合,根据MyBatis不同操作类型(增删改查)来实现日志记录,将日志信息保存到数据库中。
废话不多说,上代码!
步骤1:定义日志类型枚举
首先,创建一个枚举类LogTypeEnum
来定义日志的操作类型,方便后续日志记录的扩展。
public enum LogTypeEnum {
INSERT("INSERT", "新增"),
UPDATE("UPDATE", "更新"),
DELETE("DELETE", "删除"),
QUERY("QUERY", "查询");
private String code;
private String description;
LogTypeEnum(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
}
步骤2:创建日志数据库表
我们需要在数据库中创建一个日志表来存放日志信息。
CREATE TABLE `sys_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`operate_type` VARCHAR(2) NOT NULL COMMENT '操作类型代码',
`operate_content` varchar(999) NOT NULL COMMENT '操作内容',
`operate_time` datetime NOT NULL COMMENT '操作时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
步骤3:定义实体类SysLog
创建一个SysLog
实体类来映射上述数据库表。
@Data
public class SysLog {
private Long id;
private String operateType; // 操作类型
private String operateContent; // 操作内容
private LocalDateTime operateTime; // 操作时间
}
步骤4:定义Mapper接口
创建SysLogMapper
接口来定义MyBatis操作SQL。
public interface SysLogMapper {
void insertLog(SysLog sysLog);
}
对应的mapper.xml文件(SysLogMapper.xml
)中的SQL插入语句:
<insert id="insertLog">
INSERT INTO sys_log (operate_type, operate_content, operate_time)
VALUES (#{operateType}, #{operateContent}, #{operateTime})
</insert>
步骤5:定义自定义注解@SysLog
定义@SysLog
注解,允许指定操作类型,并提供一个默认值。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SysLog {
// 操作类型,默认为查询
String value() default "QUERY";
}
步骤6:实现日志记录切面
为了在注解标记的方法执行前后自动记录日志,我们将使用AspectJ(面向切面编程)来拦截这些方法调用。首先,确保你的项目中已经引入了AspectJ相关的依赖。
接下来,创建一个切面类,用于实现日志记录逻辑:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 日志记录切面类
*/
@Aspect
@Component
public class SysLogAspect {
@Autowired
private SysLogMapper sysLogMapper;
/**
* 环绕通知,用于拦截带有@SysLog注解的方法
*
* @param joinPoint 切入点对象
* @param sysLog 注解对象,包含操作类型信息
* @return 原方法执行结果
* @throws Throwable 抛出异常
*/
@Around("@annotation(sysLog)")
public Object around(ProceedingJoinPoint joinPoint, SysLog sysLog) throws Throwable {
long startTime = System.currentTimeMillis();
Object result;
try {
result = joinPoint.proceed(); // 执行原方法
} catch (Throwable throwable) { // 捕获方法执行中抛出的异常
handleLog(joinPoint, sysLog, startTime, throwable.getMessage(), true);
throw throwable;
}
handleLog(joinPoint, sysLog, startTime, null, false); // 方法正常执行完毕后的日志处理
return result;
}
/**
* 处理日志记录逻辑
*
* @param joinPoint 切入点对象
* @param sysLog 注解对象,包含操作类型信息
* @param startTime 方法开始执行的时间
* @param errorMsg 错误信息,如果有异常则传入异常信息
* @param isError 是否为错误日志
*/
private void handleLog(ProceedingJoinPoint joinPoint, SysLog sysLog, long startTime, String errorMsg, boolean isError) {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
LocalDateTime operateTime = LocalDateTime.now();
String contentBase = String.format("类名:%s,方法名:%s,操作类型:%s", className, methodName, sysLog.value().getDescription());
String content = isError ? contentBase + ",异常信息:" + errorMsg : contentBase;
SysLog log = new SysLog();
log.setOperateType(sysLog.value());
log.setOperateContent(content);
log.setOperateTime(operateTime);
sysLogMapper.insertLog(log);
}
}
步骤7:在Controller中使用@SysLog
接下来,我们将在一个示例Controller中展示如何使用@SysLog
注解。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/users")
@SysLog(value = LogTypeEnum.QUERY.getCode()) // 使用枚举的getCode()方法获取字符串值
public String getUsers() {
// 查询用户逻辑
return "获取用户列表";
}
@PostMapping("/users")
@SysLog(value = LogTypeEnum.INSERT.getCode())
public String createUser(User user) {
// 创建用户逻辑
return "创建用户成功";
}
// 更多操作...
}
总结
通过以上步骤,我们不仅定义了自己的日志记录注解@SysLog
,还实现了它的自动拦截逻辑,使得日志记录变得简单而高效。这种方法的优势在于它将日志记录逻辑从业务代码中分离,提高了代码的可读性和可维护性。现在,每当方法被调用,相关日志就会自动被记录下来,无需在业务代码中手动插入日志语句,极大地提升了开发效率和代码的整洁度;并且这种方式保持了代码的灵活性,同时也保留了枚举作为操作类型的标准化定义,便于理解和维护。
希望这篇教程对你有所帮助,动手实践一下,感受自定义注解和AOP带来的便利!