Spring AOP 原理深度解析

一、什么是 AOP?

1.1 AOP 基本概念

AOP(Aspect-Oriented Programming,面向切面编程)是 OOP(面向对象编程)的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,但当需要为多个不具有继承关系的对象引入同一个公共行为时,OOP 就会显得力不从心。

AOP 的核心思想:将横切关注点(如日志、事务、安全等)从业务逻辑中分离出来,实现关注点的分离。

1.2 AOP 解决的问题

  • 代码分散:横切关注点(如日志)的代码分散在多个方法中

  • 代码混乱:业务逻辑中混杂着横切关注点的代码

  • 代码重复:相同的横切逻辑需要在多个地方重复编写

二、AOP 核心概念详解

2.1 基本术语

java

// 切面(Aspect):横切关注点的模块化 @Aspect @Component public class LoggingAspect { // 切点(Pointcut):匹配连接点的表达式 @Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayer() {} // 通知(Advice):在特定连接点执行的动作 @Before("serviceLayer()") public void logBefore(JoinPoint joinPoint) { // 增强逻辑 } }

核心概念解析

  1. 切面(Aspect):横切关注点的模块化,在 Spring AOP 中通常用@Aspect注解标记

  2. 连接点(Joinpoint):程序执行过程中的某个特定点,如方法调用、异常抛出等

  3. 切点(Pointcut):匹配连接点的谓词表达式,确定在哪些连接点应用通知

  4. 通知(Advice):在切点处执行的动作,分为:

    • 前置通知(Before):在方法执行前执行

    • 后置通知(After):在方法执行后执行(无论是否异常)

    • 返回通知(AfterReturning):方法成功执行后执行

    • 异常通知(AfterThrowing):方法抛出异常后执行

    • 环绕通知(Around):包围连接点,可以控制是否执行连接点

  5. 目标对象(Target Object):被一个或多个切面通知的对象

  6. AOP 代理(AOP Proxy):由 AOP 框架创建的对象,用于实现切面契约

  7. 织入(Weaving):将切面应用到目标对象创建代理对象的过程

三、Spring AOP 的实现原理

3.1 核心实现机制

Spring AOP 主要使用两种方式实现动态代理:

3.1.1 JDK 动态代理(基于接口)

适用场景:目标类实现了接口

java

// JDK 动态代理示例 public class JdkDynamicProxyDemo { public interface UserService { void save(); } public static class UserServiceImpl implements UserService { public void save() { System.out.println("保存用户"); } } public static class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置通知"); Object result = method.invoke(target, args); System.out.println("后置通知"); return result; } } public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHandler(target) ); proxy.save(); } }
3.1.2 CGLIB 动态代理(基于子类)

适用场景:目标类没有实现接口

java

// CGLIB 动态代理示例 public class CglibProxyDemo { public static class UserService { public void save() { System.out.println("保存用户"); } } public static class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前置通知"); Object result = proxy.invokeSuper(obj, args); System.out.println("后置通知"); return result; } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserService.class); enhancer.setCallback(new MyMethodInterceptor()); UserService proxy = (UserService) enhancer.create(); proxy.save(); } }

3.2 Spring AOP 代理选择策略

java

// Spring 中的代理创建逻辑(简化版) public class DefaultAopProxyFactory implements AopProxyFactory { @Override public AopProxy createAopProxy(AdvisedSupport config) { // 1. 如果配置了优化或直接指定使用CGLIB,则使用CGLIB if (config.isOptimize() || config.isProxyTargetClass()) { return new CglibAopProxy(config); } // 2. 如果目标对象有接口,使用JDK动态代理 Class<?>[] interfaces = config.getProxiedInterfaces(); if (interfaces.length > 0) { return new JdkDynamicAopProxy(config); } // 3. 否则使用CGLIB return new CglibAopProxy(config); } }

四、Spring AOP 工作流程深度解析

4.1 整体架构

text

1. 解析 @Aspect 注解 ↓ 2. 构建 Advisor(通知器) ↓ 3. 创建代理工厂(ProxyFactory) ↓ 4. 选择代理策略(JDK/CGLIB) ↓ 5. 生成代理对象 ↓ 6. 方法调用时执行拦截链

4.2 源码级执行流程

4.2.1 代理对象的创建过程

java

// AbstractAutoProxyCreator 是关键类 public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { @Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 判断是否需要创建代理 if (!this.earlyProxyReferences.contains(cacheKey)) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 1. 如果已经是代理对象或不需要代理,直接返回 if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } // 2. 检查是否需要跳过 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } // 3. 判断是否是基础类或需要跳过的类 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // 4. 获取适用的通知器(Advisor) Object[] specificInterceptors = getAdvicesAndAdvisorsForBean( bean.getClass(), beanName, null); // 5. 如果有适用的通知器,则创建代理 if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } }
4.2.2 方法调用拦截过程

java

// JdkDynamicAopProxy 的 invoke 方法 public class JdkDynamicAopProxy implements AopProxy, InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Object target = null; try { // 1. 检查是否是 equals、hashCode 等方法 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { return equals(args[0]); } if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { return hashCode(); } if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; // 2. 如果设置了 exposeProxy,将当前代理暴露到 ThreadLocal if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); // 3. 获取拦截器链 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice( method, targetClass); // 4. 如果没有拦截器,直接执行原方法 if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // 5. 创建方法调用对象,并执行拦截器链 invocation = new ReflectiveMethodInvocation( proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } // 6. 处理返回值 Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); } return retVal; } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); } if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); } } } }

4.3 拦截器链的执行

java

// ReflectiveMethodInvocation 的 proceed 方法 public class ReflectiveMethodInvocation implements ProxyMethodInvocation { private int currentInterceptorIndex = -1; @Override public Object proceed() throws Throwable { // 1. 如果所有拦截器都执行完毕,执行原始方法 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); } // 2. 获取下一个拦截器 Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); // 3. 如果是动态方法匹配器,先进行运行时检查 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { return dm.interceptor.invoke(this); } else { // 不匹配时跳过当前拦截器 return proceed(); } } else { // 4. 执行拦截器 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } } }

五、Spring AOP 的切点表达式详解

5.1 常用切点表达式

java

@Aspect @Component public class PointcutExamples { // 1. 方法执行切点 @Pointcut("execution(public * com.example.service.*.*(..))") public void publicServiceMethods() {} // 2. 指定包下的所有方法 @Pointcut("within(com.example.service..*)") public void inServiceLayer() {} // 3. 实现特定接口的类 @Pointcut("this(com.example.service.UserService)") public void thisUserService() {} // 4. 目标对象类型 @Pointcut("target(com.example.service.UserServiceImpl)") public void targetUserServiceImpl() {} // 5. 参数匹配 @Pointcut("args(java.lang.String,..)") public void stringFirstParam() {} // 6. 注解匹配 @Pointcut("@annotation(com.example.annotation.Log)") public void logAnnotation() {} // 7. 带有特定注解的类 @Pointcut("@within(org.springframework.stereotype.Service)") public void serviceAnnotation() {} // 8. 带有特定注解的方法参数 @Pointcut("@args(com.example.annotation.Valid)") public void validArgsAnnotation() {} // 9. Bean 名称匹配 @Pointcut("bean(userService)") public void userServiceBean() {} }

5.2 切点表达式组合

java

@Aspect @Component public class CombinedPointcuts { // 组合切点:AND、OR、NOT @Pointcut("execution(* com.example.service.*.*(..)) && within(com.example.service..*)") public void serviceLayerExecution() {} // 复杂的组合 @Pointcut("execution(* com.example.service.*.*(..)) && " + "!execution(* com.example.service.*.get*(..))") public void serviceMethodsExceptGetters() {} }

六、Spring AOP 的几种通知类型实现

6.1 通知类型的实现原理

java

// 以环绕通知为例,展示如何实现 public class AspectJAroundAdvice implements MethodInterceptor, Serializable { private final AspectJExpressionPointcut pointcut; private final transient Method aspectJAdviceMethod; private final AspectInstanceFactory aspectInstanceFactory; @Override public Object invoke(MethodInvocation mi) throws Throwable { if (!(mi instanceof ProxyMethodInvocation)) { throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); } ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi); // 设置连接点信息 JoinPointMatch jpm = getJoinPointMatch(pmi); // 通过反射调用通知方法 return invokeAdviceMethod( pjp, jpm, this.aspectInstanceFactory.getAspectInstance(), this.aspectJAdviceMethod); } }

6.2 各种通知的执行时机

text

方法执行时间线: ┌─────────────────────────────────────────────────────────────┐ │ 方法调用过程 │ ├────────────┬────────┬────────┬────────┬────────┬────────────┤ │ │ │ │ │ │ │ │ Before │ 方法执行│ After │ After │ After │ Around │ │ Advice │ │ Advice │Returning│Throwing│ Advice │ │ │ │ │ Advice │ Advice │ │ └────────────┴────────┴────────┴────────┴────────┴────────────┘ ↑ ↑ ↑ ↑ ↑ 前置 方法体 后置 返回 异常 (总是执行)

七、Spring AOP 的织入时机和方式

7.1 织入时机

  1. 编译时织入(AspectJ):在编译阶段织入,需要特殊的编译器

  2. 类加载时织入(LTW):在类加载到 JVM 时织入

  3. 运行时织入(Spring AOP):在运行时通过动态代理织入

7.2 Spring AOP 的织入过程

java

// 织入的核心过程 public class ProxyFactory extends ProxyCreatorSupport { public Object getProxy() { return createAopProxy().getProxy(); } protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } // 通过 AopProxyFactory 创建代理 return getAopProxyFactory().createAopProxy(this); } }

八、Spring AOP 的局限性

8.1 功能局限性

  1. 只能拦截 public 方法

  2. 无法拦截 final 方法

  3. 无法拦截 static 方法

  4. 无法拦截 private 方法

  5. 只能作用于 Spring 容器管理的 Bean

  6. 自调用问题:同一个类中方法 A 调用方法 B,方法 B 不会被拦截

8.2 自调用问题的解决方案

java

@Service public class UserService { public void methodA() { // 自调用,methodB 不会被 AOP 拦截 methodB(); // 解决方案1:注入自身代理 // userService.methodB(); } public void methodB() { // 业务逻辑 } } // 解决方案1:通过 ApplicationContext 获取代理 @Service public class UserServiceImpl implements UserService, ApplicationContextAware { private ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } public void methodA() { // 获取代理对象调用 UserService proxy = context.getBean(UserService.class); proxy.methodB(); } } // 解决方案2:使用 AspectJ 编译时织入(解决根本问题)

九、Spring AOP 与 AspectJ 的区别

9.1 对比表格

特性Spring AOPAspectJ
实现方式动态代理(运行时)字节码操作(编译时/类加载时)
织入时机运行时编译时、类加载时
性能相对较慢(每次调用都有代理开销)较快(直接修改字节码)
功能仅支持方法级别的连接点支持字段、构造器等多种连接点
使用复杂度简单,集成在 Spring 中较复杂,需要额外配置
依赖仅需 Spring Core需要 AspectJ 编译器/织入器
自调用支持不支持支持

9.2 选择建议

  1. 使用 Spring AOP 的场景

    • 仅需要方法拦截

    • 已经使用 Spring 框架

    • 不需要字段或构造器拦截

    • 可以接受轻微的性能损耗

  2. 使用 AspectJ 的场景

    • 需要字段、构造器拦截

    • 需要最高性能

    • 需要解决自调用问题

    • 项目已经使用 AspectJ

十、Spring AOP 性能优化

10.1 优化策略

java

@Configuration @EnableAspectJAutoProxy( proxyTargetClass = false, // 优先使用JDK动态代理 exposeProxy = false // 不需要暴露代理时关闭 ) public class AopConfig { // 1. 精确的切点表达式,减少匹配范围 @Pointcut("execution(public * com.example.service.UserService.*(..))") public void userServiceMethods() {} // 2. 使用 within 减少运行时检查 @Pointcut("within(@org.springframework.stereotype.Service *)") public void serviceClasses() {} }

10.2 性能最佳实践

  1. 切点表达式优化

    • 避免使用execution(* *.*(..))这样的宽泛表达式

    • 优先使用within()而不是execution()

    • 合并多个切点表达式

  2. 代理选择策略

    • 有接口的实现优先使用 JDK 动态代理

    • CGLIB 创建代理较慢,但调用较快

  3. 缓存代理对象

    • Spring 默认会缓存代理对象

    • 避免频繁创建和销毁代理对象

十一、实际应用案例

11.1 统一日志切面

java

@Aspect @Component @Slf4j public class LoggingAspect { @Pointcut("execution(* com.example.service.*.*(..)) || " + "execution(* com.example.controller.*.*(..))") public void applicationLayer() {} @Around("applicationLayer()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); String className = joinPoint.getTarget().getClass().getSimpleName(); String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); // 记录方法入参 log.info("[{}#{}] 入参: {}", className, methodName, Arrays.toString(args)); try { Object result = joinPoint.proceed(); long elapsedTime = System.currentTimeMillis() - startTime; // 记录方法返回结果和执行时间 log.info("[{}#{}] 返回: {}, 耗时: {}ms", className, methodName, result, elapsedTime); return result; } catch (Exception e) { log.error("[{}#{}] 异常: {}", className, methodName, e.getMessage(), e); throw e; } } }

11.2 事务管理切面

java

@Aspect @Component public class TransactionAspect { @Autowired private PlatformTransactionManager transactionManager; @Pointcut("@annotation(com.example.annotation.Transactional)") public void transactionalMethod() {} @Around("transactionalMethod()") public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable { TransactionStatus status = transactionManager.getTransaction( new DefaultTransactionDefinition()); try { Object result = joinPoint.proceed(); transactionManager.commit(status); return result; } catch (Exception e) { transactionManager.rollback(status); throw e; } } }

11.3 缓存切面

java

@Aspect @Component public class CacheAspect { @Autowired private CacheManager cacheManager; @Pointcut("@annotation(com.example.annotation.Cacheable)") public void cacheableMethod() {} @Around("cacheableMethod()") public Object cacheAround(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Cacheable cacheable = method.getAnnotation(Cacheable.class); String cacheName = cacheable.value(); Cache cache = cacheManager.getCache(cacheName); // 生成缓存key String key = generateKey(joinPoint); ValueWrapper valueWrapper = cache.get(key); if (valueWrapper != null) { return valueWrapper.get(); } Object result = joinPoint.proceed(); cache.put(key, result); return result; } private String generateKey(ProceedingJoinPoint joinPoint) { // 生成缓存的key return joinPoint.getSignature().toLongString() + Arrays.toString(joinPoint.getArgs()); } }

十二、常见面试题深度解析

12.1 经典面试题及答案

Q1:Spring AOP 和 AspectJ 有什么区别?

A1

  • 实现机制:Spring AOP 使用动态代理(运行时),AspectJ 使用字节码操作(编译时)

  • 功能范围:Spring AOP 仅支持方法级别,AspectJ 支持字段、构造器、方法等

  • 性能:AspectJ 性能更好(编译时织入)

  • 使用复杂度:Spring AOP 更简单,集成在 Spring 中

Q2:Spring AOP 是如何选择使用 JDK 动态代理还是 CGLIB 的?

A2

  1. 如果目标对象实现了接口,默认使用 JDK 动态代理

  2. 如果目标对象没有实现接口,使用 CGLIB

  3. 可以通过配置强制使用 CGLIB:@EnableAspectJAutoProxy(proxyTargetClass = true)

  4. Spring Boot 2.x 开始默认使用 CGLIB

Q3:Spring AOP 的自调用问题是什么?如何解决?

A3

  • 问题:同一个类中,方法 A 调用方法 B,方法 B 的 AOP 增强不会生效

  • 原因:AOP 增强是通过代理对象实现的,自调用时使用的是 this 引用,不是代理对象

  • 解决方案

    1. 使用AopContext.currentProxy()获取当前代理对象(需要开启 expose-proxy)

    2. 使用 AspectJ 编译时织入

    3. 将方法拆分到不同的类中

Q4:Spring AOP 中的切点表达式有哪些类型?

A4

  1. execution:匹配方法执行

  2. within:匹配类型声明

  3. this:匹配代理对象类型

  4. target:匹配目标对象类型

  5. args:匹配参数类型

  6. @annotation:匹配带有指定注解的方法

  7. @within:匹配带有指定注解的类型

  8. @target:匹配目标对象带有指定注解

  9. @args:匹配参数带有指定注解

  10. bean:匹配 Spring Bean 名称

12.2 进阶面试题

Q5:Spring AOP 的拦截器链是如何执行的?

A5

  1. 当代理对象的方法被调用时,会触发invoke()方法

  2. 获取适用于当前方法的拦截器链(MethodInterceptor 列表)

  3. 创建MethodInvocation对象,封装调用信息

  4. 按顺序执行拦截器链

  5. 每个拦截器可以决定是否继续执行链,或直接返回

  6. 最后一个拦截器执行后,会调用原始方法

  7. 然后逆序执行后置处理(如果有)

Q6:Spring AOP 是如何解决循环依赖问题的?

A6

  1. Spring 使用三级缓存解决循环依赖:

    • 一级缓存:单例对象缓存(完整对象)

    • 二级缓存:早期曝光对象缓存(尚未初始化的对象)

    • 三级缓存:对象工厂缓存(ObjectFactory)

  2. AOP 代理对象的创建会提前进行

  3. 当发生循环依赖时,会先创建代理对象并放入缓存

  4. 其他对象注入的是代理对象

  5. 最终完成所有对象的初始化

Q7:如何实现一个自定义的 AOP 注解?

A7

java

// 1. 定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OperationLog { String value() default ""; String operator() default "system"; } // 2. 实现切面 @Aspect @Component public class OperationLogAspect { @Around("@annotation(operationLog)") public Object logOperation(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable { // 获取注解信息 String operation = operationLog.value(); String operator = operationLog.operator(); // 记录操作日志 // ... return joinPoint.proceed(); } } // 3. 使用注解 @Service public class UserService { @OperationLog(value = "创建用户", operator = "admin") public void createUser(User user) { // 业务逻辑 } }

十三、Spring AOP 源码分析技巧

13.1 关键源码位置

  1. 代理创建

    • AbstractAutoProxyCreator.wrapIfNecessary()

    • DefaultAopProxyFactory.createAopProxy()

    • JdkDynamicAopProxy/CglibAopProxy

  2. 拦截器链执行

    • ReflectiveMethodInvocation.proceed()

    • MethodInterceptor.invoke()

  3. 切点匹配

    • AspectJExpressionPointcut.matches()

    • MethodMatcher.matches()

13.2 调试技巧

  1. 设置断点位置

    • JdkDynamicAopProxy.invoke()

    • CglibAopProxy.intercept()

    • AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean()

  2. 观察代理对象

java

// 查看是否为代理对象 if (AopUtils.isAopProxy(bean)) { if (AopUtils.isJdkDynamicProxy(bean)) { System.out.println("JDK动态代理"); } else if (AopUtils.isCglibProxy(bean)) { System.out.println("CGLIB代理"); } }

十四、Spring Boot 中的 AOP 自动配置

14.1 自动配置原理

java

// Spring Boot 中的 AOP 自动配置类 @Configuration @ConditionalOnClass(EnableAspectJAutoProxy.class) @ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true) public class AopAutoConfiguration { @Configuration @EnableAspectJAutoProxy(proxyTargetClass = false) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false) public static class JdkDynamicAutoProxyConfiguration {} @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) public static class CglibAutoProxyConfiguration {} }

14.2 常用配置属性

yaml

spring: aop: auto: true # 是否开启AOP自动代理 proxy-target-class: true # 是否使用CGLIB代理,默认true # 或者在 application.properties 中 # spring.aop.auto=true # spring.aop.proxy-target-class=true

十五、AOP 最佳实践和常见陷阱

15.1 最佳实践

  1. 切面设计原则

    • 单一职责:一个切面只做一件事

    • 关注点分离:业务逻辑和横切关注点分离

    • 避免过度使用:不是所有功能都适合用 AOP

  2. 性能考虑

    • 避免在切面中执行耗时操作

    • 使用精确的切点表达式

    • 考虑缓存代理对象

  3. 可维护性

    • 为切面编写单元测试

    • 文档化切面的作用和范围

    • 监控切面的执行情况

15.2 常见陷阱

  1. 切点表达式过于宽泛

    java

    // 错误示例:匹配所有方法,性能差 @Pointcut("execution(* *.*(..))") // 正确示例:精确匹配 @Pointcut("execution(public * com.example.service.*.*(..))")
  2. 切面顺序问题

    java

    // 使用 @Order 注解指定切面执行顺序 @Aspect @Component @Order(1) // 数字越小优先级越高 public class LoggingAspect {} @Aspect @Component @Order(2) public class TransactionAspect {}
  3. 异常处理不当

    java

    @Around("serviceLayer()") public Object handleException(ProceedingJoinPoint joinPoint) { try { return joinPoint.proceed(); } catch (Exception e) { // 不要吞掉异常,除非确定要处理 log.error("方法执行异常", e); throw e; // 重新抛出异常 } }

总结

Spring AOP 作为 Spring 框架的核心模块之一,提供了强大的面向切面编程能力。通过本文的详细解析,我们应该掌握:

  1. 核心概念:切面、连接点、切点、通知等基本概念

  2. 实现原理:JDK 动态代理和 CGLIB 动态代理的工作原理

  3. 工作流程:从代理创建到方法拦截的完整流程

  4. 使用技巧:切点表达式、各种通知类型的使用

  5. 高级特性:切面顺序、自调用问题、性能优化等

  6. 实际应用:日志、事务、缓存等常见场景的实现

  7. 源码理解:关键源码位置和调试技巧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1199459.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

江南电缆官方合作、认证、销售电话怎么获取

随着电力基础设施建设、新能源产业扩张以及海外市场需求增长,电缆采购逐渐成为工程方、企业采购部门的核心工作之一,而获取官方正规渠道的服务则是采购流程的第一步。本文围绕[关键词]相关的高频问题展开解答,帮助采…

ETASOLUTIONS钰泰 ETA2892E8A ETA钰泰 降压开关稳压器

持性 宽输入电压范围3.6V-40V能够提供3A输出电流模式控制可编程开关频率强制PWM模式低Rds(on)内部功率FET热关断和欠压锁定保护提供ESOP8封装

​ Android 基础入门教程​3.2 基于回调的事件处理机制

3.2 基于回调的事件处理机制 分类 Android 基础入门教程 本节引言 在3.1中我们对Android中的一个事件处理机制——基于监听的事件处理机制进行了学习,简单的说就是 为我们的事件源(组件)添加一个监听器,然后当用户触发了事件后,交给监听器去处理,根据不同的事件 执行不同的操…

1小时用VBA打造个人工作自动化工具原型

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个快速原型工具包&#xff0c;包含可复用的VBA代码模块&#xff1a;1) 文件批量处理器&#xff1b;2) 数据提取模板&#xff1b;3) 邮件自动发送器&#xff1b;4) 简单GUI生…

聊聊江南电缆官方销售号码、加盟电话和技术号码,哪个能解决你的问题?

问题1:企业采购电缆时,为何需要优先确认官方销售渠道? 在电缆采购场景中,渠道的正规性直接决定产品质量、交付效率与售后保障。部分非官方渠道可能存在以次充好贴牌假货等问题,尤其是高压电缆、特种电缆等核心产品…

【资深架构师经验分享】:MyBatis-Plus中自动填充的最佳实践与性能优化

第一章&#xff1a;MyBatis-Plus自动填充机制概述 MyBatis-Plus 是一款增强的 MyBatis 框架&#xff0c;旨在简化开发过程中的 CRUD 操作。其中&#xff0c;自动填充机制是一项非常实用的功能&#xff0c;能够在实体对象插入或更新数据库时&#xff0c;自动为指定字段赋值&…

为什么选择LangGraph?一篇就够了,程序员必学收藏

为什么选择 LangGraph&#xff1f; 在 AI 世界中&#xff0c;检索增强生成&#xff08;RAG&#xff09;系统已广泛用于处理简单查询&#xff0c;生成上下文相关回答。 但随着 AI 应用复杂度不断提升&#xff0c;我们迫切需要一种能执行多步推理、保持状态和具备动态决策能力的…

解决显存不足难题,Unsloth高效微调实践

解决显存不足难题&#xff0c;Unsloth高效微调实践 在大模型时代&#xff0c;显存不足成了许多开发者和研究者面前的一道“拦路虎”。尤其是当我们想要对像 Llama3、Qwen 这样的 8B 级别大模型进行微调时&#xff0c;动辄几十 GB 的显存需求让人望而却步。有没有一种方法&…

盘点好用的精密背心袋制袋机,瑞安市天晟包装机械受青睐

在包装机械行业加速智能化、柔性化转型的当下,一台性能稳定、适配多元需求的背心袋制袋机,是食品商超、日化零售、电商快递等领域企业提升包装效率的核心装备。面对市场上功能各异、品质参差的背心袋制袋机品牌厂家,…

MinerU教育场景应用:试卷数字化系统搭建案例

MinerU教育场景应用&#xff1a;试卷数字化系统搭建案例 在教育信息化推进过程中&#xff0c;大量纸质试卷、历年真题、模拟考卷亟需转化为结构化数字资源。但传统OCR工具面对多栏排版、手写批注、复杂公式、嵌入图表的试卷时&#xff0c;常常出现文字错位、公式丢失、表格断裂…

YOLOv9推理精度下降?权重加载与输入尺寸调优指南

YOLOv9推理精度下降&#xff1f;权重加载与输入尺寸调优指南 你是不是也遇到过这种情况&#xff1a;刚部署完YOLOv9模型&#xff0c;信心满满地跑起推理&#xff0c;结果发现检测框不准、漏检严重&#xff0c;甚至一些明显目标都识别不出来&#xff1f;别急&#xff0c;这很可…

2026年塑料袋制袋机实力供应商推荐,选哪家更靠谱

2026年包装产业加速向智能化、柔性化转型,塑料袋制袋机、背心袋制袋机等设备的品质与适配性,直接决定下游食品、日化、电商等行业的包装效率与成本控制。当前市场中,制袋机制造厂数量众多,但多数企业仅能提供单一设…

2026年尼康相机存储卡推荐:影像存储趋势排名,涵盖高速读写与数据安全痛点

研究概述 在数码影像创作日益普及与专业化的今天,存储卡已不再是简单的数据容器,而是直接影响拍摄体验、工作流效率乃至作品安全的关键组件。对于尼康相机用户而言,面对从入门APS-C到旗舰无反的多样化机型,以及从静…

Z-Image-Turbo自动清除记录功能,隐私保护再升级

Z-Image-Turbo自动清除记录功能&#xff0c;隐私保护再升级 你是否担心AI生成的图片会留下痕迹&#xff1f;尤其是在处理敏感内容时&#xff0c;比如设计草图、内部宣传素材&#xff0c;甚至是一些私人创作&#xff0c;不希望被他人看到历史记录&#xff1f;现在&#xff0c;Z…

聊聊高速制袋机供应商,哪家性价比更高?

2026年包装产业智能化转型加速,全自动制袋机、高速制袋机的性能与适配性直接决定下游企业的生产效率与成本控制能力,而制袋机制造厂的合作案例丰富度则是其技术实力与市场认可度的核心证明。无论是食品饮料行业对包装…

揭秘IntelliJ IDEA启动失败真相:如何快速修复“Command line is too long“问题

第一章&#xff1a;揭秘IntelliJ IDEA启动失败的根源 IntelliJ IDEA 作为 Java 开发领域的旗舰级 IDE&#xff0c;其稳定性广受认可。然而在实际使用中&#xff0c;部分用户仍会遭遇启动失败的问题。这类问题往往并非由单一因素引起&#xff0c;而是多种潜在原因交织所致。深入…

NewBie-image-Exp0.1创意应用:基于n>miku的二次元角色生成案例

NewBie-image-Exp0.1创意应用&#xff1a;基于n>miku的二次元角色生成案例 1. 引言&#xff1a;开启你的二次元创作之旅 你是否曾幻想过&#xff0c;只需输入几行描述&#xff0c;就能让一个活灵活现的二次元角色跃然于屏幕之上&#xff1f;现在&#xff0c;这一切不再是梦…

亲测CAM++说话人验证效果,两段语音是否同一人一试便知

亲测CAM说话人验证效果&#xff0c;两段语音是否同一人一试便知 1. 上手前的期待&#xff1a;声纹识别真的靠谱吗&#xff1f; 你有没有过这样的经历&#xff1f;接到一个电话&#xff0c;对方声音有点熟&#xff0c;但又不敢确定是不是认识的人。如果有个工具能告诉你“这确…

开源大模型趋势一文详解:NewBie-image-Exp0.1引领动漫生成新范式

开源大模型趋势一文详解&#xff1a;NewBie-image-Exp0.1引领动漫生成新范式 1. NewBie-image-Exp0.1&#xff1a;开启高质量动漫生成的新篇章 在当前AI图像生成技术飞速发展的背景下&#xff0c;专注于特定风格的垂直领域大模型正逐渐成为主流。NewBie-image-Exp0.1 就是其中…

cv_unet_image-matting实战案例:社交媒体头像自动生成平台搭建步骤

cv_unet_image-matting实战案例&#xff1a;社交媒体头像自动生成平台搭建步骤 1. 项目背景与目标 你有没有遇到过这种情况&#xff1a;想换个社交平台头像&#xff0c;但手头的照片背景太乱&#xff0c;修图又麻烦&#xff1f;现在&#xff0c;借助AI图像抠图技术&#xff0…