springboot使用AOP和自定义注解实现日志

由于有些伙伴对java中的注解还不是很了解,可以参考java的元注解,有了解的小伙伴可以直接调过。

java4大元注解:

在java中使用注解要实现自定义的功能需要使用反射在程序与运行时动态的读取注解,在springboot中提供了注解的工具类AnnotatedElementUtils,在springboot中一般使用该工具类来读取注解,如果直接使用反射去读取注解可能会导致某些springboot中的注解和功能不生效。
下面是我自定义注解的定义,以下简称@Log

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

    @AliasFor("type")
    LogType value() default LogType.COMMON_LOG;

    /**
     * 代表记录log的类型
     */
    @AliasFor("value")
    LogType type() default LogType.COMMON_LOG;

}

在springboot中一般使用AOP来实现自定义注解的功能,AOP是面向切面编程,使用设计模式的动态代理模式实现,springboot中使用AsceptJ实现,这里就不详细介绍了,有需要的可以去查看一下有关AOP和动态代理的知识。


在自定义注解中使用了一个自定义的枚举,如下

@Getter
@AllArgsConstructor
public enum LogType {

    MARK_LOG,
    POSITION_LOG,
    ALL_LOG,
    COMMON_LOG

}

注:上面两个都是lombok的注解,要使用lombok要导入相关的包并安装相应的插件



下面是AOP类的代码

@Log4j2
@Aspect
@Component
public class LogAspect {

	/**
	* 用户的service类
	* 用来获取用户的积分信息
	*/
    @Autowired
    private UsersService usersService;

	/**
	* 日志的service
	* 在数据库记录日志数据(这边后端连接数据库使用了mybatis-plus)
	*/
    @Autowired
    private MarkLogService markLogService;

	/**
	* 定义切点
	*/ 
    @Pointcut("@annotation(com.grain.annotion.Log)")
    public void logPointcut() {}

	/**
	* 围绕切入目标方法
	*/ 
    @Around("logPointcut()")
    public Object saveLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Log注解切入!");

        // 获取方法中给的参数
        Object[] args = joinPoint.getArgs();
        Integer uid = (Integer) args[0];
        Integer num = (Integer) args[1];
        int mark = usersService.getMark(uid);

        // 执行被代理的方法(执行原方法)
        Object result = joinPoint.proceed();

        // 获取被代理的方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        
        if (method != null) {
            Log apiLog = AnnotatedElementUtils.findMergedAnnotation(method, Log.class);
            switch (apiLog.value()) {
                case MARK_LOG: {

					// 向数据库存入数据
                    MarkLog markLog = new MarkLog();
                    markLog.setUid(uid);
                    markLog.setUMark(mark);
                    markLog.setCMark(num);
                    markLog.setMTime(LocalDateTime.now());
                    markLogService.save(markLog);
                    break;
                }
                case POSITION_LOG: {
                	// 实现一些自定义的日志输出
                    System.out.println(1);
                    break;
                }
                case ALL_LOG:{
                	// 实现一些自定义的日志输出
                    System.out.println(2);
                    break;
                }
                case COMMON_LOG: {
                	// 实现一些自定义的日志输出
                    System.out.println(3);
                    break;
                }
            }

        } else {
            throw new RuntimeException("切入方法为空!");
        }

        log.info("Log注解切入结束!");
        return result;
    }

}

首先看到这个类使用了三个注解,依次来说明一下这三个注解的功能

@Log4j2: lombok的注解,可以方便的使用log4j输出日志(在类上使用该注解可以在类中直接使用log输出日志)
@Aspect: springboot的AOP注解,在类上加上该注解标注该类为springboot中的横切类,可以做一些AOP配置。
@Component: 标注一个类为springboot容器中的组件,并且可以自动注入springboot的组件,该类使用这个注解主要是为了使用springboot容器中的组件。


下面来说说一些重要的方法
logPointcut()这个方法使用了@Pointcut注解,定义了@Log注解的切点信息。
saveLog(ProceedingJoinPoint joinPoint)方法中做一些横切的处理,springAOP能获取到有@Log的方法,并生成切点,在saveLog(ProceedingJoinPoint joinPoint)使用了@Around注解,会在有@Log注解的方法前后做一些自定义的操作,借助ProceedingJoinPoint能获取到方法执行时的参数,借助AnnotatedElementUtils获取注解的值,判断注解值进行不同的操作。

下面是注解的使用

@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {

    @Log(LogType.MARK_LOG)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addMark(int uid, int num) {
        Users user = getById(uid);
        user.setMark(user.getMark()+num);
        updateById(user);
    }

}

由于@Log注解的valuetype属性使用了@AliasFor注解说明两者互为别名,这里有个坑,当你是用jdk自带的java.lang.reflect包下的方法去获取注解可能获取到valuetype不同的值,这个时候需要使用springboot的反射工具AnnotatedElementUtils去获取类上的注解。这边使用了spingboot的事务注解@Transactional,开启事务,在出现异常的时候自动回滚,不出现异常自动提交事务。