AOP 的简单样例

什么是 AOP ?

面向切面编程,AOP 它能够做什么?

下面以一个例子,来说名AOP的流程及优点。

以下代码在 github.com/hzbobby/Jav...


故事背景

你正在筹办一场表演/演唱会,你请了著名歌手 ------ JayChou 来演唱一首歌曲。JayChou 只需要关注在演出开始时,专心唱这首歌就好了。而你需要在演出前置办好场地,布置舞台,演出结束时收拾舞台,针对演出的突发状况进行合理处理等。这些事情是 JayChou 不用管理的。

首先看,我们怎么处理演出部分的。

现在我们有一个演出通用接口

java 复制代码
public interface IPerformance {
    void perform();
}

JayChou 来表演了

java 复制代码
public class JayChouPerformance implements IPerformance {
    @Override
    public void perform() {
        System.out.println("JayChou: oh, 能不能给我一首歌的时间...");
    }
}

你作为话事人,需要在 JayChou 演出前后做一些事情,你可以这样。通过依赖注入的方式,注入你需要的演出。

java 复制代码
@Data
public class ShowManager implements IPerformance{
    private IPerformance whoPerformance;

    @Override
    public void perform() {
        System.out.println("话事人: 布置舞台...");
        whoPerformance.perform();
        System.out.println("话事人: 收拾舞台...");
    }
}

我们在单元测试中,进行调用

java 复制代码
@SpringBootTest
public class AspectUnitTest {

    @Resource
    JayChouPerformance jayChouPerformance;

    @Test
    public void performShows() {
        ShowManager showManager = new ShowManager();
        showManager.setWhoPerformance(jayChouPerformance);
        showManager.perform();
    }
}

// 话事人: 布置舞台...
// JayChou: oh, 能不能给我一首歌的时间...
// 话事人: 收拾舞台...

如果现在要求你置办 Eason 的演出,且前置和后置步骤都是一样的,你依然可以这样做。只需要将 Eason 的演出注入即可。这个系统依然能满足要求

现在,你已经对置办演出有着非常丰富的经验,你已经是一个成熟的置办演出的系统了。

下面有新的要求过来了。

要求二:这次演出是一次公益演出,在演出开始之前,我们要进行一场公益慈善演出。我们要求,你不能侵入已有的演出系统。并且在演出开始前后,需要进行公益活动的准备。

要求三:Boss 觉得你的能力非常突出,现在把发布会组织,会议组织...活动都交给你来做。同样,在不侵入原有代码下,你该怎么做呢?

上面的要求是,不侵入原有代码,从而实现在各种活动前后执行一些操作。如果我们有很多活动都要执行一些固定的操作,或者部分活动需要操作1,部分活动需要操作2,该如何处理。示意图,如下。

OK,下面我们正式介绍如何利用 SpringAOP 来实现上述操作。


Spring AOP

参考:

www.baeldung.com/spring-aop

juejin.cn/post/684490...

docs.spring.io/spring-fram...

AOP 是一种编程范式,旨在通过分离横切关注点来提高模块化。它通过在现有代码中添加额外行为而不修改代码本身来实现这一点

相反,我们可以分别声明新代码和新行为。

下面先来了解一下几个相关概念

  • JoinPoint 连接点 / 加入点:这是一个抽象且宽泛的概念,它表示能够加入切面的位置,例如方法执行时,异常时等。
  • PointCut 切点:进一步缩小连接点的范围(或切面的范围),例如我们指定在某些类的某个方法上切入。切点是为了缩小切面所通知的连接点的范围,即切面在何处执行。我们通常使用明确的类和方法名称,或者利用正则表达式定义所匹配的类和方法名称来指定切点。
  • Advice 通知 :表示我们的切面可以在一个函数的哪里执行,例如函数执行开始,结束,返回,异常时。
    • 前置通知(Before):在目标方法被调用之前调用通知功能
    • 后置通知(After):在目标方法完成之后调用通知,此时不关心方法的输出结果是什么
    • 返回通知(After-returning):在目标方法成功执行之后调用通知
    • 异常通知(After-throwing):在目标方法抛出异常后调用通知
    • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
  • Aspect 切面切面是通知和切点的结合。通知和切点共同定义了切面的全部内容:它是什么,在何时和何处完成其功能。在切面里面可以定义:在切点x处通知y做了什么。
  • Inctroduction 引入:引入允许我们在不修改现有类的基础上,向现有类添加新方法或属性。
  • Weaving 织入 :织入是把切面应用到目标对象并创建新的代理对象 的过程。
    • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
    • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。
    • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

下面画一幅简单的示意图,来帮助理解上述过程


AOP 改造

要求二:这次演出是一次公益演出,在演出开始之前,我们要进行一场公益慈善演出。我们要求,你不能侵入已有的演出系统。并且在演出开始前后,需要进行公益活动的准备。

OK,现在着手开始我们的改造。

我们的目的是,在 perform() 前后进行一些操作。根据上述示意图,我们需要在 perform() 进行连接。

那么,首先我们先定义一个切面

java 复制代码
@Aspect
public class ShowAspect {
    // 在里面定义 切点 和 切面操作
}

接着,我们的切面只需要对实现 IPerformance 的实现类进行切入,例如,我们把相关的实现类都放在同一个 package 里面,那么我们可以这样定义切点

java 复制代码
@Aspect
public class ShowAspect {
    // 在里面定义 切点 和 切面操作
    @Pointcut("execution(* com.bobby.aspect.shows.*.perform(..))")
    public void perform() {}
}

接下来,我们要将通知与切点结合。这里我们加了前置/后置和返回通知

java 复制代码
@Aspect
public class ShowAspect {
    // 在里面定义 切点 和 切面操作
    @Pointcut("execution(* com.bobby.aspect.shows.IPerformance.perform(..))")
    public void perform() {}

    @Before("perform()")
    public void performBefore(){
        System.out.println("Before perform");
    }

    @After("perform()")
    public void performAfter(){
        System.out.println("After perform");
    }

    @AfterReturning("perform()")
    public void performAfterReturning(){
        System.out.println("After perform returning");
    }
}

接下来,我们将我们定义的切面类注册为 bean 和 所用到的 IPerformance 注册为 Bean

java 复制代码
@Component
public class AspectConfig {
    @Bean
    public ShowAspect showAspect(){
        return new ShowAspect();
    }
}

OK,我们进行测试一下。

注意到,我们的切点是接口 IPerformance 的 perform方法。因此实现了该接口的方法都会被 Weaving 生成一个代理类。

java 复制代码
@SpringBootTest
public class AspectUnitTest {

    @Resource
    JayChouPerformance jayChouPerformance;
    @Resource
    SleepNoMoreDrama sleepNoMoreDrama;

    @Test
    public void performShows() {
        ShowManager showManager = new ShowManager();
        showManager.setWhoPerformance(jayChouPerformance);
        showManager.perform();
    }
}

// 话事人: 布置舞台...
// Before perform
// JayChou: oh, 能不能给我一首歌的时间...
// After perform returning
// After perform
// 话事人: 收拾舞台...

OK,这样子我们就实现了,不侵入原有代码,

要求三:Boss 觉得你的能力非常突出,现在把发布会组织,会议组织...活动都交给你来做。同样,在不侵入原有代码下,你该怎么做呢?

OK,那现在 Boss 又要求你去组织一下活动,在活动前后要做一些相关事宜。我们可以通过定义一个活动切点,然后定义相关切面即可。


总结

  1. 定义切入点。找到需要切入的位置
  2. 定义切面:通知与切入点相结合。我们需要什么通知,在这些通知的位置,我们需要做什么
  3. 把Aspect类注册为 Bean,把切点相关类注册为 Bean (这样才能动态代理这些类)

AOP 编程的优点

  1. 不侵入原有代码
  2. 方便进行拓展
  3. 使各个部分专注于自己的逻辑。

如有错误,欢迎指正

相关推荐
橘猫云计算机设计几秒前
基于Java的班级事务管理系统(源码+lw+部署文档+讲解),源码可白嫖!
java·开发语言·数据库·spring boot·微信小程序·小程序·毕业设计
计算机-秋大田9 分钟前
基于Spring Boot的个性化商铺系统的设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计
小马爱打代码38 分钟前
Spring Boot - 动态编译 Java 类并实现热加载
spring boot·后端
天草二十六_简村人3 小时前
Rabbitmq消息被消费时抛异常,进入Unacked 状态,进而导致消费者不断尝试消费(下)
java·spring boot·分布式·后端·rabbitmq
程序员小刚3 小时前
基于SpringBoot + Vue 的汽车租赁管理系统
vue.js·spring boot·汽车
低头不见3 小时前
Spring Boot 的启动流程
java·spring boot·后端
计算机-秋大田3 小时前
基于Spring Boot的消防物资存储系统的设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计
计算机-秋大田4 小时前
基于Spring Boot的乡村养老服务管理系统的设计与实现(LW+源码+讲解)
java·spring boot·后端
天草二十六_简村人5 小时前
Rabbitmq消息被消费时抛异常,进入Unacked 状态,进而导致消费者不断尝试消费(上)
java·spring boot·分布式·后端·rabbitmq