Spring核心之(AOP)

Spring核心之(AOP)

AOP是什么

AOP(Aspect Oriented Programming,面向切面编程)是一种通过预编译方式和运行期动态代理实现程序功能统一维护的技术。它是OOP(面向对象编程)的延伸,是软件开发中的一个重要研究热点,也是Spring框架的重要组成部分。AOP通过将系统中非核心业务逻辑如事务、日志和安全等进行横向抽取并单独处理,降低业务逻辑各部分之间的耦合度,提升代码的可重用性和开发效率。

AOP作用

  • 提供声明式服务,尤其针对EJB声明式服务的替代方案,其中最重要的是声明式事务管理。
  • 采用横向抽取机制,以减少传统纵向继承体系中的重复性代码。
  • 允许用户自定义切面,完善OOP的使用场景。

Spring AOP采用纯Java实现,无需专门的编译过程和类加载器,在运行时通过代理方式向目标类织入增强代码。此外,AspectJ是一个基于Java语言的AOP框架,从Spring 2.0开始,Spring AOP引入了对AspectJ的支持,通过扩展Java语言提供编译时的横向代码织入(切面)功能。

主要功能

  • 日志记录
  • 性能统计
  • 安全控制
  • 事务处理
  • 异常处理等

主要意图

将日志记录、性能统计、安全控制、事务处理、异常处理等与业务逻辑无关的行为分离出来,使得在不影响业务逻辑代码的前提下独立修改这些行为。

AOP实现原理

AOP底层主要通过代理机制实现,Spring支持两种代理方式:

  • JDK动态代理(Proxy)
  • CGLIB字节码增强

AOP术语【掌握】

  1. Target(目标类):需要被代理的类,例如:UserService
  2. Joinpoint(连接点):可能被拦截的方法,例如:所有方法
  3. Pointcut(切入点):已经被增强的实际连接点,例如:addUser() 方法
  4. Advice(通知/增强):增强代码,例如:前置通知(before)、后置通知(after)
  5. Weaving(织入):将增强advice应用到目标对象target创建新的代理对象proxy的过程
  6. Proxy(代理类):经过织入后的具有增强功能的对象
  7. Aspect(切面):由一个切入点pointcut和相关的多个通知advice组成的一个模块化单元。一个切面可以理解为一个特殊的面,是由一个特定的切入点和相应的通知共同定义的。

image.png

AOP联盟通知类型

AOP联盟为通知(Advice)定义了标准接口org.aopalliance.aop.Advice,Spring按照通知在目标类方法的连接点位置的不同,将其分为以下五类:

  1. 前置通知org.springframework.aop.MethodBeforeAdvice

    • 在目标方法执行前实施增强。
  2. 后置通知org.springframework.aop.AfterReturningAdvice

    • 在目标方法成功执行后实施增强。
  3. 环绕通知org.aopalliance.intercept.MethodInterceptor

    • 在目标方法执行前后均可以实施增强,必须手动调用目标方法。
    try {
        // 前置通知
        // 执行目标方法
        method.invoke(target, args);
        // 后置通知
    } catch (Exception ex) {
        // 抛出异常通知
    }
    
  4. 异常抛出通知org.springframework.aop.ThrowsAdvice

    • 在方法抛出异常后实施增强。
  5. 引介通知org.springframework.aop.IntroductionInterceptor

    • 在目标类中添加新的方法和属性。

(注解)在Boot中使用AOP:

1. 添加依赖

确保在你的pom.xml文件中包含Spring AOP的起步依赖,对于Spring Boot项目,通常你只需要:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

2. 配置AOP

在Spring Boot应用中,默认情况下AOP已经启用。不过,在较早的版本或者需要特定配置时,可以在application.propertiesapplication.yml中开启AOP支持(但通常无需手动配置):

# application.properties
spring.aop.auto=true # 在老版本可能需要,但在较新的Spring Boot版本中默认就是自动启用的

3. 定义切面(Aspect)

创建一个带有@Aspect注解的类来定义横切关注点逻辑。例如:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class LoggingAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 前置通知:方法执行前的日志记录
        System.out.println("Before method execution: " + joinPoint.getSignature());

        // 执行目标方法并获取返回值
        Object result = joinPoint.proceed();

        // 后置通知:方法执行后的日志记录
        System.out.println("After method execution: " + joinPoint.getSignature());

        return result;
    }
}

在上述代码中,我们定义了一个切面类LoggingAspect,其中包含一个环绕通知logAround,它会在匹配到的切入点表达式指定的方法执行前后进行日志记录。

4. 切入点定义

通过@Around注解内的切入点表达式(如execution(* com.example.service.*.*(..)))选择要增强的方法。这个表达式表示对com.example.service包下所有类的所有方法进行拦截。

5. 代理模式

Spring Boot会根据目标类是否实现了接口自动选择代理机制:

  • 如果目标类实现了至少一个接口,则默认使用JDK动态代理。
  • 若目标类没有实现任何接口,则可以通过设置proxyTargetClass=true强制使用CGLIB代理。

在XML配置中,可以通过proxy-target-class属性设置;在Java配置中,可以使用@EnableAspectJAutoProxy(proxyTargetClass=true)注解。

示例Java配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.aop.aspectj.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
    // 其他bean配置...
}

记录用户注册日志案例

若要对UserService类的register方法实现AOP,你需要定义一个切面并精确指定切入点表达式来匹配这个特定的方法。以下是具体步骤:

  1. 首先确保你已经按照上述Spring Boot使用AOP的步骤添加了依赖项和配置。

  2. 创建一个切面(Aspect)类:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class UserServiceLoggingAspect {

    @Around("execution(* com.example.service.UserService.register(..))")
    public Object logRegisterMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        // 前置通知:在注册方法执行前记录日志
        System.out.println("Before registering a user");

        // 执行目标方法并获取返回值
        Object result = joinPoint.proceed();

        // 后置通知:在注册方法执行后记录日志
        System.out.println("After registering a user");

        return result;
    }
}

在这个例子中,我们创建了一个名为UserServiceLoggingAspect的切面,并定义了一个环绕通知logRegisterMethod。切入点表达式为execution(* com.example.service.UserService.register(..)),表示拦截com.example.service.UserService类中的register方法及其所有重载方法。

  1. 确保你的UserService类以及对应的register方法已存在,并且在Spring容器中正确管理。

现在当调用UserServiceregister方法时,将会触发环绕通知,在方法执行前后执行相应的日志记录逻辑。

(XML)半自动方式(不推荐)

通过使用Spring的ProxyFactoryBean来创建代理对象,并从Spring容器中手动获取该代理对象。

编写切面类

  • 需要实现MethodInterceptor接口以提供环绕通知功能。

创建ProxyFactoryBean Bean

  • 规定相关参数:
    • interfaces: 指定与目标类相同的接口列表。
    • target: 指定目标类的引用。
    • interceptorNames: 指定通知切面类的名称。

目标类

public interface UserService {
    void addUser();
    void updateUser();
    void deleteUser();
}

切面类

/**
 * 切面类中定义通知,需要实现不同的接口以规范方法名称。
 * 使用“环绕通知” MethodInterceptor
 */
public class MyAspect implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        System.out.println("前3");
        
        // 手动执行目标方法
        Object obj = mi.proceed();
        
        System.out.println("后3");

        return obj;
    }
}

Spring 配置

<!-- 1. 创建目标类 -->
<bean id="userServiceId" class="com.wstv.b_factory_bean.UserServiceImpl"></bean>

<!-- 2. 创建切面类 -->
<bean id="myAspectId" class="com.wstv.b_factory_bean.MyAspect"></bean>

<!-- 3. 创建代理类 -->
<!-- 使用工厂Bean FactoryBean,底层调用getObject()返回特殊Bean -->
<!-- ProxyFactoryBean用于创建代理工厂Bean,生成特殊代理对象 -->
<bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="interfaces">
        <value>com.wstv.b_factory_bean.UserService</value>
    </property>
    <property name="target" ref="userServiceId"/>
    <property name="interceptorNames">
        <value>myAspectId</value>
    </property>
    
    <!-- 可选配置,强制使用CGLIB -->
    <!-- <property name="optimize" value="true"></property> -->
    <!-- 底层机制说明:
       如果目标类有接口,则采用JDK动态代理;
       如果没有接口,则采用CGLIB字节码增强;
       如果声明 optimize="true",无论是否有接口,都采用CGLIB。 -->
</bean>

测试

@Test
public void demo01() {
 
    // 获取代理类
    UserService userService = (UserService) ApplicationContextUtil.getBean("proxyServiceId");
    userService.addUser();
    userService.updateUser();
    userService.deleteUser();
}

(XML全自动)使用AOP,不推荐

在Spring容器中获取目标类时,如果配置了AOP,Spring将自动生成代理对象。要实现此功能,需要确定目标类、AspectJ切入点表达式,并导入相关jar包:

spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE

编码实现步骤:

  1. 创建目标类(接口和实现类)
  2. 创建通知类(切面),需要实现MethodInterceptor接口,并手动实现方法调用m.proceed()前后加入通知逻辑。
  3. 使用AOP全自动实现代理,通过设置proxy-target-class属性决定使用何种代理机制:
    • proxy-target-class="true"表示使用CGLIB代理(基于继承)
    • proxy-target-class="false"表示使用JDK动态代理(基于接口,默认值)
<aop:config proxy-target-class="true">
    <!-- 切入点表达式 -->
    <aop:pointcut expression="execution(* com.wstv.c_spring_aop.*.*(..))" id="myPointCut"/>
    <!-- 将切面与切入点关联 -->
    <aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/>
</aop:config>

Spring配置

<?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
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 1. 创建目标类 -->
    <bean id="userServiceId" class="com.wstv.c_spring_aop.UserServiceImpl"/>

    <!-- 2. 创建切面类(通知) -->
    <bean id="myAspectId" class="com.wstv.c_spring_aop.MyAspect"/>

    <!-- 3. aop编程配置 -->
    <aop:config proxy-target-class="true">
        <!-- 定义切入点表达式 -->
        <aop:pointcut expression="execution(* com.wstv.c_spring_aop.*.*(..))"
                     id="myPointCut"/>
        <!-- 定义切面,关联通知与切入点 -->
        <aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut"/>
    </aop:config>
</beans>

测试

@Test
public void demo01() {
   
   

    // 获取目标类实例
    UserService userService = ApplicationContextUtil.getBean("userServiceId");
    userService.addUser();
    userService.updateUser();
    userService.deleteUser();
}

原文链接 https://www.hanyuanhun.cn | https://node.hanyuanhun.cn

posted @   汉源魂  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示