一、AOP
1、什么是AOP
AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。
使用AOP的优势:
|------------|------------------------------------------------|
| 减少重复代码 | 不需要在业务方法中定义大量的重复性的代码,只需要将重复性的代码抽取到AOP程序中即可。 |
| 代码无侵入 | 在基于AOP实现这些业务功能时,对原有的业务代码是没有任何侵入的,不需要修改任何的业务代码。 |
| 提高开发效率 | |
| 维护方便 | |
2、AOP快速入门
①导入依赖项
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
②编写代码
java
@Slf4j
@Aspect
@Component
public class RecordTimeAspect {
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
// 1. 记录开始时间
long begin = System.currentTimeMillis();
//执行原始方法
Object result = pjp.proceed();
//记录方法执行结束时间
long end = System.currentTimeMillis();
//计算方法执行耗时
log.info("方法执行耗时 {} ms", end - begin);
//返回原始方法执行结果
return result;
}
}
打开网页访问部门页面,结果如下:


由此可以得知,AOP的常见应用场景:记录系统的操作日志、权限控制、事务管理(下一章就更)。
3、AOP的核心概念
①连接点:JoinPoint
可以被AOP控制的方法(暗含方法执行时的相关信息),在SpringAOP提供的JoinPoint当中,封装了连接点方法在执行时的相关信息。

②通知-Advice
指哪些重复的逻辑,也就是共性功能(最终体现为一个方法),在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。

③切入点-PointCut
匹配连接点的条件,通知仅会在切入点方法执行时被应用,在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点.

④切面-Aspect
描述通知与切入点的对应关系(通知+切入点)

⑤目标对象
目标对象指的就是通知所应用的对象

注意: Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。
4、AOP进阶
①通知类型

Spring提供了**@PointCut**注解,该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可
java
@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.itheima.service.*.*(..))")
private void pt() {}
//前置通知
@Before("pt()")
public void before(JoinPoint joinPoint) {
log.info("before.....");
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
log.info("around before.....");
Object result = pjp.proceed();
log.info("around after.....");
return result;
}
//后置通知
@After("pt()")
public void after(JoinPoint joinPoint) {
log.info("after.....");
}
//返回后通知(程序正常执行的情况下,会执行的后置通知)
@AfterReturning("pt()")
public void afterReturning(JoinPoint joinPoint) {
log.info("afterReturning.....");
}
//异常通知(程序执行过程中出现异常,会执行的后置通知)
@AfterThrowing("pt()")
public void afterThrowing(JoinPoint joinPoint) {
log.info("afterThrowing.....");
}
}
启动服务后,结果如下:

若程序中出现了异常,将触发异常通知,如下:


注意:@AfterReturning标识的通知方法不会执行,@AfterThrowing标识的通知方法执行了;@Around环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会在执行了 (因为原始方法调用已经出异常了)
②通知顺序
在不同切面类中,默认按照切面类的类名字母排序。
控制通知的执行顺序有两种方式:修改切面类的类名(这种方式非常繁琐、而且不便管理);
使用Spring提供的@Order注解(前置通知:数字越小先执行; 后置通知:数字越小越后执行)。
定义几个切面类:
java
@Slf4j
@Component
@Aspect
@Order(2)
public class MyAspect2 {
//前置通知
@Before("execution(* com.itheima.service.impl.*.*(..))")
public void before(){
log.info("MyAspect2 -> before ...");
}
//后置通知
@After("execution(* com.itheima.service.impl.*.*(..))")
public void after(){
log.info("MyAspect2 -> after ...");
}
}
java
@Slf4j
@Component
@Aspect
@Order(8)
public class MyAspect3 {
//前置通知
@Before("execution(* com.itheima.service.impl.*.*(..))")
public void before(){
log.info("MyAspect3 -> before ...");
}
//后置通知
@After("execution(* com.itheima.service.impl.*.*(..))")
public void after(){
log.info("MyAspect3 -> after ...");
}
}
java
@Slf4j
@Component
@Aspect
@Order(5)
public class MyAspect4 {
//前置通知
@Before("execution(* com.itheima.service.impl.*.*(..))")
public void before(){
log.info("MyAspect4 -> before ...");
}
//后置通知
@After("execution(* com.itheima.service.impl..*(..))")
public void after(){
log.info("MyAspect4 -> after ...");
}
}
启动服务后:

**总结:**不同的切面类当中,默认情况下通知的执行顺序是与切面类的类名字母排序是有关系的。可以在切面类上面加上@Order注解,来控制不同的切面类通知的执行顺序。
③切入点表达式
描述切入点方法的一种表达式
常见形式:
|------------------------------|
| execution(......):根据方法的签名来匹配 |
| @annotation(......) :根据注解匹配 |
execution
java
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
其中带?的表示可以省略的部分:
-
访问修饰符:可省略(比如: public、protected)
-
包名.类名: 可省略
-
throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

@annotation自定义注解
实现步骤:编写自定义注解,然后在业务类要做为连接点的方法上添加自定义注解。
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
}


切面类:
java
@Slf4j
@Component
@Aspect
@Order(5)
public class MyAspect5 {
//前置通知
@Before("@annotation(com.itheima.anno.LogOperation)")
public void before(){
log.info("MyAspect5 -> before ...");
}
//后置通知
@After("@annotation(com.itheima.anno.LogOperation)")
public void after(){
log.info("MyAspect5 -> after ...");
}
}
启动服务后:

总结:
