目录
[2、编写AOP程序 计算Controller中每个方法的运行时间](#2、编写AOP程序 计算Controller中每个方法的运行时间)
[3、连接点---Join Point](#3、连接点---Join Point)
[四、Spring AOP原理](#四、Spring AOP原理)
[5、Spring AOP动态代理实现方式](#5、Spring AOP动态代理实现方式)
一、什么是AOP
AOP是一种思想,是某一类事物的统一处理。它的实现方式有很多,比如:Spring AOP,AspectJ,CGLIB等。之前的拦截器验证是否登陆、统一功能进行结果统一、统一异常处理等都是AOP的实现。但这些不是AOP的全部,这些是针对url进行拦截的。AOP的维度更加细致,也可以针对包、类、方法名、参数名等进行拦截处理。
二、AOP例子
上一篇博客实现了图书管理系统,现在希望能对部分运行时间长的功能进行时间优化,所以我们需找到哪一部分运行时间最长。针对所有方法,方法运行前计算当前时间,方法运行完成后计算当前时间,两时间之差就是运行时间。
针对以上需求实现,我们可以使用Spring AOP实现
1、添加配置
2、编写AOP程序 计算Controller中每个方法的运行时间
三、AOP详解
1、@Aspect注解
虽然注解是@Aspect,但是实现AOP思想是通过Spring AOP,该注解是为了标识这是一个切面类。
2、切点表达式
切点表达式来描述切点,常见两种方式:
(1)第一种方式--方法签名匹配
eg:execution(* com.example.book.Controller.*.*(..))
execution()是最常见的切点表达式,用来匹配方法,语法为:
java
execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)
其中:访问修饰符和异常可以省略。
①*使用说明
*可以匹配任意字符,只匹配一个元素(返回类型,包,类名,方法名,方法参数)
返回类型使用*:返回任意类型;
包名使用*:表示任意包;
类名使用*:表示任意类;
方法名使用*:表示任意方法;
参数使用*:表示一个任意类型的参数;
②..使用说明
..可以匹配多个连续的任意字符,可以匹配任意层次的包,或任意类型,任意个数的参数。
包名使用.. :表示此包以及此包下的所有子包;
参数使用.. :表示方法的参数可以是任意多个,任意类型的。
eg:
表示匹配访问修饰符为public、返回类型为String、包为com.example.demo.controller、类为TestController、方法名为t1、无参数的方法。
(2)第二种方式--自定义注解
上述方法签名匹配不能细致的描述不能类的不同方法,只能统一的描述某个包下的某个类下的某个方法,此时可以使用自定义注解。
①先定义一个注解
@Target标识了自定义注解所修饰的对象范围,即该注解可以用在什么地方。
ElementType.TYPE:用在类、接口或enum;
ElementType.METHOD:用在方法;
ElementType.PARAMETER:用在参数;
ElementType.TYPE_USE:用在任何地方;
@Retention指注解被保留的时间长短,表示注解的生命周期
RetentionPolicy.SOURCE:表示注解仅存在于源码中,编译成字节码后会被丢弃,在运行时无法获取到该注解的信息;
RetentionPolicy.CLASS:表示注解存在于源代码和字节码中,只能通过反射获取到该注解的信息,实际运行时无法获取到;
RetentionPolicy.RUNTIME:表示注解存在于源代码、字节码和运行时。
②利用该注解描述切点
@annotation注解+自定义注解的路径
③在连接点的方法上添加自定义注解
访问该方法,运行结果:
3、连接点---Join Point
指满足切点表达式下的方法都为连接点,也就是可以被AOP控制的方法,通过连接点名称.proceed(),可以调用指定路径下的方法。
4、通知---Advice
具体要做的工作,把重复的工作抽取出来单独定义,这部分代码就是通知。例如:以上代码中,计算每个方法消耗的时间,对应代码就是通知。
5、通知类型
通知类型指通知的执行时机,有以下几种:
(1)@Around
@Around:环绕通知,此注解标注的通知方法在目标方法前后都会执行。
(2)@Before
@Before:前置通知,此注解标注的通知方法在目标方法前会被执行。
(3)@After
@After:后置通知,此注解标注的通知方法在目标方法后会被执行。
(4)@AfterReturning
@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行。
目标方法没有异常:
目标方法有异常:
(5)@AfterThrowing
@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行。
目标方法没有异常:
目标方法有异常:
6、@PointCut
在以上不同的通知类型中,我们发现一个问题,针对切点表达式:execution(* com.example.b-ook.TestController.*.*(..))我们一直在重复写,可以通过其他方式简化吗???
@PointCut注解可以把公共的切点表达式提取出来,需要使用时引入即可。
上述代码就可以修改为:
注:若其他切面类需要使用该公共切点时,需要将公共切点的修饰限定符改为public,且该类在引用公共切点时,需要:全限定类名.方法名();
全限定类名:该类路径+该类类名。
7、@Order
当一个项目中存在多个切面类时,此时AOP的执行顺序是怎么样的呢???
运行结果:
结论:和类名名称有关,针对Before的通知类型,类名的字母顺序靠前的先执行。
可以通过@Order指定执行顺序,方式:@Order(数字)
运行结果:
8、切面---Aspect
切面=切点+通知
通过切面,我们可以知道哪些方法在执行哪些操作。
四、Spring AOP原理
Spring AOP是基于动态代理实现的AOP。
1、代理模式的概念
有些目标方法在进行调用时,不是直接对目标方法进行调用,而是通过代理类(为其他类提供一种代理以控制对目标方法的访问)间接调用,代理对象可以在客户端和目标对象之间起到中介的作用。
2、代理模式的主要角色
eg:中介推销房东的房子:Subject是房东租房这件事,也是中介需要做的事;RealSubject是房东;Proxy是中介。
3、静态代理
在程序运行前,代理类的.class文件就已经存在。例如:在出租房子前,中介就已经做好出租房子的相关工作,就等租户来租房子了。
Subject:
RealSubject:
Proxy:
调用代理类的rentHouse方法:
运行结果:
上述代理实现方式就是静态代理,对目标对象的每个方法的增强都是手动完成的。此时若增加一个卖房子的功能,则HouseSubject、RealHouseSubject、ProxyHouseSubject都需要修改。
4、动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标对象单独创建一个代理对象,程序运行时,由JVM实现,根据需要动态生成。
实现动态代理的两种方式:JDK和CGLIB
(1)JDK实现动态代理
①实现步骤
定义一个接口及其实现类(静态代理中的HouseSubject和RealHouseSubject);
自定义InvocationHandler,并重写invoke方法,在invoke方法中调用目标方法并自定义一些处理逻辑;
通过Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法创建代理对象。
②代码实现
运行结果:
注:JDK动态代理有一个致命的问题是其只能代理实现了接口的类。
(2)CGLIB动态代理
可以使用CGLIB动态代理非接口的类。
①实现步骤
定义一个被代理类;
自定义MethodInterceptor,并重写intercept方法,该方法用于增强目标方法,和JDK动态代理中的invoke方法类似;
通过Enhancer类的create()创建代理类。
②代码实现
先引入依赖:
运行结果:
5、Spring AOP动态代理实现方式
动态代理有两种实现方式:JDK和CGLIB,Spring AOP是使用哪种方式实现的???
在考虑目标对象所在类是否实现接口的前提下,还需考虑一些配置。
SpringBoot 2.X开始,默认使用CGLIB代理,可以通过配置项spring.qop.proxy-target-class=false来进行修改,对于实现接口的目标类,默认使用JDK代理。