切面的魔力:解密Spring AOP 面向切面编程

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 切面的魔力:解密Spring AOP 面向切面编程

一、AOP简介

1.1 什么是AOP ?

       AOP(Aspect-Oriented Programming)是一种软件开发技术,旨在通过将横切关注(cross-cutting concerns)从主要业务逻辑中分离出来,提供更好的模块化和可维护性。AOP通过在程序执行过程中动态地将这些关注点织入到代码中,从而实现了代码的解耦和重用。

1.2 什么是面向切面编程

       面向切面编程(Aspect-Oriented Programming)是AOP的一种具体实现方式。它通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,以切面(Aspect)的形式进行模块化。切面定义了在何处和何时应该应用横切关注点。切面通常由切点(Pointcut)和通(Advice)组成。

1.3 AOP 的特点

  1. 横切关注点的模块化:AOP允许将与业务逻辑无关的横切关注点(如日志记录、事务管理、安全性等)从主要业务逻辑中分离出来,以切面的形式进行统一管理。这样可以提高代码的可维护性,使开发人员能够更好地关注核心业务逻辑。
  2. 解耦和重用:通过将横切关注点从主要业务逻辑中分离出来,AOP实现了代码的解耦。这意味着可以更容易地修改、扩展和重用横切关注点,而无需修改主要业务逻辑。这提高了代码的可重用性和可维护性。
  3. 声明式编程:AOP允许开发人员通过声明式的方式将切面应用到目标对象中,而无需在目标对象的代码中显式地编写切面逻辑。这使得代码更加清晰、简洁,并且易于理解和维护。
  4. 动态织入:AOP允许在程序运行时动态地将切面织入到目标对象中。这意味着可以根据需要选择性地应用切面,而无需在编译时或加载时进行硬编码。这提供了更大的灵活性和可配置性。
  5. 提高系统性能:AOP可以将一些通用的横切关注点(如性能监控、缓存管理等)应用到多个目标对象中,从而提高系统的性能和效率。这避免了在每个目标对象中重复编写相同的代码。

       总的来说,AOP的特点包括横切关注点的模块化、解耦和重用、声明式编程、动态织入以及提高系统性能。这些特点使得AOP成为一种强大的技术,可以提高代码的可维护性、可重用性和可测试性,同时降低代码的复杂性和重复性。

二、 AOP的基本概念解读

2.1 AOP的基本概念

  1. 切面(Aspect):切面是一个模块化的单元,它封装了与横切关注点相关的行为。切面可以包含通知和切点。
  2. 连接点(Join Point):连接点是在应用程序执行过程中可以插入切面的点。它可以是方法调用、方法执行、异常抛出等。(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
  3. 切点(Pointcut):切点定义了在哪些连接点上应用切面。它使用表达式来匹配连接点,例如指定特定的类、方法、注解等。
  4. 通知(Advice):通知是切面在特定连接点上执行的动作。常见的通知类型包括前置通知(Before)、后置通知(After)、返回通知(After Returning)和异常通知(After Throwing)。
  5. 引入(Introduction):引入允许在现有类中添加新的方法和属性。它允许将新功能引入到现有的类中,而无需修改类的源代码。
  6. 织入(Weaving):织入是将切面应用到目标对象中并创建新的代理对象的过程。织入可以在编译时、类加载时或运行时进行。
  7. AOP代理(Proxy):AOP框架创建的对象,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。(代理=目标+通知)    注:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
  8. 目标对象(Target): 包含连接点的对象,被通知(被代理)的对象,完成具体的业务逻辑 。

2.2 AOP 概念趣事解读

       AOP最多的就是概念,面对这么概念我们大多数人是记不住的。特别是像博主这样的越看感觉都要长脑子了,所以我准备一个小故事来解读AOP各功能(术语)的解读。

《 这是一个好故事 》

       小猪侠是一个勇敢而机智的超级英雄,他的使命是保护城市的和平与安全。他有一个特殊的能力,可以通过棒棒糖来赋予他人额外的力量和技能。这个棒棒糖就是它的 " 切面(Aspect)"。

       小猪侠的切面是一个模块化的单元,它封装了与超级英雄相关的行为。他的切面包含了一些 通知(Advice),这些通知定义了在特定的情境(连接点)下给其他超级英雄赋予的额外力量和技能。连接点(Join Point)可以是超级英雄的战斗、救援行动、危机处理等。

       为了将它的切面应用到超级英雄身上,小猪侠需要创建一个 代理(Proxy)。代理是一个中间人,它将超级英雄包装起来,并在必要时调用切面中定义的通知。代理使得超级英雄在执行任务时能够自动获得额外的力量和技能。

       超级英雄们在执行任务时,并不知道小猪侠的切面和代理的存在。他们只需要按照正常的方式进行战斗和救援,而不需要关注切面的实现细节。这就是 目标对象(Target)的作用。目标对象是超级英雄们正常的行动,而切面和代理则为他们提供了额外的力量和技能。

       一天,小猪侠遇到了一个危机,城市中的一座大桥即将坍塌。他需要帮助其他超级英雄一起救援。小猪侠使用他的切面技能,创建了一个前置通知,它会在超级英雄们展开救援行动之前被触发。

       小猪侠将他的切面应用到超级英雄们身上,通过代理来调用他们的救援行动。当超级英雄们准备展开救援行动时,切面中的前置通知被触发,小猪侠使用棒棒糖赋予他们额外的力量和技能,使得他们能够成功救援并修复大桥。

       在这个故事中,小猪侠的切面代表了它的棒棒糖,连接点是超级英雄们展开救援行动的位置,代理是将切面应用到超级英雄们身上的中间人,通知是切面中定义的赋予额外力量和技能的动作,目标对象是超级英雄们正常的行动,织入是将切面应用到目标对象中的过程。

三、代码情景演示

3.1 编写目标对象(超级英雄们正常的行动)

1. 为了降低代码耦合性,首先编写一个动作行为的接口定义发动技能和飞行的方法。

package com.ycxw.aop.biz;
/**
 * @author 云村小威
 * @site blog.csdn.net/Justw320
 * @create 2023-08-17 16:08
 */
public interface Behavior {
  // 飞行
  public boolean fly(String name);
  // 发动技能
  public void skill(String name, String skills);
}

2. 编写实现接口的类

package com.ycxw.aop.biz.impl;
import com.ycxw.aop.biz.Behavior;
/**
 * @author 云村小威
 * @site blog.csdn.net/Justw320
 * @create 2023-08-17 16:15
 */
public class BehaviorImpl implements Behavior {
  public BehaviorImpl() {
    super();
  }
  @Override
  public boolean fly(String name) {
    System.out.println("超级英雄:"+name);
    return true;
  }
  @Override
  public void skill(String name, String skills) {
    System.out.println("超级英雄:"+name+" 发动了"+skills);
  }
}

3.2 编写通知类

通知:是切面中定义的赋予额外力量和技能的动作

3.2.1 前置通知

在目标对象使用前执行:

package com.ycxw.aop.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
 * 前置通知
 * @author 云村小威
 * @site blog.csdn.net/Justw320
 * @create 2023-08-17 16:20
 */
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
        //在这里,可以获取到目标类的全路径及方法及方法参数,然后就可以将他们写到日志表里去
        String target = arg2.getClass().getName();
        String methodName = arg0.getName();
        String args = Arrays.toString(arg1);
        System.out.println("【前置通知:系统日志】:"+target+"."+methodName+"("+args+") 被调用了");
    }
}

3.2.2 后置通知

在目标对象使用完后执行:

package com.ycxw.aop.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
 * 后置通知
 * @author 云村小威
 * @site blog.csdn.net/Justw320
 * @create 2023-08-17 16:48
 */
public class MyAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
        String target = arg3.getClass().getName();
        String methodName = arg1.getName();
        String args = Arrays.toString(arg2);
        System.out.println("【后置通知:】:"+target+"."+methodName+"("+args+") 被调用了,"+"该方法被调用后的返回值为:"+arg0);
    }
}

3.2.3 异常通知

当运行发生异常执行该通知:

package com.ycxw.aop.advice;
import com.ycxw.aop.exception.PriceException;
import org.springframework.aop.ThrowsAdvice;
/**
 * 异常通知
 * @author 云村小威
 * @site blog.csdn.net/Justw320
 * @create 2023-08-17 17:00
 */
public class MyThrowsAdvice implements ThrowsAdvice {
    public void afterThrowing(PriceException ex) {
        System.out.println("【异常通知】:当技能施展发生异常,那么执行此处代码块!!!");
    }
}

3.2.4 环绕通知

这个在平常是用的最多的,比较便捷,相当于结合前置和后置的通知功能。

package com.ycxw.aop.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.util.Arrays;
/**
 * @author 云村小威
 * @site blog.csdn.net/Justw320
 * @create 2023-08-17 16:54
 */
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation arg0) throws Throwable {
        String target = arg0.getThis().getClass().getName();
        String methodName = arg0.getMethod().getName();
        String args = Arrays.toString(arg0.getArguments());
        System.out.println("【环绕通知调用前:】:"+target+"."+methodName+"("+args+")被调用了");
//    arg0.proceed()就是目标对象的方法
        Object proceed = arg0.proceed();
        System.out.println("【环绕通知调用后:】:该方法被调用后的返回值为:"+proceed);
        return proceed;
    }
}

3.3 spring核心xml文件配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--  目标对象  -->
    <bean class="com.ycxw.aop.biz.impl.BehaviorImpl" id="behavior"></bean>
<!-- 通知  -->
    <bean class="com.ycxw.aop.advice.MyMethodBeforeAdvice" id="beforeAdvice"></bean>
    <bean class="com.ycxw.aop.advice.MyAfterReturningAdvice" id="afterAdvice"></bean>
    <bean class="com.ycxw.aop.advice.MyMethodInterceptor" id="interceptor"></bean>
    <bean class="com.ycxw.aop.advice.MyThrowsAdvice" id="throwsAdvice"></bean>
<!--  代理  ProxyFactoryBean类似于工厂模式(直接配置)-->
    <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="behaviorProxy">
    <!--   配置目标对象   -->
        <property name="target" ref="behavior"></property>
        <!--配置代理目标对象的接口 cglid动态代理-->
        <property name="proxyInterfaces">
            <list>
                <value>com.ycxw.aop.biz.Behavior</value>
            </list>
        </property>
        <!--配置通知-->
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>afterAdvice</value>
                <value>interceptor</value>
                <value>throwsAdvice</value>
            </list>
        </property>
    </bean>
</beans>

3.4 测试运行

1. 编写测试类

package com.ycxw.aop.demo;
import com.ycxw.aop.biz.Behavior;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * @author 云村小威
 * @site blog.csdn.net/Justw320
 * @create 2023-08-17 16:08
 */
public class demo1 {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
        Behavior bean = (Behavior) context.getBean("behaviorProxy");
        bean.fly("小威");
        bean.skill("小威","变大技能");
    }
}

2. 运行结果

由此可见,应证了前面的通知。可看到环绕通知跟前置和、后置通知处于等效功能,而环绕通知更为简便。

3.5 配置过滤通知后测试

这里过滤了后置通知:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--AOP-->
    <!--  目标对象  -->
    <bean class="com.ycxw.aop.biz.impl.BehaviorImpl" id="behavior"></bean>
<!-- 通知  -->
    <bean class="com.ycxw.aop.advice.MyMethodBeforeAdvice" id="beforeAdvice"></bean>
    <bean class="com.ycxw.aop.advice.MyAfterReturningAdvice" id="afterAdvice"></bean>
    <bean class="com.ycxw.aop.advice.MyMethodInterceptor" id="interceptor"></bean>
    <bean class="com.ycxw.aop.advice.MyThrowsAdvice" id="throwsAdvice"></bean>
    <!--过滤-->
    <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpAdvisor">
        <!--配置需要过滤的通知-->
        <property name="advice" ref="afterAdvice"></property>
        <!--通过正则过滤指定方法-->
        <property name="pattern" value=".*skill"></property>
    </bean>
<!--  代理  ProxyFactoryBean类似于工厂模式(直接配置)-->
    <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="behaviorProxy">
    <!--   配置目标对象   -->
        <property name="target" ref="behavior"></property>
        <!--配置代理目标对象的接口 cglid动态代理-->
        <property name="proxyInterfaces">
            <list>
                <value>com.ycxw.aop.biz.Behavior</value>
            </list>
        </property>
        <!--配置通知-->
        <property name="interceptorNames">
            <list>
                <value>beforeAdvice</value>
                <value>regexpAdvisor</value>
                <value>throwsAdvice</value>
            </list>
        </property>
    </bean>
</beans>

由运行结果可见,等代码执行完后才执行后置通知,在前面的fly方法后才过滤的后置通知。

总结

       AOP是面向切面编程,它通过在程序执行过程中动态地将横切关注点织入到代码中来实现。当程序执行到目标对象的目标方法时,AOP可以在方法调用前、后或异常抛出时执行相应的通知。

       具体来说,如果连接点上有前置通知,AOP会先执行前置通知,然后再执行目标方法。前置通知可以在目标方法执行之前执行一些预处理操作,如日志记录、参数验证等。

       如果没有前置通知,AOP会直接执行目标方法。然后,AOP会检查目标方法上是否有后置通知。如果有后置通知,AOP会在目标方法执行后执行后置通知。后置通知可以在目标方法执行之后执行一些后处理操作,如结果处理、资源释放等。

       除了前置和后置通知,AOP还支持异常通知和环绕通知。异常通知可以在目标方法抛出异常时执行一些处理逻辑。环绕通知是最强大的通知类型,它可以完全控制目标方法的执行过程,包括在方法调用前后执行自定义的逻辑。

       需要注意的是,虽然通知代码通常是非业务核心代码,如日志记录和事务管理,但并不是所有的通知都是非业务核心代码。有些通知可能涉及到业务逻辑,例如在目标方法执行前进行权限检查。AOP的灵活性和可配置性使得开发人员可以根据具体需求来定义和应用切面,以实现代码的解耦和重用,提高代码的可维护性和可测试性。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
27天前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
AOP(面向切面编程)能够帮助我们在不修改现有代码的前提下,为应用程序添加新的功能或行为。Micronaut框架中的AOP模块通过动态代理机制实现了这一目标。AOP将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,提高模块化程度。在Micronaut中,带有特定注解的类会在启动时生成代理对象,在运行时拦截方法调用并执行额外逻辑。例如,可以通过创建切面类并在目标类上添加注解来记录方法调用信息,从而在不侵入原有代码的情况下增强应用功能,提高代码的可维护性和可扩展性。
49 1
|
18天前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
2天前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
14 2
|
9天前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
45 9
|
3天前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
11 1
|
1天前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
11 0
|
3天前
|
XML Java 数据库连接
【2020Spring编程实战笔记】Spring开发分享~(上)
【2020Spring编程实战笔记】Spring开发分享~
24 0
|
1月前
Micronaut AOP与代理机制:实现应用功能增强,无需侵入式编程的秘诀
【9月更文挑战第9天】AOP(面向切面编程)通过分离横切关注点提高模块化程度,如日志记录、事务管理等。Micronaut AOP基于动态代理机制,在应用启动时为带有特定注解的类生成代理对象,实现在运行时拦截方法调用并执行额外逻辑。通过简单示例展示了如何在不修改 `CalculatorService` 类的情况下记录 `add` 方法的参数和结果,仅需添加 `@Loggable` 注解即可。这不仅提高了代码的可维护性和可扩展性,还降低了引入新错误的风险。
39 13
|
1月前
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
Spring基础3——AOP,事务管理
|
2月前
|
Java Spring XML
掌握面向切面编程的秘密武器:Spring AOP 让你的代码优雅转身,横切关注点再也不是难题!
【8月更文挑战第31天】面向切面编程(AOP)通过切面封装横切关注点,如日志记录、事务管理等,使业务逻辑更清晰。Spring AOP提供强大工具,无需在业务代码中硬编码这些功能。本文将深入探讨Spring AOP的概念、工作原理及实际应用,展示如何通过基于注解的配置创建切面,优化代码结构并提高可维护性。通过示例说明如何定义切面类、通知方法及其应用时机,实现方法调用前后的日志记录,展示AOP在分离关注点和添加新功能方面的优势。
44 0