目录
1、什么是Spring AOP
1.1、什么是AOP
官方回答:
AOP(Aspect Oriented Programming):面向切面编程,它是一种思想,是对某一件事情的集中处理。
看不懂没关系,继续往下看~
举例说明:
例如我们要实现一个用户登录权限的校验,如果不使用AOP,我们就需要在所有的页面中都要各自实现或者调用用户登录验证的方法(出登录和注册页面不需要),而使用AOP后,我们只需要在某一处配置一下,就可以实现所有需要判断用户登录验证的地方进行用户登录验证了,不再需要每个方法中都写相同的用户登录验证了~
1.3、AOP与Spring AOP的关系
AOP是一种思想,而Spring AOP是一个框架,提供了一种对AOP思想的实现,他们的关系类似于IoC和DI
2、AOP的组成
以下四部分:
2.1、切面(Aspect)
官方回答:
切面(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义
通俗理解:
在程序中就是一个处理某方面具体问题的一个类。类里面包含了很多方法,这些方法就是切点和通知
2.2、连接点(Join Point)
官方回答:
应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,抛出异常时,甚至是修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为
通俗理解:
可能会触发AOP规则的所有点(所有请求)
也就是说,你要告诉AOP他要拦截哪些请求,例如上述的例子,你要告诉程序,除了登录和注册其他的请求都需要验证用户是否登录~
2.3、切点(Pointcut)
官方回答:
Pointcut是匹配Join Point的谓词
Pointcut的作用就是提供一组规则(使用AspectJ pointcut expression language来描述)来匹配Join Point,给满足规则的Join Point添加Advice
通俗理解:
用来进行主动拦截的规则(配置)
2.4、通知(Advice)
官方理解:
在AOP术语中,切面的工作被称之为通知
通知:定义了切面是什么,何时使用,其描述了切面要完成的工作,还解决何时执行这个工作的问题
通俗理解:
程序中被拦截请求触发的具体动作(做什么事),就是在通知中实现具体的业务代码。
通知分类:
- 前置通知:使用注解@Before,通知方法会在目标方法调用之前执行
- 后置通知:使用注解@After,通知方法会在目标方法返回或者抛出异常后调用
- 返回之后通知:使用注解@AfterReturning,通知方法会在目标方法返回后调用
- 抛异常后通知:使用注解@AfterThrowing,通知方法会在目标方法抛出异常后调用
- 环绕通知:使用注解@Around,通知包裹了被通知的方法,在被通知的方法之前和调用之后执行自定义行为
AOP具体的执行动作:
3、Spring AOP实现步骤
3.1、添加Spring AOP依赖
创建Spring Boot项目,然后在Maven仓库中搜索AOP,添加依赖:
3.2、定义切面(创建切面类)
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect //告诉序程序,这个一个切面类
@Component//随着框架的启动,面也要启动~
public class UserAspect {
}
3.3、定义切点(配置拦截规则)
/**
* 切点(配置拦截规则)
*/
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut() {
}
切点表达式说明:
AspectJ支持三种通配符
- **:匹配任意字符,只匹配一个元素(包、类、方法、方法参数)
- .. :匹配任意字符:可以匹配多个元素,在表示类时,必须和*联合使用
- +:表示按照类型匹配指定类的所有的类本身及其子类,必须跟在类名后面,
切点表达式由切点函数组成,其中executio()是最常用的切点函数,用来匹配方法,
语法为:execution(<修饰符><返回类型><包.类.方法(参数)><异常>)
- 修饰符【一般省略】:如public 公共方法、*代表任意
- 返回值【不能省略】:如void 返回没有值、String返回值字符串、*返回任意
- 包:如:固定包com.cr、com子包下任意某包com.*.cr、com包下所有自包,包含自己com.. 、com包下任意子包com.*.cr..
- 类:如指定类、以xxx结尾的某些类、以xxx开头的某些类、任意类(*)
- 方法名【不能省略】:如指定方法名、以xxx开头的方法、以xxx结尾的某些方法、任意(*)
- 参数:如无参()、一个整型(int)、参数任意(..)
3.4、定义通知的实现
/**
* 前置通知
*/
@Before("pointcut()")
public void beforeAdvice() {
System.out.println("执行了前置通知");
}
/**
* 后置通知
*/
@After("pointcut()")
public void afterAdvice() {
System.out.println("执行了后置通知");
}
这样看代码,看不懂的哦?我们看一下我的目录
我们再捋一捋:
我们在UserController类中,写两个方法,测试一下:
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/hi")
public String sayHi() {
System.out.println("执行了目标方法,sayHi()方法");
return "hi,老朋友~";
}
@RequestMapping("/hello")
public String sayHello() {
System.out.println("执行了目标方法,sayHello()方法");
return "hello,新朋友!!!";
}
}
程序运行起来,前端:
后端控制台:
前端:
控制台:
咱们加一个类,当这个类不被拦截时:
@RestController
@RequestMapping("/art")
public class ArticleController {
@RequestMapping("/hi")
public String sayHi() {
System.out.println("hi,hi,看到我了吗?");
return "hi,hi,看到我了吗?";
}
}
程序运行,前端:
控制台:
说明AOP是起作用了的~
再看一个环绕通知,其他的和前置通知和后置通知一样我就不举例啦~
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("进入环绕通知");
Object obj = joinPoint.proceed();//执行目标方法
System.out.println("退出环绕通知");
return obj;
}
前端:
控制台:
4、Spring AOP实现原理
前置小知识:
4.1、织入
织入(Weaving):代理的生成时机
织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中
在目标对象的生命周期里有多个点可以进行织入:
- 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的
- 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的jiazai时织入(Load-time weaving.LTW)就支持以这种方式织入切面
- 运行期:切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象。SpringAOP就是以这种方式织入切面的
4.2、动态代理
此种实现在设计模式上称为动态代理模式,在实现的技术手段上,都是在class代码运行期,动态的织入字节码
Spring框架中的AOP,主要基于两种方式:JDK和CGLIB的方式。这两种方式的代理目标都是被代理类中的方法,在运行期,动态的织入字节码生成代码类
- CGLIB是Java中的动态代理框架,主要作用就是根据目标类和方法,动态生成代理类
- Java中的动态代理框架,几乎都是依赖字节码框架(如ASM、Javassist等)实现的
- 字节码框架是直接操作class字节码的框架。可以加在已有的class字节码文件信息,修改部分信息,或动态生成一个class
JDK和CGLIB实现的区别:
- JDK实现,要求被代理类必须实现接口,之后是通过InvocationHandler及Proxy,在运行时动态的在内存中生成了代理类对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实现的方式),只是该代理是在运行期时,动态的织入统一的业务逻辑字节码来完成
- CGLIB实现,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类对象
4.3、原理:
Spring AOP是构建在动态代理基础上的,因此Spring 对AOP的支持局限于方法级别的拦截
Spring AOP支持JDKProxy和CGLIBProxy方式实现动态代理。默认情况下,对于非final修饰的类,Spring AOP会基于CGLIBProxy生成代理类,CGLIBProxy生成代理类的原理就是继承目标类,被关键字final修饰的类,由于不能被继承,所以会基于DKProxy生成代理类
Spring AOP的本质就是生成一个目标对象的代理类,当前端传来请求时,不会将请求直接交给目标对象,而是首先代理类进行代理,如果满足一定的条件,才会将请求交给目标对象
如果处理请求前需要登录验证,那么代理类会去验证用户账号是否登录,如果用户登陆了才会将请求交给目标对象并执行核心业务代码,否则代理类之间返回响应让用户先登录~
好啦~本期到这了就结束咯~晚安!!!