Spring AOP面向切面编程


Spring AOP (Aspect-Oriented Programming) 是 Spring 框架的核心模块之一,用于处理 横切关注点(Cross-Cutting Concerns),如日志、事务、安全等。它通过代理机制将通用功能模块化,避免代码重复,提高可维护性

核心概念

1.切面 (Aspect) :通过 @Aspect 注解定义
封装横切关注点的模块(如日志工具类)

2.连接点 (Join Point) :程序执行过程中的一个点,比如方法的执行或异常的处理
在Spring AOP中,连接点总是表示方法的执行

//切面类
public class LoggingAspect {
    // 前置通知  连接点:JoinPoint    这段就表示在哪个执行方法前执行
    public void beforeAdvice(JoinPoint jp) {
        System.out.println("[前置] 方法: " + jp.getSignature().getName());
    }

3.切入点 (Pointcut)表达式 :是expression表达式,定义规范哪些连接点 (哪些方法)会被拦截的表达式,常用表达式:

	// 拦截 service 包下所有方法
	//expression=表达式 execution(访问权限 返回值类型 包路径.方法名(参数))
     @Pointcut("execution(* com.example.demo.aop.UserService.*(..))") 
     //这个表达式 意思是 所以在com....UserService方法都纳入 切面管理拦截范围
     //这是通配符表示 当然也可以指定是哪个 比如com....UserService.say()

4.通知 (Advice)
切面在连接点执行的动作,分五种类型:
@Before:前置通知,方法执行前
@After:后置通知,方法结束后(无论成功/异常)
@AfterReturning:返回通知,方法正常返回后
@AfterThrowing:异常通知,方法抛出异常后
@Around:环绕通知(可控制方法执行)

5.目标对象 (Target Object)
在AOP(面向切面编程)中,目标对象是指那些我们真正希望执行业务逻辑的对象

//目标接口UserService 
public interface UserService {}
//目标接口的实现类
public class UserServiceImpl implements UserService {}

// 而程序真正执行业务逻辑的对象是UserServiceImpl,它才是目标对象
// 关键点 看真正执行业务逻辑方法的对象是哪个
<bean id="userService" class="com.example.UserServiceImpl"/>

这个bean就是目标对象,它实现了UserService接口。在AOP中,目标对象就是被代理的对象,也就是我们想要增强的对象

6.织入 (Weaving)
切面应用到目标对象的过程,Spring AOP 在运行时通过动态代理实现

实现方式

1. 基于 XML 配置(JDK动态代理模式)

前置 后置 返回 异常通知

// 目标对象的接口
public interface UserService {
    void testException();
    String getUser(int id);
}
//目标对象
public class UserServiceImpl implements UserService {
    @Override
    public String getUser(int id) {
        System.out.println("执行UserServiceImpl.getUser方法 获取用户ID: " + id);
        return "User_" + id;
    }
        return "User_" + id;
    }
    @Override
    public void testException() {
        System.out.println("===模拟异常testException方法===");
         //模拟异常
         int i = 1/0;
    }
}

//切面类
public class LoggingAspect {
    // 前置通知
    public void beforeAdvice(JoinPoint jp) {
        System.out.println("[前置] 方法: " + jp.getSignature().getName());
    }

    // 后置通知(无论是否异常都执行)
    public void afterAdvice(JoinPoint jp) {
        System.out.println("[后置] 清理资源: " + jp.getSignature().getName());
    }

    // 返回通知
    public void afterReturningAdvice(JoinPoint jp, Object result) {
        System.out.println("[返回] 结果: " + result);
    }

    // 异常通知
    public void afterThrowingAdvice(JoinPoint jp, Throwable ex) {
        System.out.println("[异常] 错误: " + ex.getMessage());
    }
}

配置xml文件

 <!-- 1. 定义目标对象 -->
 <bean id="userService" class="com.example.demo.aop.UserServiceImpl"/>

 <!-- 2. 定义切面类(需要被代理的目标类)-->
 <bean id="loggingAspect" class="com.example.demo.aop.LoggingAspect"/>
       
  <!-- 3. 配置AOP -->
 <aop:config>
     <!-- 定义切点表达式  execution(访问权限 返回值类型 包路径.方法名(参数))-->
     <aop:pointcut id="serviceMethods"
             expression="execution(* com.example.demo.aop.UserService.*(..))"/>

     <!-- 配置切面 = 通知类型+ 切入点-->
    <aop:aspect ref="loggingAspect">
        <!-- 前置通知 -->
        <aop:before method="beforeAdvice" pointcut-ref="serviceMethods"/>

        <!-- 后置通知 -->
        <aop:after method="afterAdvice" pointcut-ref="serviceMethods"/>

        <!-- 返回通知 -->
       <aop:after-returning method="afterReturningAdvice"
                                 pointcut-ref="serviceMethods"
                                 returning="result"/>
       <!-- 异常通知 -->
       <aop:after-throwing method="afterThrowingAdvice"
                                pointcut-ref="serviceMethods"
                                throwing="ex"/>
        
        </aop:aspect>
    </aop:config>

测试类

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
        UserService userService = (UserService)ac.getBean("userService");
        try {
            // 正常流程测试
            System.out.println("===正常流程测试===");
            userService.getUser(100);
            //模拟异常
            System.out.println("===模拟异常流程测试===");
            userService.testException();
        } catch (Exception e) {
            System.out.println("-----");
            System.out.println("主程序捕获异常: " + e.getMessage());
        }
    }

打印输出

ClassPathXmlApplicationContext - Refreshing  //初始读取
ClassPathXmlApplicationContext@182decdb //创建启动
//读取到需要加载的有8个bean
XmlBeanDefinitionReader - Loaded 8 bean definitions from resource [aop.xml]

//DefaultListableBeanFactory是Spring框架中的一个核心类,它实现了BeanFactory接口
//它的主要职责包括实例化、定位、配置这些对象以及建立它们之间的依赖关系
//而bean实例创建顺序 是递归依赖关系由底下而上的顺序创建bean实例
org.springframework.beans.factory.support.DefaultListableBeanFactory 

//internalAutoProxyCreator是Spring AOP框架中的一个内部类,主要负责创建AOP代理
//简单说就是对那些需要被AOP代理的Bean进行拦截,并为其创建代理对象
DefaultListableBeanFactory - Creating singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
//创建目标对象 UserServiceImpl 的bean实例
 <bean id="userService" class="com.example.demo.aop.UserServiceImpl"/>
DefaultListableBeanFactory - Creating singleton bean 'userService'

//这个AspectJPointcutAdvisor bean是Spring AOP框架内部使用的一个组件
//用于将切面(Aspect)中的通知(Advice)与切点(Pointcut)关联起来
//每一个这样的Advisor代表一个具体的切面通知(如@Before)和它对应的切入点表达式
//也就是<!-- 配置切面 = 通知类型+ 切入点表达式--> 这也就是所谓织入过程
DefaultListableBeanFactory - Creating singleton bean
'org.springframework.aop.aspectj.AspectJPointcutAdvisor#0'
'org.springframework.aop.aspectj.AspectJPointcutAdvisor#1'
'org.springframework.aop.aspectj.AspectJPointcutAdvisor#2'
'org.springframework.aop.aspectj.AspectJPointcutAdvisor#3'

//最后创建切面类loggingAspect的bean实例(它是依赖最多的 最后 创建)
DefaultListableBeanFactory - Creating singleton bean 'loggingAspect'
//当所以bean实例创建完成后,织入过程也就完成了 所有方法执行顺序已经安排明白了

===正常流程测试===
[前置] 方法: getUser  //在执行方法前执行
执行UserServiceImpl.getUser方法 获取用户ID: 100  //执行目标方法getUser
[后置] 清理资源: getUser // 执行完方法后 执行后置通知
[返回] 结果: User_100 //返回结果后 执行返回通知
===模拟异常流程测试===
[前置] 方法: testException //在执行方法前执行
===模拟异常testException方法=== //执行目标方法testException
[后置] 清理资源: testException  // testException方法都异常被捕获了 后置通知还是执行了
[异常] 错误: / by zero  //在发生异常时候捕获 执行异常通知
-----
主程序捕获异常: / by zero

环绕通知

环绕通知可以包裹目标方法执行,也就是可以同时包含另外四种通知
上面目标接口UserService 和实现类UserServiceImpl 保持不变,切面类添加环绕通知方法

    // 环绕通知
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("=========环绕通知========");
        try {
            // 1. 前置通知
            System.out.println("[前置] 方法: " + pjp.getSignature().getName());
            // 2. 执行目标方法
            Object result = pjp.proceed();
            // 3. 返回通知逻辑
            System.out.println("[返回] 结果: " + result);
            // 4. 后置通知(无论是否异常都执行)
            System.out.println("[后置] 清理资源: " + pjp.getSignature().getName());
            return result;

        } catch (Throwable ex) {
            // 5. 异常通知逻辑
            System.out.println("[异常] 错误: " + ex.getMessage());
            // 6. 后置通知(异常也要执行后置 所以环绕通知如果设置后置通知 也要放在这里一个)
            System.out.println("[后置] 异常清理: " + pjp.getSignature().getName());
            throw new RuntimeException(ex); // 重新抛出异常
        }
    }

配置文件

        <!-- 配置切面 =切入点+通知-->
        <aop:aspect ref="loggingAspect">
            <!-- 环绕通知 -->
            <aop:around method="aroundAdvice" pointcut-ref="serviceMethods"/>
        </aop:aspect>
    </aop:config>

测试打印输出

===正常流程测试===
=========环绕通知========
[前置] 方法: getUser
执行UserServiceImpl.getUser方法 获取用户ID: 100
[返回] 结果: User_100
[后置] 清理资源: getUser
===模拟异常流程测试===
=========环绕通知========
[前置] 方法: testException
===模拟异常testException方法===
[异常] 错误: / by zero
[后置] 异常清理: testException
-----
主程序捕获异常: java.lang.ArithmeticException: / by zero

注意:当同时使用环绕通知和其他通知时
执行顺序为:环绕前 -> 前置 -> 目标方法 -> 环绕后 -> 返回/异常 -> 后置

这里我们使用的是JDK动态代理(默认):当目标对象实现接口时使用

2. 基于XML配置(CGLIB动态代理模式)

如果目标类 (未实现接口),则使用CGLIB动态代理模式

1.创建目标类(无接口)

public class User {
    public void createUser(String username) {
        System.out.println("创建用户: " + username);
    }
    public void deleteUser(String username) {
        System.out.println("删除用户: " + username);
    }
}

2.方法拦截器

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
// MethodInterceptor 核心拦截接口,所有方法调用都会路由到这里
public class LoggingInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("[CGLIB代理] 开始拦截: " 
        						+ invocation.getMethod().getName());
        System.out.println("参数: " + Arrays.toString(invocation.getArguments()));

        // 执行原始方法
        Object result = invocation.proceed();

        System.out.println("[CGLIB代理] 方法执行完成");
        return result;
    }
}

配置文件

    <!-- 1. 定义目标对象 -->
    <bean id="user" class="com.example.demo.aop.User" />
    
    <!-- 2. 定义拦截器 -->
    <bean id="loggingInterceptor" class="com.example.demo.aop.LoggingInterceptor" />
    
    <!-- 3. 配置CGLIB代理 -->
    <!-- 代理对象 userServiceProxy-->
    <bean id="userServiceProxy" 
    			<!-- 基于spring的工厂类ProxyFactoryBean 由它生成代理类对象-->
    			class="org.springframework.aop.framework.ProxyFactoryBean">
        
        <!-- 当设置proxyTargetClass=true时,强制使用CGLIB代理-->
        <property name="proxyTargetClass" value="true" />
        
        <!-- 指定目标对象 -->
        <property name="target" ref="user" />
        
        <!-- 指定拦截器 interceptorNames拦截器列表 
        		interceptorNames字段可以当标签字段使用 特定的-->
        <property name="interceptorNames">
            <list>
                <!-- 这里引用了上面定义的拦截器bean -->
                <value>loggingInterceptor</value>      <!-- 日志记录 -->
            </list>
        </property>
    </bean>

测试类

    public static void main(String[] args) {
        // 加载Spring配置
        ApplicationContext context = 
        		new ClassPathXmlApplicationContext("aopCglib.xml");

        // 获取代理对象 注意不是目标对象 是配置文件里面定义的 代理对象啊
        User userProxy = (User) context.getBean("userServiceProxy");

        // 调用方法
        userProxy .createUser("张三");
        userProxy .deleteUser("李四");
    }

打印输出

//这些日志也可以看出整个cgLib下的执行流程

// 读取配置文件加载 
ClassPathXmlApplicationContext - Refreshing 
ClassPathXmlApplicationContext@4b4523f8 //容器对象
//发现 3个需要创建示例
XmlBeanDefinitionReader - Loaded 3 bean definitions from [aopCglib.xml]
// 目标对象user
Creating shared instance of singleton bean 'user'
// 拦截器对象loggingInterceptor
Creating shared instance of singleton bean 'loggingInterceptor'
//代理对象userServiceProxy
DefaultListableBeanFactory - Creating instance of singleton bean 'userServiceProxy'

//核心 生成代理对象由它执行方法,并同时织入 拦截啊 日志啊什么的这些方法
// 执行的方式用proxy.invokeSuper(target, args)
//由于代理类是继承目标对象的,所以可以向上 直接引用调用父类方法
// 具体的业务执行逻辑 还是目标对象执行的,至于为什么这么设计这点在 下面CGLIB机制总结讲

[CGLIB代理] 开始拦截: createUser
参数: [张三]
创建用户: 张三
[CGLIB代理] 方法执行完成

[CGLIB代理] 开始拦截: deleteUser
参数: [李四]
删除用户: 李四
[CGLIB代理] 方法执行完成

3. 基于注解(推荐)

这里我们展示一下环绕通知里附带四种的方式
1.目标类(无接口)

@Service //表示业务层 就是具体执行业务逻辑的类
public class UserService {
    public String createUser(String username) {
        System.out.println("创建用户: " + username);
        return "用户:"+username;
    }
    public void testException() {
        System.out.println("===模拟异常testException方法===");
        //模拟异常
        int i = 1/0;
    }
}

2.切面类(定义代理逻辑)

@Aspect //标识一个类为切面类
@Component //标注一个类为Spring容器的Bean
public class LoggingAspect {

    // @Pointcut定义切点表达式:execution()里面匹配UserService中的所有方法
    @Pointcut("execution(* com.example.demo.aop.annotate.UserService.*(..))")
    public void userServiceMethods() {}

    // 环绕通知(可控制是否执行目标方法)
    @Around("userServiceMethods()")
    public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("【CGLIB代理 环绕通知】开始");
        //ProceedingJoinPoint环绕通知参数 连接点:表示执行的具体方法
        //方法名:joinPoint.getSignature().getName()
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        System.out.println("【CGLIB代理 前置通知】开始拦截方法: " + methodName);
        System.out.println("参数: " + java.util.Arrays.toString(args));
        try {
            // 执行目标方法
            Object result = joinPoint.proceed();
            System.out.println("【CGLIB代理 后置通知】目标方法执行完成");
            // 如果有返回值,打印返回值
            if (result != null) {
                System.out.println("[CGLIB代理 返回通知]返回值: " + result);
            }
            return result;
        } catch (Exception e) {
            System.out.println("【CGLIB代理 异常通知】方法执行异常: " + e.getMessage());
            System.out.println("【CGLIB代理 后置通知】目标方法异常且执行完成");
            throw e;
        }
    }
}

3.配置类

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration //作用:定义类为配置类 相当于xml
@EnableAspectJAutoProxy(proxyTargetClass = true) // 关键配置:强制使用CGLIB代理
@ComponentScan(basePackages = "com.example.demo.aop.annotate") //扫描包路径
public class AppConfig {

    // 不需要其他配置,Spring会自动扫描@Component、@Service等注解
}

4.测试应用程序

public class Application {

    public static void main(String[] args) {
        // 创建Spring容器(基于注解配置)
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        // 获取代理对象 这点很重要啊 是代理对象啊
        UserService userService = context.getBean(UserService.class);

        // 检查代理类型
        System.out.println("代理对象类型: " + userService.getClass().getName());
        try {
            // 调用方法(将被代理拦截)
            System.out.println("\n=== 调用 createUser ===");
            userService.createUser("张三");

            System.out.println("\n=== 调用模拟异常测试 ===");
            userService.testException();

        }catch (Exception e){
            System.out.println("-----");
            System.out.println("主程序捕获异常: " + e.getMessage());
        }
    }
}

日志打印

=== 调用 createUser ===
【CGLIB代理 环绕通知】开始
【CGLIB代理 前置通知】开始拦截方法: createUser
参数: [张三]
创建用户: 张三
【CGLIB代理 后置通知】目标方法执行完成
[CGLIB代理 返回通知]返回值: 用户:张三

=== 调用模拟异常测试 ===
【CGLIB代理 环绕通知】开始
【CGLIB代理 前置通知】开始拦截方法: testException
参数: []
===模拟异常testException方法===
【CGLIB代理 异常通知】方法执行异常: / by zero
【CGLIB代理 后置通知】目标方法异常且执行完成
-----
主程序捕获异常: / by zero

Process finished with exit code 0

这里顺便看下打印的流程日志

//启动Spring容器:注解扫描配置项 并启动
annotation.AnnotationConfigApplicationContext - Refreshing 
annotation.AnnotationConfigApplicationContext@156643d4

2.注册内部Bean处理器  - 创建配置注解处理器(处理`@Configuration`,`@Bean`等注解)
//由DefaultListableBeanFactory开始处理这些Bean实例
//创建internalConfigurationAnnotationProcessor的Bean实例
DefaultListableBeanFactory -Creating singleton bean —
			'....internalConfigurationAnnotationProcessor'
//internalConfigurationAnnotationProcessor 负责解析配置类并注册相应的BeanDefinition

3.扫描组件 扫描到两个候选组件:`LoggingAspect`(切面类)和`UserService`(目标类)
//注解扫描类扫描需要被注册成bean的类
annotation.ClassPathBeanDefinitionScanner - 
Identified candidate component class:
file [...LoggingAspect.class]
file [...UserService.class]

4.创建其他内部Bean
//由DefaultListableBeanFactory开始处理这些Bean实例
org.springframework.beans.factory.support.DefaultListableBeanFactory - 
//internalEventListenerProcessor 建事件监听处理器
Creating singleton bean '......event.internalEventListenerProcessor'
Creating singleton bean '......event.internalEventListenerFactory'
//自动注入处理器内部Bean
Creating singleton bean '.....internalAutowiredAnnotationProcessor'
Creating singleton bean '.....internalCommonAnnotationProcessor'

5.创建AOP自动代理创建器
//internalCommonAnnotationProcessor对需要被AOP代理的Bean进行拦截,并为其创建代理对象
Creating singleton bean '......config.internalAutoProxyCreator'

6.创建配置类实例 appConfig的bean
Creating singleton bean 'appConfig'

7.发现AspectJ通知方法 在`LoggingAspect`类中发现了AspectJ环绕通知方法
//ReflectiveAspectJAdvisorFactory职责是从配置中解析切面
//它负责创建和管理切面(advice)和切入点(pointcut)的映射关系
org...aspectj.annotation.ReflectiveAspectJAdvisorFactory - 
Found AspectJ method: //找到AspectJ方法
LoggingAspect.logMethodExecution(ProceedingJoinPoint) throws java.lang.Throwable

8.创建切面Bean
DefaultListableBeanFactory - Creating singleton bean 'loggingAspect'

9.创建目标对象BeanUserServiceDefaultListableBeanFactory - Creating singleton bean 'userService'

10.创建代理对象
在`AppConfig`类上用了`@EnableAspectJAutoProxy(proxyTargetClass=true)`
这个注解引入了`AspectJAutoProxyRegistrar`
它会向容器注册一个`AnnotationAwareAspectJAutoProxyCreator`的bean实例 
这个bean是对如果匹配符合配置了AspectJ范围的的bean
调用`BeanPostProcessor`:bean实例初始化后置处理方法
它在bean初始化后置处理中,会检查当前bean是否与任何切点匹配
如果匹配,则会为这个bean创建代理对象

步骤是
 在我们的测试类中`Application`类的`main`方法中:
 ApplicationContext context = 
 			new AnnotationConfigApplicationContext(AppConfig.class);
当执行这行代码时,Spring容器开始初始化。
初始化过程:
 1. 解析配置类`AppConfig`。
 2. 扫描`com.example`包下的所有组件(包括`UserService`和`LoggingAspect`)。
 3. 创建并注册所有检测到的bean定义。
 4. 然后,Spring容器会实例化单例bean(非懒加载的)。
在实例化`UserService`这个bean时
Spring会通过`AnnotationAwareAspectJAutoProxyCreator`这个`BeanPostProcessor`
对`UserService`进行处理。具体过程如下:
1.Spring容器创建`UserService`的原始实例(target)后,
	会调用`BeanPostProcessor`的`postProcessAfterInitialization`方法。
2.在`postProcessAfterInitialization`方法中,`AnnotationAwareAspectJAutoProxyCreator`
  会检查是否有切面通知(advice)适用于这个bean
 (即检查所有切面的切点表达式是否匹配`UserService`中的方法)
  @Pointcut("execution(* com.example.demo.aop.annotate.UserService.*(..))")
  自然包含UserService中方法 
3.即匹配,则会创建一个代理对象。由于我们设置了`proxyTargetClass=true`,
因此会使用CGLIB生成代理。这个代理对象是`UserService`的子类,
并织入了切面逻辑(即`LoggingAspect`中定义的环绕通知)

4.最后,容器中实际使用的`UserService` bean就是这个代理对象,而不是原始的`UserService`实例
@Aspect
@Component
public class LoggingAspect {

    // 定义切点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    // 前置通知
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before: " + joinPoint.getSignature().getName());
    }

    // 环绕通知(可修改返回值)
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Start: " + joinPoint.getSignature());
        Object result = joinPoint.proceed(); // 执行目标方法
        System.out.println("End: " + joinPoint.getSignature());
        return result;
    }
}
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>

<aop:config>
    <aop:aspect ref="loggingAspect">
        <aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>
        <aop:before method="logBefore" pointcut-ref="serviceMethods"/>
    </aop:aspect>
</aop:config>

代理机制

在对目标对象需要增强时候,如果目标对象实现了接口使用JDK动态代理模式,这也是默认的方式,如果目标对象未实现接口使用CGLIB代理 详情解释点击连接

CGLIB 代理

1.目标对象

public class UserService {
    public void createUser(String username) {
        System.out.println("创建用户: " + username);
    }
    public void deleteUser(String username) {
        System.out.println("删除用户: " + username);
    }
}

2.拦截器方法

public class LoggingInterceptor implements MethodInterceptor {
    //需要拦截的方法列表
    private final Set<String> interceptedMethods;
    public LoggingInterceptor() {
    	// 配置文件放入需要拦截的方法 intercepted.methods=createUser,deleteUser
        try (InputStream input = getClass()
        		.getClassLoader().getResourceAsStream("proxy-config.properties")) {
            Properties prop = new Properties();
            prop.load(input);

            // 从配置读取需要拦截的方法列表
            String methods = prop.getProperty("intercepted.methods", "");
            interceptedMethods = new HashSet<>(Arrays.asList(methods.split(",")));
        } catch (Exception e) {
            throw new RuntimeException("加载配置文件失败", e);
        }
    }
    @Override
    public Object intercept(
    			Object obj, // 目标对象
    			Method method, //执行方法
    			Object[] args, //执行方法参数
    			MethodProxy proxy) // 方法代理执行:表示要触发父类的方法对象
    			throws Throwable {
        String methodName = method.getName();

        // 检查当前方法是否需要拦截
        if (interceptedMethods.contains(methodName)) {
            System.out.println("[CGLIB代理] 开始拦截: " + methodName);
            System.out.println("参数: " + Arrays.toString(args));
            //调用目标方法执行(invokeSuper调用原始父类方法)
            Object result = proxy.invokeSuper(obj, args); 
            System.out.println("[CGLIB代理] 方法执行完成");
            return result;
        }

        // 不需要拦截的方法直接执行
        return proxy.invokeSuper(obj, args);
    }
}
这里注意下,执行目标方法(比如目标类中的createUser方法) 有两种方式
methodProxy.invoke(obj, args)
methodProxy.invokeSuper(obj, args)

代理对象继承目标对象,自然包括其非final的方法 字段 和其余属性
如用`methodProxy.invoke(target, args)`,则执行的是我们传入的目标对象(原始对象)的方法
如用`methodProxy.invokeSuper(proxy, args)`,则执行的是代理对象中继承自父类(目标类)的方法

但是需要注意的是 如果使用methodProxy.invoke(target, args) 即代理对象处理会出现问题,我们都是直接调用父类的方法执行,具体原因在下面总结里面讲

3.生成代理对象

public class ProxyFactory {
    public static <T> T createProxy(Class<T> targetClass) {
        System.out.println("===进入ProxyFactory生成代理类流程===");
        //1.创建增强器Enhancer对象(CGLib的核心类)
        Enhancer enhancer = new Enhancer();
        //设置超类:目标对象为父类(需要被代理的类)
        enhancer.setSuperclass(targetClass); // 设置父类(目标类)
        //设置回调(方法拦截器)
        enhancer.setCallback(new LoggingInterceptor());
        // 创建代理对象
        return (T) enhancer.create();

    }
}

4.测试应用类

public class Main {
    public static void main(String[] args) {
        // 使用工厂类生成代理对象:创建CGLIB代理对象
        UserService proxy = ProxyFactory.createProxy(UserService.class);
        // 调用方法(将被拦截)
        proxy.createUser("张三");
        // 调用方法(未被拦截)
        proxy.deleteUser("李四");
    }
}

CGLIB 核心实现原理

按照应用测试类步骤讲解
1.使用工厂类生成代理对象:创建CGLIB代理对象,进入工厂类生成代理对象方法

    public static <T> T createProxy(Class<T> targetClass) {
        System.out.println("===进入ProxyFactory生成代理类流程===");
        //1.创建增强器Enhancer对象(CGLib的核心类)
        Enhancer enhancer = new Enhancer();
        //2.设置超类:目标对象为父类(需要被代理的类)
        enhancer.setSuperclass(targetClass); // 设置父类(目标类)
        //3.设置回调属性(即代理对象 拥有方法拦截器 属性)
        enhancer.setCallback(new LoggingInterceptor());
        // 创建代理对象
        return (T) enhancer.create();

在第三步中生成代理对象之前进入Callback回调方法进入
生成新的LoggingInterceptor拦截器对象 给到个包含拦截哪些方法范围

重点啊 会重写MethodInterceptor接口的intercept方法
MethodInterceptor接口的核心作用,所有调用方法都会路由到这里
也就是即将生成的代理类所有调用的方法 都会转而调用重写的intercept方法

然后生成代理对象返回

在第2步,调用代理对象执行方法proxy.createUser(“张三”);
当调用代理对象的方法时,实际上调用的是子类(代理类)中重写的方法。在子类重写的方法中,会调用我们预先设定的MethodInterceptor的intercept方法

//代理类执行createUser方法转而调用设置的MethodInterceptor的intercept方法
Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) 
方法参数
obj:目标对象target :userService
method:第一次当前执行的方法:creatUser
args:方法参数 张三
proxy:代理执行方法creatUser,这个是即将生成的代理对象的方法 由父类那边继承来的

然后在这里进行拦截处理逻辑

在之后处理目标执行方法creatUser
问题是现在 这个方法执行方式有两种
1.原始目标类对象可以执行
2.代理子类从目标对象继承了一份 他可以执行

为什么选择使用目标类执行方式:methodProxy.invokeSuper(obj, args)
两种使用有什么区别:
关键点:拦截器类的作用域
真正实现拦截功能的是在 回调的MethodInterceptor的intercept方法
这个方法是在生成代理对象 增强器类 给即将生成的代理对象设置的回调属性
        //3.设置回调属性(即代理对象 拥有方法拦截器 属性)
        enhancer.setCallback(new LoggingInterceptor());
也就是说 只有代理对象才会回调进入拦截方法 而父类目标对象不会

如果使用.invokeSuper()方法 是调用父类目标对象执行方法 然后不会进入拦截 直接执行结束

如果使用.invoke()则是进入代理对象自身执行 目标方法,那么问题来了 会进入拦截方法
然后再次调用进入intercept方法 ,再走到.invoke()方法 又进入拦截....
是不是就死锁这里出不来了,最终结果就是 溢出报错

所以这里使用要慎重

总结:CGLIB的核心 就是对目标对象生成 一个带有回调拦截方法的代理对象,由代理子类执行目标方法进入拦截方法里,然后再拦截方法中调用父类目标对象方法同时,织入需要拦截的方法内容,比如打印 过滤等等

JDK 动态代理

1.目标对象有 接口

//1. 接口
public interface UserService {
    void saveUser();
}
// 2. 实现类
public class UserServiceImpl implements UserService {
    public void saveUser() {
        System.out.println("保存用户");
    }
}

2.拦截器 类

public class LogInvocationHandler implements InvocationHandler {
    //目标对象
    private final Object target;

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    //参数:目标对象 目标执行方法 执行方法参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[LOG] 方法调用: " + method.getName());
        return method.invoke(target, args);
    }
}

3.测试应用类

public class Main {
    public static void main(String[] args) {
    	//目标对象 注意啊 生成的对象是接口userService  但具体的实例对象是UserServiceImpl
        UserService userService = new UserServiceImpl();
		
		//生成代理类
        UserService proxy = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(),
                new Class[]{UserService.class},
                new LogInvocationHandler(userService));
		// 代理对象执行方法
        proxy.saveUser();
    }
}
参数说明
loader: 类加载器用 去加载代理对象
interfaces:被动态代理的类 需要实现的接口
h:InvocationHandler(调用处理程序类) 

工作流程
1.在运行时动态生成代理类(类名格式:$ProxyN2.代理类实现指定的接口
3.所有方法调用转发到 InvocationHandler.invoke()
4.invoke() 方法中实现代理对象执行具体业务逻辑
(包括拦截和织入需要执行的内容 执行目标方法)

JDK 核心实现原理

总结:JDK动态代理 就是接口不能实例化,给个具体的子类实例化代理,由代理类执行,只是生成这个代理类时候给它加上个 回调拦截的属性,所有代理类执行的方法都会回调到.invoke()这,然后再这个方法里 织入拦截功能 打印日志啊 等,然后调用method.invoke(target, args);执行目标方法

Aspects 和 AOP 的区别

在Spring框架的核心模块中 AOP模块和 Aspects 模块作用很相似,两者的主要区别是:

1.AOP(面向切面编程)是一种编程范式,更相当于一种设计思想,允许模块化横切关注点
2. Aspect(切面)是AOP中的模块化单元,它封装了横切关注点(如日志记录、事务管理等)

简单说,AOP是编程范式思想,而Aspect是这种设计思路具体实现单元

从定义、角色和技术实现三个维度理解:

🔍 一、定义与本质区别

概念AOP (面向切面编程)Aspect (切面)
性质一种编程范式(类似OOP),解决代码横切关注点AOP中的核心模块单元,封装横切逻辑(如日志、安全)
目标提升代码模块化,分离业务逻辑与非核心功能(如事务、日志)具体实现AOP目标的技术组件
层级架构级设计思想实现AOP的具体技术构件

关键总结:AOP是方法论(“做什么”),Aspect是方法论中的工具(“如何做”)。

⚙️ 二、在AOP技术中的角色
AOP框架(如Spring AOP或AspectJ)通过以下核心概念实现功能,其中Aspect是核心载体:

1.Join Point(连接点)
程序执行的特定位置(如方法调用、异常抛出)。Spring AOP仅支持方法执行连接点,而AspectJ支持字段访问、构造方法等更丰富的连接点。

2.Pointcut(切点)
通过表达式(如execution(* com.service.*.*(..)))定义哪些连接点需被拦截。

3.Advice(通知)
切面在连接点执行的具体逻辑,分为:

  • @Before(前置通知)
  • @After(后置通知)
  • @Around(环绕通知,可控制目标方法是否执行)。

4.Aspect(切面)
封装了Pointcut和Advice的实体类,例如:

   @Aspect
   public class LoggingAspect {
       @Pointcut("execution(* *Service.*(..))")
       public void serviceMethods() {}
       
       @Before("serviceMethods()")
       public void logBefore(JoinPoint jp) {
           System.out.println("执行方法: " + jp.getSignature());
       }
   }

5.Weaving(织入)
将切面逻辑嵌入目标代码的过程:

  • Spring AOP:运行时通过动态代理(JDK Proxy/CGLIB)织入。
  • AspectJ:支持编译期、加载期等更高效的织入方式。

🔄 三、AOP不同实现中Aspect的差异

特性Spring AOPAspectJ
Aspect实现普通Java类(@Aspect注解)同Spring,但功能更强大
织入方式运行时动态代理编译期/加载期直接修改字节码
连接点支持仅方法执行方法、构造器、字段访问等全部连接点
性能代理调用有额外开销无运行时代理,性能高8-35倍
依赖需Spring容器管理Bean可独立使用,无需容器

典型场景

  • Spring AOP:适合轻量级事务管理(如@Transactional)。
  • AspectJ:需拦截私有方法或静态方法时(如安全审计)。

💎 总结

  • AOP 是解决横切关注点的编程范式,核心目标是解耦。
  • Aspect 是AOP范式的实现单元,包含通知(Advice)和切点(Pointcut)。
  • 技术选型
    • 若仅需拦截Spring Bean中的方法 → Spring AOP(简单易用)。
    • 若需拦截final类、静态方法或极致性能 → AspectJ(功能完整)。

二者本质是整体与部分的关系:AOP是设计思想,Aspect是落地工具。正如城市需要规划(AOP),而规划需具体设施(Aspect)支撑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值