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. 使各个部分专注于自己的逻辑。

如有错误,欢迎指正

相关推荐
14L7 小时前
互联网大厂Java面试:从Spring Cloud到Kafka的技术考察
spring boot·redis·spring cloud·kafka·jwt·oauth2·java面试
地藏Kelvin7 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
一个有女朋友的程序员7 小时前
Spring Boot 缓存注解详解:@Cacheable、@CachePut、@CacheEvict(超详细实战版)
spring boot·redis·缓存
wh_xia_jun8 小时前
在 Spring Boot 中使用 JSP
java·前端·spring boot
yuren_xia9 小时前
在Spring Boot中集成Redis进行缓存
spring boot·redis·缓存
yuren_xia9 小时前
Spring Boot + MyBatis 集成支付宝支付流程
spring boot·tomcat·mybatis
我爱Jack10 小时前
Spring Boot统一功能处理深度解析
java·spring boot·后端
RainbowJie112 小时前
Spring Boot 使用 SLF4J 实现控制台输出与分类日志文件管理
spring boot·后端·单元测试
面朝大海,春不暖,花不开12 小时前
Spring Boot MVC自动配置与Web应用开发详解
前端·spring boot·mvc
发愤图强的羔羊12 小时前
SpringBoot异步导出文件
spring boot·后端