AOP:面向切面编程,相对于OOP面向对象编程
Spring的AOP的存在目的是为了解耦。AOP可以让一组类共享相同的行为。在OOP中只能继承和实现接口,且类继承只能单继承,阻碍更多行为添加到一组类上,AOP弥补了OOP的不足。
还有就是为了清晰的逻辑,让业务逻辑关注业务本身,不用去关心其它的事情,比如事务。
Spring的AOP是通过JDK的动态代理和CGLIB实现的。

一、AOP的术语:

aop 有一堆术语,非常难以理解,简单说一下

  • 通知(有的地方叫增强)(Advice)
    需要完成的工作叫做通知,就是你写的业务逻辑中需要比如事务、日志等先定义好,然后需要的地方再去用
  • 连接点(Join point)
    就是spring中允许使用通知的地方,基本上每个方法前后抛异常时都可以是连接点
  • 切点(Poincut)
    其实就是筛选出的连接点,一个类中的所有方法都是连接点,但又不全需要,会筛选出某些作为连接点做为切点。如果说通知定义了切面的动作或者执行时机的话,切点则定义了执行的地点
  • 切面(Aspect)
    其实就是通知和切点的结合,通知和切点共同定义了切面的全部内容,它是干什么的,什么时候在哪执行
  • 引入(Introduction)
    在不改变一个现有类代码的情况下,为该类添加属性和方法,可以在无需修改现有类的前提下,让它们具有新的行为和状态。其实就是把切面(也就是新方法属性:通知定义的)用到目标类中去
  • 目标(target)
    被通知的对象。也就是需要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。
  • 织入(Weaving)把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:
    • 编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器
    • 类加载期:切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码
    • 运行期:切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的。

例:

  1. public class UserService{
  2. void save(){}
  3. List list(){}
  4. ....
  5. }

在UserService中的save()方法前需要开启事务,在方法后关闭事务,在抛异常时回滚事务。
那么,UserService中的所有方法都是连接点(JoinPoint),save()方法就是切点(Poincut)。需要在save()方法前后执行的方法就是通知(Advice),切点和通知合起来就是一个切面(Aspect)。save()方法就是目标(target)。把想要执行的代码动态的加入到save()方法前后就是织入(Weaving)。
有的地方把通知称作增强是有道理的,在业务方法前后加上其它方法,其实就是对该方法的增强。

二、常用AOP通知(增强)类型

  • before(前置通知): 在方法开始执行前执行
  • after(后置通知): 在方法执行后执行
  • afterReturning(返回后通知): 在方法返回后执行
  • afterThrowing(异常通知): 在抛出异常时执行
  • around(环绕通知): 在方法执行前和执行后都会执行

    三、执行顺序

    around > before > around > after > afterReturning

四、先说一下SpringAop非常霸道又用的非常少的功能 —引入(Introduction)

  1. 配置类
  1. @Aspect
  2. @Component
  3. public class IntroductionAop {
  4. @DeclareParents(value = "com.jiuxian..service..*", defaultImpl = DoSthServiceImpl.class)
  5. public DoSthService doSthService;
  6. }
  1. service代码
  1. public interface DoSthService {
  2. void doSth();
  3. }
  4. @Service
  5. public class DoSthServiceImpl implements DoSthService {
  6. @Override
  7. public void doSth() {
  8. System.out.println("do sth ....");
  9. }
  10. }
  11. public interface UserService {
  12. void testIntroduction();
  13. }
  14. @Service
  15. public class UserServiceImpl implements UserService {
  16. @Override
  17. public void testIntroduction() {
  18. System.out.println("do testIntroduction");
  19. }
  20. }
  1. 测试代码
  1. @Test
  2. public void testIntroduction() {
  3. userService.testIntroduction();
  4. //Aop 让UserService方法拥有 DoSthService的方法
  5. DoSthService doSthService = (DoSthService) userService;
  6. doSthService.doSth();
  7. }
  1. 结果
  1. do testIntroduction
  2. do sth ....

五、五种通知(增强)代码实现

  1. 配置类

(1) 对方法

  1. @Aspect
  2. @Component
  3. public class TransactionAop {
  4. @Pointcut("execution(* com.jiuxian..service.*.*(..))")
  5. public void pointcut() {
  6. }
  7. @Before("pointcut()")
  8. public void beginTransaction() {
  9. System.out.println("before beginTransaction");
  10. }
  11. @After("pointcut()")
  12. public void commit() {
  13. System.out.println("after commit");
  14. }
  15. @AfterReturning("pointcut()", returning = "returnObject")
  16. public void afterReturning(JoinPoint joinPoint, Object returnObject) {
  17. System.out.println("afterReturning");
  18. }
  19. @AfterThrowing("pointcut()")
  20. public void afterThrowing() {
  21. System.out.println("afterThrowing afterThrowing rollback");
  22. }
  23. @Around("pointcut()")
  24. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  25. try {
  26. System.out.println("around");
  27. return joinPoint.proceed();
  28. } catch (Throwable e) {
  29. e.printStackTrace();
  30. throw e;
  31. } finally {
  32. System.out.println("around");
  33. }
  34. }
  35. }

(2) 对注解

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. public @interface Log {
  4. String value() default "";
  5. }
  1. @Aspect
  2. @Component
  3. public class AnnotationAop {
  4. @Pointcut(value = "@annotation(log)", argNames = "log")
  5. public void pointcut(Log log) {
  6. }
  7. @Around(value = "pointcut(log)", argNames = "joinPoint,log")
  8. public Object around(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
  9. try {
  10. System.out.println(log.value());
  11. System.out.println("around");
  12. return joinPoint.proceed();
  13. } catch (Throwable throwable) {
  14. throw throwable;
  15. } finally {
  16. System.out.println("around");
  17. }
  18. }
  19. }
  20. @Before("@annotation(com.jiuxian.annotation.Log)")
  21. public void before(JoinPoint joinPoint) {
  22. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  23. Method method = signature.getMethod();
  24. Log log = method.getAnnotation(Log.class);
  25. System.out.println("注解式拦截 " + log.value());
  26. }
  1. service 方法实现
  1. public interface UserService {
  2. String save(String user);
  3. void testAnnotationAop();
  4. }
  5. @Service
  6. public class UserServiceImpl implements UserService {
  7. @Override
  8. public String save(String user) {
  9. System.out.println("保存用户信息");
  10. if ("a".equals(user)) {
  11. throw new RuntimeException();
  12. }
  13. return user;
  14. }
  15. @Log(value = "test")
  16. @Override
  17. public void testAnnotationAop() {
  18. System.out.println("testAnnotationAop");
  19. }
  20. }
  1. 测试类
  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class SpringbootAopApplicationTests {
  4. @Resource
  5. private UserService userService;
  6. @Test
  7. public void testAop1() {
  8. userService.save("张三");
  9. Assert.assertTrue(true);
  10. }
  11. @Test
  12. public void testAop2() {
  13. userService.save("a");
  14. }
  15. @Test
  16. public void testAop3() {
  17. userService.testAnnotationAop();
  18. }
  19. }
  1. 结果
  • 执行testAop1时
  1. around
  2. before beginTransaction
  3. 保存用户信息
  4. around
  5. after commit
  6. afterReturning :: 张三
  • 执行testAop2时
  1. around
  2. before beginTransaction
  3. 保存用户信息
  4. around
  5. after commit
  6. afterThrowing rollback
  • 执行testAop3时
  1. test
  2. around
  3. testAnnotationAop
  4. around
  1. pom文件
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-aop</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-test</artifactId>
  12. <scope>test</scope>
  13. </dependency>

六、最常用的execution解释

例: execution( com.jiuxian..service..*(..))

  • execution 表达式的主体
  • 第一个* 代表任意的返回值
  • com.jiuxian aop所横切的包名
  • 包后面.. 表示当前包及其子包
  • 第二个* 表示类名,代表所有类
  • .*(..) 表示任何方法,括号代表参数 .. 表示任意参数

    例: execution( com.jiuxian..service.Service.add*(String))

表示: com.jiuxian 包及其子包下的service包下,类名以Service结尾,方法以add开头,参数类型为String的方法的切点。

七、特别的用法

  1. @Pointcut("execution(public * *(..))")
  2. private void anyPublicOperation() {}
  3. @Pointcut("within(com.xyz.someapp.trading..*)")
  4. private void inTrading() {}
  5. @Pointcut("anyPublicOperation() && inTrading()")
  6. private void tradingOperation() {}

可以使用 &&, ||, ! 运算符来定义切点

八、更多详细介绍请参阅官网

SpringAOP官网介绍

九、本文示例代码

GitHub 源码