AOP,面向切面编程。其实是一种编程思想。主要是对多个相同的代码逻辑的方法进行切面,分离出相同的代码,整合至一个模块里面(比如事务管理,安全管理,系统日志等)。

为什么需要AOP

如果有一个场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?
如果都用 复制粘贴 的方法,那势必会造成代码的冗余,后面在维护方面上也会很困难。
如果对 公共的代码 进行方法的封装,就可以节约代码的冗余,当这段代码修改修改时,也只需要改变这个方法就可以了。
但是如果有一天需求发生改变,我们可能需要再抽象出一个方法,然后再在需要的地方调用这个新的方法。又或者我们不需要这个方法了,我们还是得删除每一处调用该方法的地方。
此时,我们可以使用AOP的思想,做公共代码做横切面,整合成一个新的模块,更加方便操作和维护。

AOP的分类

AOP要达到的效果是,保证开发者在不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP本质是由AOP框架修改业务组件的多个方法的源代码。AOP其实就是代理模式的一种应用。可以分两类:
静态AOP实现
AOP框架在编译阶段对程序源代码进行修改,生成静态的AOP代理类。比如AspectJ;
动态AOP实现
AOP框架在运行阶段动态生成代理对象(在内存中以JDK动态代理,或CGLib动态地生成AOP代理类)比如Spring AOP。

AOP术语

通知(Advice): AOP框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
连接点(JoinPoint):连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在Spring AOP中,连接点总是方法的调用。
切点(PointCut):可以插入增强处理的连接点。
切面(Aspect):切面是通知和切点的结合。
引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象这个过程就是织入。

Spring AOP

基本代码

  1. package com.ly.ybg.school;
  2. public interface IBuy {
  3. String buy();
  4. }
  5. // Boy.class
  6. package ccom.ly.ybg.school;
  7. import org.springframework.stereotype.Component;
  8. @Component
  9. public class Boy implements IBuy {
  10. @Override
  11. public String buy() {
  12. System.out.println("男孩买了一个游戏机");
  13. return "游戏机";
  14. }
  15. }
  16. // Girl.class
  17. package com.ly.ybg.school;
  18. import org.springframework.stereotype.Component;
  19. @Component
  20. public class Girl implements IBuy {
  21. @Override
  22. public String buy() {
  23. System.out.println("女孩买了一件漂亮的衣服");
  24. return "衣服";
  25. }
  26. }
  1. // 配置文件 AppConfig.class
  2. package com.ly.ybg.school;
  3. import org.springframework.context.annotation.ComponentScan;
  4. import org.springframework.context.annotation.Configuration;
  5. @Configuration
  6. @ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})
  7. public class AppConfig {
  8. }
  1. package com.ly.ybg.school;
  2. import com.ly.ybg.school.Boy;
  3. import com.ly.ybg.school.Girl;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. public class AppTest {
  6. public static void main(String[] args) {
  7. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  8. Boy boy = context.getBean("boy",Boy.class);
  9. Girl girl = (Girl) context.getBean("girl");
  10. boy.buy();
  11. girl.buy();
  12. }
  13. }

AOP功能

Spring AOP支持的AspectJ指示器

Aspect指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法
@arg() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配连接点的执行方法
this() 限制连接点匹配AOP代理的Bean引用为指定类型的类
target() 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类具备指定指定类型的注解
within() 限制连接点匹配指定的类(类型)
@within() 限制连接点匹配指定注解所标注的类型
@annotation() 限制匹配带有指定注解连接点
  1. execution(* com.ly.ybg.school.querySchool(..))
  2. * -------------------- 返回任意类型
  3. com.ly.ybg.school ----- 方法所属的类(类型)
  4. querySchool ----------- 方法名称
  5. (..) ------------------ 使用任意参数
  6. 多个匹配之间可以使用连接符 && || ! 来表示 且,或,非的关系。
  7. execution(* com.ly.ybg.school.querySchool(...) && within(com.ly.ybg.school.*))
  8. 在切点中选择bean,可以使用
  9. execution(* com.ly.ybg.school.querySchool(...) && bean(girl))
  1. package com.sharpcj.aopdemo.test1;
  2. import org.aspectj.lang.annotation.Aspect;
  3. import org.aspectj.lang.annotation.Before;
  4. import org.springframework.stereotype.Component;
  5. @Aspect
  6. @Component
  7. public class BuyAspectJ {
  8. @Before("execution(* com.ly.ybg.school.IBuy.buy(..)) && within(com.ly.ybg.school.*) && bean(girl)")
  9. public void hehe(){
  10. System.out.println("男孩女孩都买自己喜欢的东西");
  11. }
  12. }
  13. 结果:
  14. 男孩买了一个游戏机
  15. 男孩女孩都买自己喜欢的东西
  16. 女孩买了一件漂亮的衣服

通过注解声明的5种通知类型

@Before 通知方法会在目标方法调用之前执行
@After 通知方法会在目标方法返回或抛出异常后调用
@AfterReturning 通知方法会在目标方法返回后调用
@AfterThrowing 通知方法会在目标方法抛出异常后调用
@Around 通知方法会将目标方法封装起来
  1. package com.ly.ybg.school;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.*;
  4. import org.springframework.stereotype.Component;
  5. @Aspect
  6. @Component
  7. public class BuyAspectJ {
  8. @Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
  9. public void hehe() {
  10. System.out.println("before ...");
  11. }
  12. @After("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
  13. public void haha() {
  14. System.out.println("After ...");
  15. }
  16. @AfterReturning("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
  17. public void xixi() {
  18. System.out.println("AfterReturning ...");
  19. }
  20. @Around("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
  21. public void xxx(ProceedingJoinPoint pj) {
  22. try {
  23. System.out.println("Around aaa ...");
  24. pj.proceed();
  25. System.out.println("Around bbb ...");
  26. } catch (Throwable throwable) {
  27. throwable.printStackTrace();
  28. }
  29. }
  30. }
  31. package com.ly.ybg.school;
  32. import com.ly.ybg.school.Boy;
  33. import com.ly.ybg.school.Girl;
  34. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  35. public class AppTest {
  36. public static void main(String[] args) {
  37. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  38. Boy boy = context.getBean("boy",Boy.class);
  39. Girl girl = (Girl) context.getBean("girl");
  40. boy.buy();
  41. // girl.buy();
  42. }
  43. }
  44. 执行结果:
  45. Around aaa...
  46. before...
  47. 男孩买了一个游戏机
  48. Around bbb...
  49. After...
  50. AfterReturning...

注意: @Around 修饰的环绕通知类型,是将整个目标方法封装起来,在使用时,我们传入 ProceedingJoinPoint 类型的参数,并且需要调用 ProceedingJoinPointproceed() 的方法,该方法其实就是执行目标对象的目标方法。

可以通过使用 @Pointcut 注解声明切点表达式,然后使用表达式简化上面的代码。

  1. package com.sharpcj.aopdemo.test1;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.*;
  4. import org.springframework.stereotype.Component;
  5. @Aspect
  6. @Component
  7. public class BuyAspectJ {
  8. @Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")
  9. public void point(){}
  10. @Before("point()")
  11. public void hehe() {
  12. System.out.println("before ...");
  13. }
  14. @After("point()")
  15. public void haha() {
  16. System.out.println("After ...");
  17. }
  18. @AfterReturning("point()")
  19. public void xixi() {
  20. System.out.println("AfterReturning ...");
  21. }
  22. @Around("point()")
  23. public void xxx(ProceedingJoinPoint pj) {
  24. try {
  25. System.out.println("Around aaa ...");
  26. pj.proceed();
  27. System.out.println("Around bbb ...");
  28. } catch (Throwable throwable) {
  29. throwable.printStackTrace();
  30. }
  31. }
  32. }

通过注解处理通知中的参数

  1. package com.sharpcj.aopdemo.test1;
  2. public interface IBuy {
  3. String buy(double price);
  4. }
  5. package com.sharpcj.aopdemo.test1;
  6. import org.springframework.stereotype.Component;
  7. @Component
  8. public class Girl implements IBuy {
  9. @Override
  10. public String buy(double price) {
  11. System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));
  12. return "衣服";
  13. }
  14. }
  15. package com.sharpcj.aopdemo.test1;
  16. import org.springframework.stereotype.Component;
  17. @Component
  18. public class Boy implements IBuy {
  19. @Override
  20. public String buy(double price) {
  21. System.out.println(String.format("男孩花了%s元买了一个游戏机", price));
  22. return "游戏机";
  23. }
  24. }
  1. package com.sharpcj.aopdemo.test1;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.*;
  4. import org.springframework.stereotype.Component;
  5. @Aspect
  6. @Component
  7. public class BuyAspectJ {
  8. @Pointcut("execution(String com.sharpcj.aopdemo.test1.IBuy.buy(double)) && args(price) && bean(girl)")
  9. public void gif(double price) {
  10. }
  11. @Around("gif(price)")
  12. public String hehe(ProceedingJoinPoint pj, double price){
  13. try {
  14. pj.proceed();
  15. if (price > 68) {
  16. System.out.println("女孩买衣服超过了68元,赠送一双袜子");
  17. return "衣服和袜子";
  18. }
  19. } catch (Throwable throwable) {
  20. throwable.printStackTrace();
  21. }
  22. return "衣服";
  23. }
  24. }

通过注解配置织入方式

注解 @EnableAspectJAutoProxy()
启用Spring AOP的时候,该注解的参数 proxyTargetClass 默认为false。
proxyTargetClass=true 使用的是 cglib 的动态代理方式,这种代理的缺点是如果扩展类的方法被 final 修饰,无法进行织入。
proxyTargetClass=false ,使用的是 jdk 的基于接口的动态代理方式,这个时候,代理会生成一个接口对象,将这个接口对象强制转换为实现该接口的一个类。