此处做个简单的记录(部分知识点+代码实现)
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 :织⼊,指把增强应⽤到⽬标对象来创建代理对象的过程。
可以在网盘取代码:
链接:https://pan.baidu.com/s/1NoioAoSdU8agTUve7QYUmQ
提取码:1234
简单实现其实就三个类:
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自定义注解测试回参";
}
}
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();
}
}