一.什么是AOP
AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP(面向对象编程)的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,使用AOP进行编程,可以降低代码的侵入性,提高程序的可重用性,同时提高了开发的效率。
简单来说,aop是一种是一种思想和和规范,通过选择不同方法(可以在不同类)在不同时机(方法执行前、执行后、返回前后、抛出异常后…),对选择的方法统一添加处理逻辑。
springAOP是对AOP思想的一种具体实现,但是它只实现了对方法的增强,没有实现对属性的增强
二.AOP的典型应用场景
- 统一的日志记录
- 统一的方法执行时间统计
- 统一的返回格式设置
- 统一的异常处理
- 事务的开启和提交
三.关于AOP的核心知识点
切面类:定义该类是一个切面类
切点(PointCut):定义要增强的方法
Pointcut
的作用就是提供一组规则(使用
AspectJ pointcut expression language
来描述)来匹配
Join Point,给满足规则的
Join Point
添加
Advice通知:定义方法增强的实现逻辑
定义了切面是什么,何时使用,其描述了切面要完成的工作,还解决何时执行这个工作的问题。
切点表达式说明
AspectJ
支持三种通配符:*
:匹配任意字符,只匹配一个元素(包,类,或方法,方法参数)。* ..
:匹配任意字符,可以匹配多个元素 ,在表示类时,必须和
*
联合使用。+
:表示按照类型匹配指定类的所有类,必须跟在类名后面,如
com.cad.Car+ ,
表示继承该类的所有子类包括本身。切点表达式由切点函数组成,其中
execution()
是最常用的切点函数,用来匹配方法,语法为:execution(
修饰符
>
返回类型
>
包
.
类
.
方法
(
参数
)>
异常
>)
修饰符
和
异常
可以省略。表达式示例
定义相关通知
通知定义的是被拦截的方法具体要执行的业务,比如用户登录权限验证方法就是具体要执行的业务。Spring AOP 中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本方法进行调用:前置通知使用@Before:通知方法会在目标方法调用之前执行。后置通知使用@After:通知方法会在目标方法返回或者抛出异常后调用。返回之后通知使用@AfterReturning:通知方法会在目标方法返回后调用。抛异常后通知使用@AfterThrowing:通知方法会在目标方法抛出异常后调用。环绕通知使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义 的行为。
四.AOP的实现原理
Spring AOP
是构建在
动态代理
基础上,因此
Spring
对
AOP
的支持局限于方法级别的拦截
。
是构建在
动态代理
基础上,因此
Spring
对
AOP
的支持局限于方法级别的拦截
。
Spring AOP
支持
JDK Proxy
和
CGLIB
方式实现动态代理。默认情况下,实现了接口的类(被代理类),使用
AOP
会基于 JDK 生成代理类,没有实现接口的类,会基于
CGLIB
生成代理类。
支持
JDK Proxy
和
CGLIB
方式实现动态代理。默认情况下,实现了接口的类(被代理类),使用
AOP
会基于 JDK 生成代理类,没有实现接口的类,会基于
CGLIB
生成代理类。
对于使用JDK实现的动态代理:代理类和被代理类要实现同一个接口,在实现的过程中会使用到的api如下:Invocationhandler,proxy.newProxyInstance对于基于GCLIB实现的动态代理:继承原始类(原始类不能被final修饰),是专门生成代理类的第三方框架,是基于asm字节码框架生成的JDK
和
CGLIB
的区别1. JDK 实现,要求被代理类必须实现接口,之后是通过 InvocationHandler 及 Proxy,在运行时动态的在 内存中生成了代理类对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实现的方式), 只是该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成。2. CGLIB 实现,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类对象。
织入(
Weaving
):代理的生成时机
Weaving
):代理的生成时机
织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中。
在目标对象的生命周期里有多个点可以进行织入:
编译期:
切面在目标类编译时被织入。这种方式需要特殊的编译器。
AspectJ
的织入编译器就是以这种
切面在目标类编译时被织入。这种方式需要特殊的编译器。
AspectJ
的织入编译器就是以这种
方式织入切面的。
类加载器:
切面在目标类加载到
JVM
时被织入。这种方式需要特殊的类加载器(
ClassLoader
)
,
它可以 在目标类被引入应用之前增强该目标类的字节码。AspectJ5
的加载时织入(
load-timeweaving. LTW
)
切面在目标类加载到
JVM
时被织入。这种方式需要特殊的类加载器(
ClassLoader
)
,
它可以 在目标类被引入应用之前增强该目标类的字节码。AspectJ5
的加载时织入(
load-timeweaving. LTW
)
就支持以这种方式织入切面。
运行期:
切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,
AOP
容器会为目标对象动态创建一个代理对象。SpringAOP
就是以这种方式织入切面的。
切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,
AOP
容器会为目标对象动态创建一个代理对象。SpringAOP
就是以这种方式织入切面的。
五.AOP的实现方式
JDK和GCLIB实现的代理类如下:
JDK
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//动态代理:使用JDK提供的api(InvocationHandler、Proxy实现),此种方式实现,要求被代理类必须实现接口
public class PayServiceJDKInvocationHandler implements InvocationHandler {
//目标对象即就是被代理对象
private Object target;
public PayServiceJDKInvocationHandler(Object target) {
this.target = target;
}
//proxy代理对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
//1.安全检查
System.out.println("安全检查");
//2.记录日志
System.out.println("记录日志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过反射调用被代理类的方法
Object retVal = method.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
}
public static void main(String[] args) {
PayService target = new AliPayService();
//方法调用处理器
InvocationHandler handler =
new PayServiceJDKInvocationHandler(target);
//创建一个代理类:通过被代理类、被代理实现的接口、方法调用处理器来创建
PayService proxy = (PayService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{PayService.class},
handler
);
proxy.pay();
}
}
GCLIB
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.Method;
public class PayServiceCGLIBInterceptor implements MethodInterceptor {
//被代理对象
private Object target;
public PayServiceCGLIBInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy
methodProxy) throws Throwable {
//1.安全检查
System.out.println("安全检查");
//2.记录日志
System.out.println("记录日志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过cglib的代理方法调用
Object retVal = methodProxy.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
}
public static void main(String[] args) {
PayService target = new AliPayService();
PayService proxy = (PayService) Enhancer.create(target.getClass(), new
PayServiceCGLIBInterceptor(target));
proxy.pay();
}
}
六.AOP的使用
SpringAOP在使用时主要有以下两种使用方式:
①使用aspectj风格的注解进行开发:
添加依赖
org.springframework.boot
spring-boot-starter-aop
定义通知时机和通知逻辑
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author tongchen
* @create 2023-05-08 11:14
*/
@Aspect
//将配置加载到容器中
@Component
public class AopConfig {
//定义切点
//包后面一定要有一个空格
@Pointcut("execution(* com.ljl..service.*Service.*(..))")
public void pointCut(){
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint){
//pointcut匹配的方法
try {
Long start=System.currentTimeMillis();
Object proceed = joinPoint.proceed();
Long end=System.currentTimeMillis();
System.out.println("方法运行的时间:"+(end-start));
} catch (Throwable e) {
throw new RuntimeException(e);
}
return null;
}
}
定义切点方法
import org.springframework.stereotype.Service;
import java.util.ArrayList;
/**
* @author tongchen
* @create 2023-05-08 11:13
*/
@Service
public class AspectService {
public Object timeTest(){
//模拟获取数据库中的数据
return new ArrayList();
}
}
②定义一个类,实现MethodInteceptor