[Spring]再谈AOP

  • Post author:
  • Post category:其他



【Spring】AOP_LemmonTreelss的博客-CSDN博客

一、前言

之前简单聊过AOP的一些概念。今天再次深入学习一下。现在大部分项目都是基于微服务框架实现的。比如dubbo、spring cloud。我们作为提供方,负责某一个微服务,这个微服务里会有多个方法,为了不给调用方埋坑。我们针对对每一个对外提供的方法都进行「try …catch…」操作。根据返回码判断正常异常情况。而不是直接把异常信息抛给调用方。

如果每一个对外提供的方法都进行「try …catch…」操作。技术上可行,但是代码比较丑陋,可读性较差。如果改进?

二、AOP概述

可以使用AOP(面向切面)来进行改进。面向对象将程序抽象成各个不同的对象,不同对象各司其职,互不干扰。但是有的时候会出现不同组件(对象)间出现公共的行为。比如每一个方法都进行「try …catch…」。这种场景就可以采用AOP了。在

不修改源代码

的前提下,为系统中不同组件添加通用功能。也就是传说中的无侵入。

AOP采用

横向抽取

机制,取代了

纵向继承体系

重复性代码。

三、AOP详解

1、连接点(Join Point):类里面哪些方法可以被增强,这些方法被称为连接点。比如1个类提供了add、delete、update、select方法。这4个方法都被称为连接点。

2、切入点(Pointcut ):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。比如我只增强add、update方法。实际被增强的方法为切入点

3、增强/通知(Advice):指定这些pointcut被增强的时机,与逻辑。是

切面代码真正被执行的地方

Before Advice: 在 JoinPoints 执行前增强

After Advice: 在 JoinPoints 执行后增强(不管是否抛出异常都会增强)

After returning advice: 在 JoinPoints 执行正常退出后增强(抛出异常则不会被增强)

After throwing advice: 方法执行过程中抛出异常后增强

Around Advice: 这是所有 Advice 中最强大的,它在 JoinPoints 前后都可增强切面代码,也可以选择是否执行原有正常的逻辑,如果不执行原有流程,它甚至可以用自己的返回值代替原有的返回值,甚至抛出异常。在这些 advice 里我们就可以写入切面代码了

举个生活中的例子,一个小饭店里有10个菜。这10个菜就是JoinPoint。而我只选择了锅包肉,锅包肉就是JoinCut。我们可以统计吃锅包肉的时间、可以选择吃锅包肉之前或之后买单。

通过吃锅包肉时间、买单等与

吃锅包肉这个业务动作

解耦,都是统一写在advice的逻辑里。

talk is cheap, show me your code!

public interface TestService {
   // 吃锅包肉
   void eatGuoBaoRou();

   // 吃蘑菇
   void eatMushroom();

   // 吃白菜
   void eatCabbage();
}

@Component
public class TestServiceImpl implements TestService {
   @Override
   public void eatGuoBaoRou() {
       System.out.println("吃锅包肉");
   }

   @Override
   public void eatMushroom() {
       System.out.println("吃蘑菇");
   }

   @Override
   public void eatCabbage() {
       System.out.println("吃白菜");
   }
}



@Aspect
@Component
public class TestAdvice {
   // 1. 定义 PointCut
   @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatGuoBaoRou())")
   private void eatGuoBaoRou(){}

   // 2. 定义应用于 JoinPoint 中所有满足 PointCut 条件的 advice, 这里我们使用 around advice,在其中增强逻辑
   @Around("eatGuoBaoRou()")
   public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable {
       System.out.println("吃锅包肉开始时间");
       //  原来的 TestServiceImpl.eatGuoBaoRou 逻辑,可视情况决定是否执行
       point.proceed();
       System.out.println("吃锅包肉结束时间");
   }
}

吃锅包肉开始时间

吃锅包肉

吃锅包肉结束时间

可以看到通过AOP操作,在吃锅包肉方法前后加上相关逻辑。

原有执行逻辑无侵入

回到开头的例子又该如何解决呢?

PointCut 的 AspectJ pointcut expression language 声明式表达式,这个表达式支持的类型比较全面,可以用正则,注解等来指定满足条件的 joinpoint , 比如类名后加 .*(..) 这样的正则表达式就代表这个类里面的所有方法都会被增强,使用 @annotation 的方式也可以指定对标有这类注解的方法织入代码

1、定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GlobalErrorCatch {

}

2、使用注解

public class TestServiceImpl {
   @GlobalErrorCatch
   public void test() {
        // 此处写服务里的执行逻辑
       
   }
}

3、指定注解形式的pointcut及advice

@Aspect
@Component
public class TestAdvice {
   // 1. 定义所有带有 GlobalErrorCatch 的注解的方法为 Pointcut
   @Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)")
   private void globalCatch(){}
   // 2. 将 around advice 作用于 globalCatch(){} 此 PointCut 
   @Around("globalCatch()")
   public Object handlerGlobalResult(ProceedingJoinPoint point) throws Throwable {
       try {
           return point.proceed();
       } catch (Exception e) {
           System.out.println("执行错误" + e);
           return ServiceResultTO.buildFailed("系统错误");
       }
   }

}

所有标记着 GlobalErrorCatch 注解的方法都会统一在 handlerGlobalResult 方法里执行,我们就可以在这个方法里统一 catch 住异常,所有 service 方法中又长又臭的 「try…catch…」全部干掉

四、AOP的实现原理

动态代理?



版权声明:本文为qq_26545305原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。