目录
1.什么是AOP、SpringAOP?
2.AOP的组成
3.SpringAOP的实现
4.切点的表达式
1.什么是AOP、SpringAOP?
在学习SpringAOP之前,我们得先了解一下什么是AOP。AOP是一种面向切面编程的思想。那什么是切面呢?它其实是对某一类事情的集中处理。比如,使用AOP的具体实现用来统一处理用户在访问网页时检查用户的登录状态的判断。在之前的学习中,我们是通过在每个需要处理用户请求的方法中都编写用户登录状态判断的代码,好在整体的方法数还比较少。
那如果方法很多呢?我们就需要在每一个每一个方法中编写相同逻辑代码判断用户的状态,我们写了如此多重复的代码反而是为了处理相同的一段逻辑,这是项目开发中的大忌,大大增强了我们程序的耦合性。而这一问题就可以使用AOP思想来进行处理,当用户发送请求时,我们将处理用户请求的不同方法中判断用户是否登录的逻辑视为一类事情,使用AOP思想的具体实现对这一类事情进行集中处理,这样我们只需要编写一遍代码,便能够在需要任何进行用户是否登录的逻辑判断的方法中进行判断与处理。上述的这种做法就是AOP思想的一种具体实现。
除此之外,使用SpringAOP还能够帮助我们进行:
- 统一日志记录
- 统一方法运行时间统计
- 统一的返回个事处理
- 统一的异常处理
- 事务的开启和提交
- ...
说了这么多,AOP和SpringAOP具体有什么联系呢?
不知道大家是否还记得,我们在Spring学习之初知道:IOC是一种控制反转思想,而DI就是这种思想的具体实现。类似的,AOP就是一种面向切面的思想或者说是集中处理的思想,而SpringAOP就是就针对这种切面思想的具体实现。既然SpringAOP的功能这么强大,那还不赶紧来学习一下它的使用和实现原理!
2.AOP的组成
在正式开始学习SpringAOP之前,我们有必要对AOP的组成进行理解,以便对我们接下来SpringAOP的具体实现和原理能有个充分的理解。AOP的组成大体共有以下四部分:
- 切面(Aspect)
是切点和通知的结合,包括了横切逻辑的定义,也包括了逻辑点的定义。- 切点(Pointcut)
用来匹配连接点。- 通知(Advice)
切点的实现逻辑- 连接点(Join Point)
应用执行过程中能够插入切面的一个点
wc,看完懵逼,这是什么?看完定义想必肯定一头雾水。接下来,我们试着用通俗易懂的语言来描述一下AOP的这几个组成部分:
- 首先:切面
在程序中,切面说白了其实就是一个类,这个类中包含了具体的拦截规则和拦截后的业务实现,是切点和通知的集合。- 其次:切点。
就是上面切面类中主动拦截时的条件。- 然后:通知
就是在拦截成功后,针对这一拦截做具体处理的业务代码。在AOP,通知又分为以下五类:
- 前置通知:在执行目标业务方法之前执行的方法
- 后置通知:在执行目标业务方法之后执行的方法
- 环绕通知:在执行目标业务方法之前和之后执行的方法
- 异常通知:目标方法抛出异常后执行的方法
- 返回通知:目标方法返回之后或者说执行结束后执行的方法
- 最后:连接点
指的就是所有可能触发切面类中拦截规则即切点的所有请求的集合。
看完之后如果还不是特别清楚的话,我们接下来就来具体实现一个SpringAOP程序,来更好的理解上述AOP的相关组成。
3.SpringAOP的实现
接下来我们以拦截UserController类中的所有方法为例,来学习一下SpringAOP的具体实现步骤。
SpringAOP的具体实现分为以下三个步骤:
- 添加SpringAOP框架支持
- 定义切面类和切点
- 定义通知方法
SpringAOP中相关组成使用注解速查:
定义切面类 @Aspect 定义切点 @PointCut("切点表达式") 定义通知方法-前置通知 @Before("针对切点的方法名") 定义通知方法-后置通知 @After("针对切点的方法名") 定义通知方法-环绕通知 @Around("针对切点的方法名") 定义通知方法-异常通知 @AfterThrowing("针对切点的方法名") 定义通知方法-返回通知 @AfterReturning("针对切点的方法名")
我们来在程序中具体实现,同时学习一下SPringAOP实现的细节及注意事项:
4.切点的表达式
AspectJ ⽀持三种通配符* :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数).. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。+ :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的 所有⼦类包括本身
切点表达式由切点函数组成,其中execution()为最常用的切点函数,切点定义的注解及其函数的语法如下:
@PointCut("execution(<修饰符> <返回类型> <包.类.方法(参数)> <异常>)")
【ps】:其中的修饰符和异常选项通常省略。
😄例如,在我们上边的SpringAOP程序中:
接下里我们来看看切点表达式中对应选项的一些常用写法:
😄对于修饰符
修饰符 含义 * 匹配目标类中被任意修饰符修饰的方法 public 匹配目标类中被public修饰的方法 ... ... 😄对于返回类型
返回类型 含义 * 匹配目标类中任意返回值类型的方法 String 匹配目标类中字符串类型返回值的方法 void 匹配目标类中无返回值的方法 ... ... 😄对于包类型
包类型 含义 com.shuai.demo 固定包demo com.shuai.demo.*.service demo二级包中任意的service包,例如:(com.shuai.demo.staff.service) com.shuai.demo.. demo包下的任意子包,包含自身 com.shuai.demo.*.service.. demo二级包中任意的service包中的任意包,包含service自身 ... ... 😄对于包中的类
类写法 含义 包.UserController 固定类Controller 包.*Controller 以Controller结尾的类 包.User* 以User开头的类 包.* 包中的任意类 ... .. 😄 对于类中的方法
方法的写法 含义 包.类.getUser 固定方法getUser() 包.类.get* 类中以add开头的方法 包.类.*Do 类中以Do结尾的方法 包.类.* 类中的任意方法 ... ... 😄对于方法中的参数
方法中的参数写法 含义 包.类.方法() 类中的无参方法 包.类.方法(int) 类中带一个Int类型参数的方法 包.类.方法(int,String) 类中带一个int类型一个String类型的参数的方法 包.类.方法(..) 类中任意参数的方法 ... ...
写一些切点表达式来理解一下它的含义:
- execution(* com.shuai.demo.User.*(..)) :匹配 User 类⾥的所有⽅法。
- execution(* com.shuai.demo.User+.*(..)) :匹配User类的⼦类包括User类中的所有⽅法。
- execution(* com.shuai.*.*(..)) :匹配 com.shuai包下的所有类的所有⽅法。
- execution(* com.shuai..*.*(..)) :匹配 com.shuai包下、⼦孙包下所有类的所有⽅法。
- execution(* com.shuai.demo.addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个 参数类型是 int
- ...
5.实例:SpringAOP实现统计方法执行时间
@Aspect //告诉SpringAOP这是一个切面类 @Component //将其加入到Spring中进行管理 @Slf4j //这里握住主要使用了其中的日志对象 public class UserAspect { @Pointcut("execution(String com.example.demo.controller.UserController.*(..)") public void getExecuteTime() { } /** * 统计UserController中的testAdvice方法的执行时间 * @param joinPoint * @return */ @Around("getExecuteTime()") public Object timeAround(ProceedingJoinPoint joinPoint) { // 定义返回对象、得到方法需要的参数 Object obj = null; Object[] args = joinPoint.getArgs(); long startTime = System.currentTimeMillis(); try { obj = joinPoint.proceed(args); } catch (Throwable e) { log.error("统计某方法执行耗时环绕通知出错", e); } // 获取执行的方法名 long endTime = System.currentTimeMillis(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String methodName = signature.getDeclaringTypeName() + "." + signature.getName(); // 打印耗时的信息 log.info(methodName + "执行了: " + (endTime - startTime) + "ms"); return obj; } }