一,AOP基础
1.1 AOP概述
1.2 AOP核心概念
1.3 AOP快速入门
-
导依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.20</version> </dependency> -
制作连接点方法(原始操作,Service接口与实现类)
@Service public class aopServiceImpl implements aopService { private aopDao dao; @Autowired public void setDao(aopDao dao){ this.dao=dao; } @Override public void save() { dao.save(); System.out.println("Service....save....."); } @Override public void update() { dao.update(); System.out.println("Service....update...."); } } -
制作共性功能(通知类与通知)
public class MyAdvice { public void before(){ System.out.println("before"); } } -
定义切入点
public class MyAdvice { @Pointcut("execution(* com.aopDemo.service.impl.aopServiceImpl.save())") public void pt(){}; } -
绑定切入点与通知关系(切面)
public class MyAdvice { @Pointcut("execution(* com.aopDemo.service.aopService.save())") public void pt(){}; //这种pt()是简写,实际上我们可以把pt抽取到一个pt类然后通过类名.方法名()来调用 @Before("pt()") public void before(){ System.out.println("before"); } } -
定义通知类受Spring容器管理,并定义当前类为切面类
@Component @Aspect public class MyAdvice { @Pointcut("execution(* com.aopDemo.service.aopService.save())") public void pt(){}; @Before("pt()") public void before(){ System.out.println("before"); } } -
开启Spring对AOP注解驱动支持
@Configuration @ComponentScan(basePackages ="com.aopDemo") @EnableAspectJAutoProxy//开启spring对aop注解驱动的支持 public class SpringConfig { }
1.4 AOP执行流程
-
Spring容器启动
-
读取所有切面配置中的切入点
-
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
-
匹配失败,创建对象
-
匹配成功,创建原始对象(目标对象)的代理对象
-
-
获取bean执行方法
-
获取bean,调用方法并执行,完成操作
-
获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作)
-
目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
**代理(**Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
AOP在执行的时候,对目标对象创建对应的代理对象,最终注入的是代理对象。
二,AOP进阶
2.1 切入点表达式
2.3.1 execution
注意:虽然:包名.类名可省略,一般不会省略。
通配符:可以使用通配符描述切入点,快速描述。
-
*:单个独立的任意符合,可以独立出现,也可以作为前缀或后缀的匹配符出现。execution (public * com.itheima.*.UserService.find*(*))匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的,带有一个参数的方法。
-
..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写。execution (public User com..UserService.findById(..))匹配com包下的任意包中的UserService类或接口中所有名为findById的方法。
-
+:专用与匹配子类类型execution(* *..*Service+.*(..))
举例:
-
完整写法:
-
省略修饰符的写法
-
全部省略的写法
这种写法可能会匹配到多个方法,不建议使用。
-
基于接口的写法
-
使用通配符的写法
书写建议:
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*****通配快速描述
- 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
- 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名词采用*匹配,例如getByld书写成getBy*,selectAll书写成selectAl
- 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
2.3.2 @annotation
使用步骤:
-
自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String value() default ""; } -
切面类上
@Component @Aspect public class MyAdvice { @Pointcut("@annotation(com.aopDemo.annoation.Log)") public void pt2(){}; @Before("pt2()") public void before(){ System.out.println("before"); } } -
连接点类上
@Service public class aopServiceImpl implements aopService { private aopDao dao; @Autowired public void setDao(aopDao dao){ this.dao=dao; } @Log @Override public void save() { dao.save(); System.out.println("Service....save....."); } }这个注解只能加到实现类上,加到接口是无效的。
2.2 通知类型
重点记住一下环绕通知,它与其他的通知有所区别:
- 当用环绕通知绑定的方法有返回值的时候,我们在通知里面也需要返回这个值,如下图。
@Around注意事项
- 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
- 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
- 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
- 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
@PointCut注解
- 该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。
//抽取重复的切入点表达式
@Pointcut("execution(* com.normaling.controller.*.*(..))")
private void pt(){}
//private的目的是为了限制只能在当前切面类中使用,如果是public则外部切面类也可复用
//复用
@Around("pt()")
public Object testAops(ProceedingJoinPoint pjp) throws Throwable{
long begin = System.currentTimeMillis();
Object proceed = pjp.proceed();
long end = System.currentTimeMillis();
System.out.println(pjp.getSignature() + "耗时:" + (end - begin) + "毫秒");
return proceed;
}
2.3 通知顺序
当多个切面的切入点匹配到同一个目标方法,目标方法运行时,多个通知方法都会被执行,此时会存在通知的执行顺序。
2.4 AOP通知获取数据
2.4.1 获取切入点方法的参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProceedJointPoint:适用于环绕通知
小技巧:
-
对于@Around里面的获取到的args,我们可以手动进行更改,然后再将数值传递过去
Object[] args=pjp.getArgs(); args[0]=1; Object ret=pjp.proceed(args)
2.4.2 获取切入点方法返回值
注意点:
-
针对
@AfterReturning注解,当我们返回值和JointPoint参数都要用的时候,JointPoint必须是第一个@AfterReturning(value="pt()",returning="ret") public void afterReturning(JointPoint jp,String ret){ }
2.4.3 获取切入点方法运行异常信息
2.5 连接点
-
ProceedingJoinPoint
-
JoinPoint