AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP(面向对象编程)的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,使用AOP进行编程,可以降低代码的侵入性,提高程序的可重用性,同时提高了开发的效率。
简单来说,aop是一种是一种思想和和规范,通过选择不同方法(可以在不同类)在不同时机(方法执行前、执行后、返回前后、抛出异常后...),对选择的方法统一添加处理逻辑。
springAOP是对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:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义 的行为。
对于使用JDK实现的动态代理:代理类和被代理类要实现同一个接口,在实现的过程中会使用到的api如下:Invocationhandler,proxy.newProxyInstance对于基于GCLIB实现的动态代理:继承原始类(原始类不能被final修饰),是专门生成代理类的第三方框架,是基于asm字节码框架生成的JDK 和 CGLIB 的区别1. JDK 实现,要求被代理类必须实现接口,之后是通过 InvocationHandler 及 Proxy,在运行时动态的在 内存中生成了代理类对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实现的方式), 只是该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成。2. CGLIB 实现,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类对象。
JDK和GCLIB实现的代理类如下:
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();
}
}
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();
}
}
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