Spring AOP的使用完整代码实现 (日志记录)-- 完整代码

此处做个简单的记录(部分知识点+代码实现)

一:AOP的部分知识点

1、什么是AOP
AOP是面向切面编程,简单来说就是将某些重复出现的内容拎出来复用,抽取出来的这部分功能就是一个切面,在方法需要用到这个切面功能的时候,使⽤代理技术对⽅法进⾏增强。可用于日志记录、业务锁等等
2、AOP动态代理的两种方式:
CGLIB动态代理 + JDK动态代理(实现接口)
根据类是否实现接⼝来判断动态代理⽅式:
如果实现接⼝会使⽤ JDK 的动态代理,JDK动态代理通过反射来接收被代理的类,核⼼是 InvocationHandler 接⼝和 Proxy 类。
如果没有实现接⼝会使⽤ CGLib 动态代理,CGLib 是在运⾏时动 态⽣成某个类的⼦类,如果某个类被标记为 fifinal,不能使⽤ CGLib 。

3、AOP的重要术语:(比较书面化,自己玩下代码更容易理解)
Aspect :切⾯。多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。Aspect由PointCut和Advice组成
Joinpoint :连接点,程序执⾏过程中的某⼀⾏为,即业务层中的所有⽅法。
Advice :通知,包括前置通知、后置通知、返回后通知、异常 通知和环绕通知。
Pointcut :切⼊点,指被拦截的连接点,切⼊点⼀定是连接点,但连接点不⼀定是切⼊点。
Proxy :代理,Spring AOP 中有 JDK 动态代理和 CGLib 代理,⽬标对象实现了接⼝时采⽤ JDK 动态代 理,反之采⽤ CGLib 代理。
Target :代理的⽬标对象,指⼀个或多个切⾯所通知的对象。
Weaving :织⼊,指把增强应⽤到⽬标对象来创建代理对象的过程。

二、AOP的使用

可以在网盘取代码:
链接:https://pan.baidu.com/s/1NoioAoSdU8agTUve7QYUmQ
提取码:1234

1、Spring AOP与自定义注解Annotation的使用

简单实现其实就三个类:
a、声明一个切面类
b、声明一个注解。@interface
c、然后就可以使用了,声明一个controller 进行测试就行了

(1)声明一个切面类: AopLogAspect

package cn.hsa.ips.admin.annotation;


import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Description:AOP切面
 * @date: 2022/2/8 14:40
 */
@Aspect
@Component
@Slf4j
public class AopLogAspect {

    //切点表达式,表示加了ApiLog注解的都是切点,路径是自定义注解的全路径
    @Pointcut("@annotation(ApiLog)")
    public void pointcut() {
    }

    /*
     * 前置通知
     * @date 2022/2/8 15:09
     */
    @Before("pointcut()")
    public void checkBefore() {
        log.info("我是前置通知===========>");
//        System.out.println("我是前置通知===========>");
    }

    @Around("@annotation(apiLog)")
    public Object logAround(ProceedingJoinPoint joinPoint, ApiLog apiLog) {
        //获取执行方法的类的名称(包名加类名)
        String className = joinPoint.getTarget().getClass().getName();
        //获取实例
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取方法
        Method method = signature.getMethod();
        //获取方法名
        String methodName = method.getName();
        //获取入参
        Object[] args = joinPoint.getArgs();
        //转换json
        String argsJsonStr = JSONUtil.toJsonStr(args);
        //获取当前时间
        long start = System.currentTimeMillis();
        //2、执行方法获取返回值
        try {
            Object proceed = joinPoint.proceed();
            //计算耗时
            long costTime = System.currentTimeMillis() - start;
             //请求参数打印
            if (apiLog.params()){
                log.info("{}方法请求参数  ===>  {}", methodName, argsJsonStr); 
            }
            if (apiLog.cost()) {
                //打印耗时
                log.info("{}方法执行耗时 ===> {}ms", methodName, costTime);
            }
            //转换json
            String proceedJsonStr = JSONUtil.toJsonStr(proceed);
            //打印回参
            if (apiLog.result()) {
                log.info("{}方法返回参数  ===>  {}", methodName, proceedJsonStr);
            }
            //此处可以保存方法日志信息:方法名称,入参,回参,耗时等
            log.info("{}方法保存日志信息  ===>  {}", methodName,"保存成功");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return true;
    }

    /*
     * 后置通知
     * @date 2022/2/8 15:09
    */
    @After("pointcut()")
    public void checkAfter() {
        log.info("我是后置通知===========>");
    }
}

(2)声明一个自定义注解:
注解里面的属性可以根据需要自定义:下面的值在切面中并没有完全用到

package cn.hsa.ips.admin.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;

/**
 * 接口出入参自动日志
 * @date: 2022/2/8 14:39
 */

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

    /**
     * 业务名
     *
     * @return 业务名
     */
    String business() default "";

    /**
     * 模块名
     *
     * @return 模块名
     */
    String module() default "";

    /**
     * 是否打印入参
     *
     * @return 是否打印入参
     */
    boolean params() default true;

    /**
     * 是否打印出参
     *
     * @return 是否打印出参
     */
    boolean result() default true;

    /**
     * 是否输出耗时
     *
     * @return 是否输出耗时
     */
    boolean cost() default false;

    /**
     * 是否输出异常信息
     *
     * @return 是否输出异常信息
     */
    boolean exception() default true;

    /**
     * 慢日志阈值
     * <p>
     * 当值小于 0 时,不进行慢日志统计。
     * 当值大于等于0时,当前值只要大于等于这个值,就进行统计。
     *
     * @return 阈值
     */
    long slow() default -1;
}

(3)声明一个controller ,使用自定义注解。

package cn.hsa.ips.admin.annotation;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description: OperationLogController
 * @date: 2022/2/8 14:48
 */

@RestController
@RequestMapping(value = "/test")
public class OperationLogController {

    /*
     *  @ApiLog为自定义注解,后面为注解类的参数,不赋值会取默认值
     * @date 2022/2/8 15:05
    */
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @ApiLog(business = "测试业务", module = "某个模块", cost = true)
    public Object recordLogOfAop(@RequestBody String user, String content) {
        return "AOP+Annotation自定义注解测试回参";
    }
}

(4)执行结果:
Spring AOP的使用完整代码实现 (日志记录)-- 完整代码_第1张图片

2、AOP的第二个使用实例(用测试类)

a、定义一个业务逻辑类:定义里面的方法逻辑等。
b、定义一个切面类:定义这个切面做什么功能。Pointcut(切点)+Advice (通知:前置通知,后置通知等等)
c、给目标方法标志何时何地运行。
d、Bean注入,切面类和业务逻辑类(目标方法所在类)都加入到容器中
e、测试

(1)定义一个业务逻辑类:定义里面的方法逻辑等。

package cn.hsa.AOP.test;

/*
 * 逻辑类
 * @date 2022/2/8 20:43
*/
public class MathCalculator {

 	public int testMethod(int i, int j) {
        System.out.println("testMethod入参 >> div");
        return i + j;
    }
}

(2)定义一个切面类
定义这个切面做什么功能。Pointcut(切点)+Advice (通知:前置通知,后置通知等等)

package cn.hsa.AOP.test;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class LogAspects {

	//抽取公共的切入点表达式
	@Pointcut("execution(public int MathCalculator.*(..))")
	private void pointCut(){};
	
	//JoinPoint一定要出现在参数列表的第一位
	@Before(value = "cn.hsa.AOP.test.LogAspects.pointCut()")
	public void logStart(JoinPoint joinpoint) {
		System.out.println("我是前置通知>>>>"+joinpoint.getSignature().getName()+">>>>"+Arrays.toString(joinpoint.getArgs()));
	}

	@After(value ="cn.hsa.AOP.test.LogAspects.pointCut()")
	public void logEnd(JoinPoint joinpoint) {
		System.out.println("我是后置通知>>>>>"+joinpoint.getSignature().getName()+">>>>"+Arrays.toString(joinpoint.getArgs()));
	}

	@AfterReturning(value ="execution(public int MathCalculator.*(..))",returning="object")
	public void logReturn(Object object) {
		System.out.println("logReturn>>>>"+object);
	}

	@AfterThrowing(value = "execution(public int MathCalculator.*(..))",throwing = "object")
	public void logException(Exception object) {
		System.out.println("logException>>>>"+object);
	}

}

(3)Bean注入,将切面类和业务逻辑类(目标方法所在类)都加入到容器中

package cn.hsa.AOP.test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;


/**
 * AOP:【动态代理】
 * 主要三步:
 * 1、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个类是切面类(@Aspect)
 * 2、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
 * 3、开启基于注解的AOP模式;@EnableAspectJAutoProxy
 */
@EnableAspectJAutoProxy
@Configuration
public class ApiLogOfAop {
    //业务逻辑类加入到容器中
    @Bean
    public MathCalculator mathCalculator() {
        System.out.println("mathCalculator bean");
        return new MathCalculator();
    }

    //切面类加入到容器中
    @Bean
    public AopLogAspect aopLogAspect() {
        return new AopLogAspect();
    }
}

(4)测试

package cn.hsa.AOP.test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;


/**
 * AOP 测试类别
 */
@Slf4j
@Component
public class AopTest1 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApiLogOfAop.class);
        MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
        mathCalculator.testMethod(1, 1);
        applicationContext.close();
    }
}

(5)结果
Spring AOP的使用完整代码实现 (日志记录)-- 完整代码_第2张图片

你可能感兴趣的:(spring,java,aop,自定义注解)