You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Spring AOP @Around调用ExecutorService submit时触发NullPointerException

Spring AOP切面引发ExecutorService.submit()空指针异常问题解析

问题背景

我们实现了一个用于记录方法耗时的Spring AOP切面,但调用ExecutorService.submit()方法时抛出NullPointerException,经排查是切面向ExecutorService传入了null值。需要解决以下问题:

  • 遗漏了哪些配置?
  • 为何会传入null?
  • 如何处理该问题或让目标方法跳过切面?

补充代码与依赖

依赖配置

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.4.3</version>
</parent>

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

业务代码

@Log4j2
@Service
@RequiredArgsConstructor
public class MessageProcessorService {
    
    @Qualifier("asyncpoolexecutor")
    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
    private final ParsingAndValidationSteps steps;

    public void processMessage() throws Exception {
        try {
            Future<String> populateClientIdTask = threadPoolTaskExecutor.submit(steps.populateClientIdTask);
            populateClientIdTask.get();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
@Service
@Scope(value = "prototype")
public class ParsingAndValidationSteps {

    @Autowired
    ApplicationContext ctx;
    
    public Callable<String> populateClientIdTask = () -> ctx.getBean(ClientServiceBean.class).getClientId();
}
@Service
public class ClientServiceBean {
    public String getClientId() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return String.valueOf(System.currentTimeMillis());
    }
}

AOP切面代码

@Around("execution(* com.*.*.*.*(..))")
public Object allMethodsInApplicationPackage(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    Object proceed = proceedingJoinPoint.proceed();
    logger.info("[Metrics] {} executed in {} ms", proceedingJoinPoint.getSignature(), System.currentTimeMillis() - start);
    return proceed;
} 

异常堆栈

java.lang.NullPointerException
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:143)
    at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.submit(ThreadPoolTaskExecutor.java:410)
    at com.parsingandvalidationservice.service.MessageProcessorService.processMessage(MessageProcessorService.java:75)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
    at com.aspect.Metrics.allMethodsInApplicationPackage(Metrics.java:36)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:642)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:632)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
    at com.parsingandvalidationservice.service.MessageProcessorService$$SpringCGLIB$$0.processMessage(<generated>)
    at com.parsingandvalidationservice.listener.ParsingValidationServiceListenerDelegator.delegate(ParsingValidationServiceListenerDelegator.java:57)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
    at com.aspect.Metrics.allMethodsInApplicationPackage(Metrics.java:36)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:642)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:632)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
    at com.parsingandvalidationservice.listener.ParsingValidationServiceListenerDelegator$$SpringCGLIB$$0.delegate(<generated>)
    at com.parsingandvalidationservice.listener.ParsingValidationServiceListener.listen(ParsingValidationServiceListener.java:50)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
    at com.aspect.Metrics.allMethodsInApplicationPackage(Metrics.java:36)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:642)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:632)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727)
    at com.parsingandvalidationservice.listener.ParsingValidationServiceListener$$SpringCGLIB$$0.listen(<generated>)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169)
    at org.springframework.kafka.listener.adapter.KotlinAwareInvocableHandlerMethod.doInvoke(KotlinAwareInvocableHandlerMethod.java:45)
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119)
    at org.springframework.kafka.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:78)
    at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:474)
    at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.invoke(MessagingMessageListenerAdapter.java:421)
    at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:85)
    at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:50)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:2889)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeOnMessage(KafkaMessageListenerContainer.java:2867)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:2779)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:2616)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:2505)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:2151)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeIfHaveRecords(KafkaMessageListenerContainer.java:1527)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:1465)
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:1334)
    at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run$$$capture(CompletableFuture.java:1804)
    at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java)
    at java.base/java.lang.Thread.run(Thread.java:1583)

问题原因

  1. 切面拦截范围过度宽泛:当前切面使用execution(* com.*.*.*.*(..))作为切点,会拦截com包下所有类的所有方法,包括Callable接口的call()方法。当ThreadPoolTaskExecutor.submit()传入Callable对象时,切面会尝试拦截其call()方法的执行。
  2. Prototype Bean的初始化顺序问题:ParsingAndValidationSteps是prototype作用域Bean,其中的populateClientIdTask字段在Bean实例化时就已初始化。此时Spring尚未完成ApplicationContext ctx的Autowired注入,导致ctx为null。当切面拦截call()方法执行时,ctx.getBean()抛出NPE,切面捕获异常后返回null,最终将null传入submit()方法,触发AbstractExecutorService中的空指针检查。

解决方案

方案1:缩小切面拦截范围

修改切点表达式,仅拦截业务服务类的方法,避免拦截JDK接口或框架类的方法。示例:

@Around("execution(* com.parsingandvalidationservice.service.*.*(..)) || execution(* com.parsingandvalidationservice.listener.*.*(..))")
public Object allMethodsInApplicationPackage(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    Object proceed = proceedingJoinPoint.proceed();
    logger.info("[Metrics] {} executed in {} ms", proceedingJoinPoint.getSignature(), System.currentTimeMillis() - start);
    return proceed;
}

方案2:标记方法跳过切面

自定义一个注解,用于标记不需要被切面拦截的方法:

  1. 定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipAspect {
}
  1. 修改切面切点,排除带注解的方法:
@Around("execution(* com.*.*.*.*(..)) && !@annotation(com.yourpackage.SkipAspect)")
public Object allMethodsInApplicationPackage(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    // 原有逻辑
}
  1. 调整ParsingAndValidationSteps的写法,将Callable改为方法并添加注解:
@Service
@Scope(value = "prototype")
public class ParsingAndValidationSteps {

    @Autowired
    ApplicationContext ctx;
    
    @SkipAspect
    public Callable<String> populateClientIdTask() {
        return () -> ctx.getBean(ClientServiceBean.class).getClientId();
    }
}
  1. 在MessageProcessorService中调用方法获取Callable:
Future<String> populateClientIdTask = threadPoolTaskExecutor.submit(steps.populateClientIdTask());

方案3:延迟Callable初始化

利用@PostConstruct注解,在Bean完成Autowired注入后再初始化Callable:

@Service
@Scope(value = "prototype")
public class ParsingAndValidationSteps {

    @Autowired
    ApplicationContext ctx;
    
    private Callable<String> populateClientIdTask;
    
    @PostConstruct
    public void init() {
        populateClientIdTask = () -> ctx.getBean(ClientServiceBean.class).getClientId();
    }
    
    public Callable<String> getPopulateClientIdTask() {
        return populateClientIdTask;
    }
}

在MessageProcessorService中调用get方法获取Callable:

Future<String> populateClientIdTask = threadPoolTaskExecutor.submit(steps.getPopulateClientIdTask());

内容的提问来源于stack exchange,提问作者Mohit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 15:25:54