自定义注解+AOP,实现日志拦截存储

这里我们介绍下使用自定义注解,加aop来实现日志的存储

首先自定义注解

import com.etc.mainboot.enums.BusinessType;
import com.etc.mainboot.enums.OperatorType;

import java.lang.annotation.*;

/**
 * 操作日志记录处理
 */
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperateLog {

    /**
     * 请求模块-title
     */
    String title() default "";

    /**
     * 请求具体业务方法
     */
    BusinessType bizType() default BusinessType.OTHER;

    /**
     * 操作人员类型
     */
    OperatorType operateType() default OperatorType.OTHER;

    /**
     * 是否保存请求参数,默认false
     */
    boolean isSaveData() default false;
}

编写AOP切面

package com.etc.mainboot.aop;

import com.etc.mainboot.annotation.OperateLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * aop切面处理
 * 对模块、方法、参数的日志处理
 * @Aspect注解切面,@PointCut定义切点,标记方法
 * @author ChenDang
 * @date 2019/5/28 0028
 */
@Aspect
@Component
public class LogAspect {

    /**
     * 此处定义切点是注解方法,这里使用注解的方式。
     * 也可以使用aop最原始的支撑包名的方式
     */
    @Pointcut("@annotation(com.etc.mainboot.annotation.OperateLog)")
    public void operateLog(){}

    /**
     * 环绕增强,在这里进行日志操作
     */
    @Around("operateLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{

        Object res = null;
        long time = System.currentTimeMillis();
        try {
            //方法继续执行
            res =  joinPoint.proceed();
            time = System.currentTimeMillis() - time;
            return res;
        } finally {
            try {
                //方法执行完成后增加日志
                addOperationLog(joinPoint,res,time);
            }catch (Exception e){
                System.out.println("LogAspect 操作失败:" + e.getMessage());
                e.printStackTrace();
            }
        }
    }


    private void addOperationLog(JoinPoint joinPoint, Object res, long time){
        //joinPoint.getSignature()获取封装了署名的对象,在该对象中可以获取目标方法名,
        //所属类的class等信息
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        String method = signature.getDeclaringTypeName() + "." + signature.getName();
        System.out.println("拦截方法名:"+method);
        //获取注解对象,取到注解里定义的字段
        OperateLog annotation = signature.getMethod().getAnnotation(OperateLog.class);
        if(annotation != null) {

            String title = annotation.title();
            String bizType = annotation.bizType().name();
            String operateType = annotation.operateType().name();
            boolean isSaveData = annotation.isSaveData();
            System.out.println("title:"+title+",bizType:"+bizType+",operateType:"+operateType+",isSaveData:"+isSaveData);
            //获取参数
            HttpServletRequest request = ((ServletRequestAttributes)(RequestContextHolder.getRequestAttributes())).getRequest();
            Map<String, String[]> map =request.getParameterMap();
            if (map == null || map.isEmpty()) {
                return;
            }
            //输出参数
            for(String key : map.keySet()){
                System.out.println("key:"+key+",value="+map.get(key));
            }
        }
        //TODO 这里保存日志
        System.out.println("记录日志:");
    }

    @Before("operateLog()")
    public void doBeforeAdvice(JoinPoint joinPoint){
        System.out.println("进入方法前执行.....");

    }

    /**
     * 处理完请求,返回内容
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "operateLog()")
    public void doAfterReturning(Object ret) {
        System.out.println("方法的返回值 : " + ret);
    }

    /**
     * 后置异常通知
     */
    @AfterThrowing("operateLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }


    /**
     * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
     */
    @After("operateLog()")
    public void after(JoinPoint jp){
        System.out.println("方法最后执行.....");
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private OperateLog getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(OperateLog.class);
        }
        return null;
    }

}

编写Controller,测试

package com.etc.mainboot;

import com.etc.mainboot.annotation.OperateLog;
import com.etc.mainboot.enums.BusinessType;
import com.etc.mainboot.enums.OperatorType;
import com.etc.mainboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/index")
public class IndexController {

    @Autowired
    UserService userService;

    @OperateLog(title = "hello",bizType = BusinessType.QUERY,operateType = OperatorType.CUSTOMER)
    @RequestMapping("/hello")
    public String hello(){
        userService.hello();
        return "hello world";
    }
}

编写Service和实现类

public interface UserService {

    public void hello();
}
@Service
public class UserServiceImpl implements UserService {

    @Async("asyncServiceExecutor")
    @Override
    public void hello(){
        System.out.println("当前运行的线程名称:" + Thread.currentThread().getName());
    }
}

这里我们使用了线程池管理

@Configuration
@EnableAsync
public class ThreadExecutorConfig {

    @Bean
    public Executor asyncServiceExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(10);
        //配置最大线程数
        executor.setMaxPoolSize(10);
        //配置队列大小
        executor.setQueueCapacity(99999);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("async-service-thread-");

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }
}

访问controller

看下控制台输出结果

==========================================

进入方法前执行.....
拦截方法名:com.etc.mainboot.IndexController.hello
当前运行的线程名称:async-service-thread-3
title:hello,bizType:QUERY,operateType:CUSTOMER,isSaveData:false
key:username,value=[Ljava.lang.String;@63cf387f
记录日志:
方法最后执行.....
方法的返回值 : hello world 

 ===================================

总结

使用自定义注解+aop,实现日志的存储。在环绕通知doAround里,我们可以获取访问方法和注解相关信息,以及request请求里的信息,包含参数、参数值、请求方法、客户端信息等等,然后在doAround里,保存信息到数据库,这里我就没有写数据库操作了,大家自己写下。

猜你喜欢

转载自blog.csdn.net/u012373281/article/details/91046755