- 博主简介:想进大厂的打工人
- 博主主页:@xyk:
- 所属专栏: JavaEE进阶
目录
文章目录
一、初识AOP
1.1 什么是AOP?
1.2 AOP的组成
1.2.1 切面(Aspect)
1.2.2 切点(Pointcut)
1.2.3 连接点(Join Point)
1.2.4 通知(Advice)
1.3 AOP的使用场景
二、Srping AOP 实现
2.1 添加Spring AOP 依赖
2.2 定义切面和切点
2.3 定义通知
三、Spring AOP 实现原理
3.1 什么是动态代理?
3.2 JDK 动态代理实现
3.3 CGLIB 动态代理实现
3.4 JDK 和 CGLIB 实现的区别
AOP(Aspect Oriented Programming):面向切面编程,它是⼀种思想,它是对某⼀类事情的
集中处理。在我们想要对某一件事情进行集中处理,就可以使用到AOP,它提供一种将程序中的横切关注点模块化的方式。在 AOP 中,我们将这些横切关注点称为“切面”,它们独立于业务逻辑模块,但是可以在程序运行的不同阶段被织入到业务逻辑中。
简单来说,AOP 就是对某一件事进行集中处理的思想方式~
切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包
括了连接点的定义。相当于处理某方面具体问题的一个类,包含多个方法,而这些方法就是切点和通知。
Pointcut 的作⽤就是提供⼀组规则来匹配连接点(Join Point),给满足规则的连接点添加通知(Advice),可以理解为用来进行主动拦截的规则(配置)
应⽤执⾏过程中能够插⼊切⾯的⼀个点,连接点可以理解为可能会触发AOP规则的所有点。(所有请求)
在AOP术语中,切面的工作被称之为通知。通知是切面在连接点上执行的动作。它定义了在何时(例如在方法调用之前或之后)以及如何(例如打印日志或进行性能监控)应用切面的行为。即,程序中被拦截请求触发的具体动作。
Spring 切⾯类中,可以在方法上使⽤以下注解,会设置⽅法为通知方法,在满⾜条件后会通知本
⽅法进⾏调⽤:
在做任何一个系统都需要登录功能,那么几乎想要使用这个系统都需要我们进行验证用户登录状态,我们之前的处理⽅式是每个 Controller 都要写⼀遍⽤户登录验证,然⽽当你的功能越来越多,那么你要写的登录验证也越来越多,⽽这些⽅法⼜是相同的,这么多的⽅法就会代码修改和维护的成本。对于这种功能统⼀,且使⽤的地⽅较多的功能,就可以考虑 AOP来统⼀处理了。
除了统一登录判断外,使用AOP还可以实现:
Spring AOP 的实现步骤如下:
org.springframework.boot
spring-boot-starter-aop
使用 @Aspect
注解表明当前类为一个切面,而在切点中,我们要定义拦截的规则,具体实现如下:
@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {
// 定义切点(配置拦截规则)
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){
}
}
在上述实现代码中,pointcut 为一个空方法,只是起到一个“标识”的作用,标识下面的通知方法具体指的是哪个切点,切点可以有多个。
切点表达式由切点函数组成,其中 execution()
是最常⽤的切点函数,⽤来匹配⽅法,语法为:
execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
修饰符和异常可以省略
常见的切点表达式的示例:
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/hi")
public String sayHi(String name){
System.out.println("执行了Hi");
return "Hi," + name;
}
@RequestMapping("/hello")
public String sayHello(){
System.out.println("执行了Hello");
return "Hello,world";
}
}
通知定义的是被拦截方法具体要执行的业务。我们上面列出了可以使用哪些通知~这里举出例子
package com.example.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {
// 定义切点(配置拦截规则)
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void beforeAdvice(){
System.out.println("执行了前置通知~");
}
@After("pointcut()")
public void AfterAdvice(){
System.out.println("执行了后置通知~");
}
/**
* 环绕通知
* @param joinPoint
* @return
*/
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint){
System.out.println("进入了环绕通知~");
Object obj = null;
try {
// 执⾏拦截⽅法
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("退出了环绕通知~");
return obj;
}
}
环绕通知是在前置通知之前和后置通知之后运行的~
Spring AOP 是通过动态代理的⽅式,在运⾏期将 AOP 代码织⼊到程序中的,它的实现⽅式有两种:JDK Proxy
和 CGLIB
。因此,Spring 对 AOP 的支持局限于方法级别的拦截。
动态代理(Dynamic Proxy)是一种设计模式,它允许 在运行时创建代理对象,并将方法调用转发给实际的对象。 动态代理可以用于实现横切关注点(如日志记录、性能监控、事务管理等)的功能,而无需修改原始对象的代码。
在Java中,动态代理通常使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。
调用者在调用方法时,会先转发给代理类创建的代理对象,随后再由代理对象转发给目标对象。
以下是使用动态代理的一般步骤:
先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,再通过 Proxy 来创建代理类。
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, Method
Proxy 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(),n
ew PayServiceCGLIBInterceptor(target));
proxy.pay();
}
}