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.创建目标对象Bean(UserService)
DefaultListableBeanFactory - 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.在运行时动态生成代理类(类名格式:$ProxyN)
2.代理类实现指定的接口
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 AOP | AspectJ |
---|---|---|
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)支撑。