Spring学习笔记-AOP

在学习 AOP 前,你需要对代理模式有一定了解,因为Spring AOP 的底层实现是基于代理模式的,关于代理模式可以参考我的这篇笔记。

设计模式-代理模式

1. 什么是 AOP

AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。是对面向对象编程 OOP 的补充,是函数式编程的一种衍生泛型。

2. AOP 概念

AOP 在 Spring Framework 中用于:

  • 提供声明式企业服务。最重要的此类服务是 声明式事务管理
  • 让用户实现自定义方面,用 AOP 补充他们对 OOP 的使用。

2.1 名词解释

  • 横切关注点:跨越应用程序多个模块的方法或功能。即与业务逻辑无关的,但是需要我们关注的部分,如日志,安全,缓存,事务等等。
  • 切面(Aspect):跨多个类的关注点的模块化对象。即,它是一个类。
  • 连接点(JointPoint):程序执行过程中的一个点,例如方法的执行或异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
  • 通知(Advice):被通知的对象。
  • 切入点(PointCut):匹配连接点的谓词。Advice 与切入点表达式相关联,并在与切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。
  • 目标(Target):一个或多个方面被通知的对象。也称为“被通知对象”。由于 Spring AOP 是使用运行时代理实现的,因此该对象始终是代理对象。
  • 代理(Proxy):由 AOP 框架创建的对象,用于实现方面协定(建议方法执行等)。在 Spring Framework 中,AOP 代理是 JDK 动态代理或 CGLIB 代理。
  • 编织(Weaving):将方面与其他应用程序类型或对象链接以创建建议对象。这可以在编译时(例如,使用 AspectJ 编译器)、加载时或运行时完成。Spring AOP 与其他纯 Java AOP 框架一样,在运行时执行编织。

2.2 通知类型种类

  • 前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
    • ApplicationContext 中在 < aop:aspect > 里面使用 < aop:before > 元素进行声明;
    • Spring 接口:org.springframework.aop.AfterReturningAdvice
  • 后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
    • ApplicationContext 中在 < aop:aspect > 里面使用 < aop:after > 元素进行声明。
    • Spring 接口:org.springframework.aop.AfterAdvice
  • 返回后通知(After Returning advice :在某连接点正常完成后执行的通知,不包括抛出异常的情况。
    • ApplicationContext 中在 < aop:aspect > 里面使用 << after-returning >> 元素进行声明。
    • Spring 接口:org.springframework.aop.AfterReturningAdvice
  • 环绕通知(Around advice):包围一个连接点的通知,类似 Web 中 Servlet规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。
    • ApplicationContext 中在 < aop:aspect > 里面使用 < aop:around > 元素进行声明。
    • Spring 接口:org.springframework.aop.MethodInterceptor
  • 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
    • ApplicationContext 中在 < aop:aspect > 里面使用 < aop:after-throwing > 元素进行声明。
    • org.springframework.aop.ThrowsAdvice

3. AOP 实现

下面我们用不同的方法实现一个简单的AOP例子:在方法调用前后打印日志。

3.1 导入依赖包

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9.1</version>
</dependency>

如果程序运行时报 BeanCreationException 错误,先检查是否没有导入该包再去排查其他错误原因

3.2 方法一:Spring 接口实现

  1. 编写日志类分别实现 MethodBeforeAdvice 接口和 AfterReturningAdvice 接口
public class BeforeLog implements MethodBeforeAdvice  {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("execute " + method.getName() + " before");
    }
}
public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("execute " + method.getName() + " after");
    }
}
  1. 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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <bean id="userService" class="com.zhao.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.zhao.log.BeforeLog"/>
    <bean id="afterLog" class="com.zhao.log.AfterLog"/>

    <aop:config>
        <!--切入点-->
        <aop:pointcut id="pointcut" expression="execution(* com.zhao.service.UserServiceImpl.*(..))"/>
        <!--执行环绕增强!-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>
  1. 测试代码
public class App{
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        // 错误写法 UserServiceImpl userService =  ctx.getBean("userService", UserServiceImpl.class);
        UserService userService = (UserService) ctx.getBean("userService");
        
        userService.add();
    }
}

注意:这里很容易把第中间那行代码写成下面错误形式(UserService 是接口,UserServiceImpl 是实现类),程序会报Bean named 'userService' is expected to be of type 'com.zhao.service.UserServiceImpl' but was actually of type 'com.sun.proxy.$Proxy3'错误,这是因为这里通过aop获得的是代理类,不是实现类。

UserServiceImpl userService =  ctx.getBean("userService", UserServiceImpl.class);
  1. 运行结果
execute add before
add
execute add after

3.3 方法二:自定义切面实现

  1. 编写自定义切面类

    public class Log {
        public void before() {
            System.out.println("before");
        }
    
        public void after() {
            System.out.println("after");
        }
    }
    
  2. 配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd">
        
        <bean id="log" class="com.zhao.log.Log"/>
        <aop:config>
            <!--自定义切面-->
            <aop:aspect ref="log">
                <!--切入点-->
                <aop:pointcut id="pointcut" expression="execution(* com.zhao.service.UserServiceImpl.*(..))"/>
                <!--通知-->
                <aop:before method="before" pointcut-ref="pointcut"/>
                <aop:after method="after" pointcut-ref="pointcut"/>
            </aop:aspect>
        </aop:config>
    </beans>
    
  3. 测试代码同上不变

3.4 方法三:注解实现

  1. 注解标注切面和方法

    @Component
    @Aspect
    public class AnnotationLog {
        @Before("execution(* com.zhao.service.UserServiceImpl.*(..))")
        public void before() {
            System.out.println("before");
        }
    
        @After("execution(* com.zhao.service.UserServiceImpl.*(..))")
        public void after() {
            System.out.println("before");
        }
    }
    
  2. 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"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
       
        <!--组件扫描-->
        <context:component-scan base-package="com.zhao.*"/>
        <context:annotation-config/>
        <!--启用 @AspectJ 支持-->
        <aop:aspectj-autoproxy/>
    </beans>
    
  3. 输出为

    环绕前
    void com.zhao.service.UserService.add()
    before
    add
    before
    环绕后
    

3.5 切换JDK和CGLIB

<aop:aspectj-autoproxy proxy-target-class="false"/> // 默认为JDK
<aop:aspectj-autoproxy proxy-target-class="true"/>  // CGLIB

相关面试题

  1. 解释下什么是 AOP?

  2. AOP 的代理有哪几种方式?

  3. 怎么实现 JDK 动态代理?

  4. AOP 的基本概念:切面、连接点、切入点等?

  5. 通知类型(Advice)型(Advice)有哪些?

posted @   油虾条  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示