java 切面实现记录操作日志
一、自定义注解
自定义注解
本次操作通过自定义注解来实现,声明自定义注解
常用
的注解
自定义注解的成员可以是八种基本类型(byte、short、char、int、long、float、double、boolean)和String、Enum、Class、annotations、数组等数据类型
声明方式:
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface 类名 {
//多个自定义成员
String value() default "";
}
1.@Retention (在自定义的注解上面添加此注解,表示自定义注解可以保留在那个阶段,一共有三个阶段)
源码:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* 注释将被编译器丢弃
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* 注释将由编译器记录在类文件中,但不需要在运行时由 VM(java 虚拟机) 保留。这是默认行为
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*注释将由编译器记录在类文件中,并在运行时由 VM(java 虚拟机) 保留,因此可以反射性地读取它们。<-常用
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
1.@Retention(RetentionPolicy.RUNTIME) 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在.
2.@Retention(RetentionPolicy.CLASS) 注解被保留到编译后的class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期.
3.@Retention(RetentionPolicy.SOURCE) 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃,被编译器忽略.
级别:RUNTIME>CLASS>SOURCE
2.@Target (在自定义注解上面添加该注解,表示自定义注解能运用在哪些位置,该注解的内部成员由枚举实现,一共有10种类型)
源码:
public enum ElementType {
/**
* Class, interface (including annotation type), or enum declaration
* 作用于类上
*/
TYPE,
/**
* Field declaration (includes enum constants)
* 作用于字段
*/
FIELD,
/**
* Method declaration
* 作用于方法上
*/
METHOD,
/**
* Formal parameter declaration
* 作用于方法的参数
*/
PARAMETER,
/**
* Constructor declaration
* 作用于构造函数
* */
CONSTRUCTOR,
/**
* Local variable declaration
* 用于局部变量
*/
LOCAL_VARIABLE,
/**
* Annotation type declaration
* 用于注释类型
*/
ANNOTATION_TYPE,
/**
* Package declaration
* 用于包声明
*/
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
1.ElementType.TYPE 可以应用于类的任何元素(类、接口、注解、enum)。
2.ElementType.FIELD 可以应用于字段或属性(成员变量、对象、属性、枚举的常量)。
3.ElementType.METHOD 可以应用于方法级注释。
4.ElementType.PARAMETER 可以应用于方法的参数。
5.ElementType.CONSTRUCTOR 可以应用于构造函数。
6.ElementType.LOCAL_VARIABLE 可以应用于局部变量。
7.ElementType.ANNOTATION_TYPE 可以应用于注释类型。
8.ElementType.PACKAGE 可以应用于包声明。
9.ElementType.TYPE_PARAMETER 类型参数,表示这个注解可以用在 Type的声明式前。
10.ElementType.TYPE_USE 类型的注解,表示这个注解可以用在所有使用Type的地方(如:泛型,类型转换等)。
二、AOP切面实现日志操作记录
以上纯属注解的解释,开始正题
1.自定义日志注解
package com.lb.annonation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author LuoBo
* @Date 2022/1/26
* @description 测试切面日志(自定义注解)
*/
@Target({ElementType.METHOD}) //应用于方法上面
@Retention(RetentionPolicy.RUNTIME)//表示在运行时注解任可用
public @interface OperationLogAnno {
/**
* 操作位置
*/
String operatePage() default "";
/**
* 操作类型
*/
String operateType() default "";
/**
* 业务域,各自业务自己定义
*/
String bizType() default "";
/**
* 操作类型(枚举类型)
*/
OperationType operationType() default OperationType.OTHER;
}
操作类型(枚举类型)
package com.lb.annonation;
/**
* @Author LuoBo
* @Date 2022/1/26
*/
public enum OperationType {
/**
* 创建
*/
CREATE,
/**
* 批量创建
*/
BATCH_CREATE,
/**
* 更新
*/
UPDATE,
/**
* 批量更新
*/
BATCH_UPDATE,
/**
* 删除
*/
DELETE,
/**
* 批量删除
*/
BATCH_DELETE,
/**
* 导入
*/
IMPORT,
/**
* 导出
*/
EXPORT,
/**
* 其他类型
*/
OTHER,
;
}
2.切面类
切面实现日志记录(此处使用前置通知的方式记录接口调用的情况)
package com.lb.aspect;
import cn.hutool.core.date.DateUtil;
import com.lb.annonation.OperationLogAnno;
import com.lb.mapper.AspectLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Date;
@Slf4j
@Aspect
@Configuration
public class OperationAspect {
@Autowired
private AspectLog aspectLog;
//切点
@Pointcut("@annotation(com.lb.annonation.OperationLogAnno)")
private void cutMethod() {
}
//
@Before("cutMethod()")
public void before(JoinPoint joinPoint) throws Throwable {
// 获取方法名
//String methodName = joinPoint.getSignature().getName();
// 反射获取目标类
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法对应的参数类型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根据类、方法、参数类型(重载)获取到方法的具体信息
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
// 获取方法上声明的注解
OperationLogAnno anno = objMethod.getDeclaredAnnotation(OperationLogAnno.class);
//记录日志时间
String formatDate = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
//调用接口保存日志
aspectLog.addLog(anno.operatePage(),anno.operateType(),anno.bizType(),anno.operationType(), formatDate);
}
}
mapper层AspectLog
/**
* @Author LuoBo
* @Date 2022/1/26
* @description 测试切面日志
*/
@Repository
public interface AspectLog {
@Insert("insert into log (operate,type,methodRoot,realType,operateTime) values (#{operate},#{type},#{methodRoot},#{realType},#{time})")
void addLog(String operate, String type, String methodRoot, OperationType realType,String time);
}
接口测试:
package com.lb.controller;
import cn.hutool.core.date.DateUtil;
import com.lb.annonation.OperationLogAnno;
import com.lb.annonation.OperationType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* @Author LuoBo
* @Date 2022/1/26
* @description
*/
@RestController
@RequestMapping("/aspect")
public class TestAspectController {
@PostMapping("/update")
@OperationLogAnno(operatePage = "测试修改用户数据",operateType = "更新",bizType = "/aspect/update",operationType = OperationType.UPDATE)
public void testUpdate(){
String format = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
System.out.println("测试操作日志,方法调用成功!"+System.currentTimeMillis()+" "+format);
}
}
测试结果:
数据库数据更新成功: