SpringBoot:在Runnable的run方法中使用@Autowired注入实例触发空指针异常的问题
你触发空指针异常的核心原因非常明确:你手动通过new MyClass("abc", "def")创建的MyClass实例,根本不是Spring容器管理的Bean。
Spring的@Autowired注解只有在Spring自己创建并托管的对象实例上才会生效——它会在Bean初始化时自动注入依赖。而你手动new出来的对象,Spring完全不知道它的存在,自然不会给它注入serviceA,所以serviceA会是null,调用serviceA.innerMethod()时就会抛出NPE。
下面给你几种不同场景下的解决方案,按推荐程度排序:
方案1:让MyClass由Spring托管,动态设置参数
这种方案最符合Spring的设计原则,推荐优先使用:
- 修改
MyClass,改用构造器注入ServiceA(比字段注入更安全,支持不可变对象),同时添加参数设置方法:
@Component public class MyClass{ private final ServiceA serviceA; private String a; private String b; // 构造器注入ServiceA,Spring会自动装配 public MyClass(ServiceA serviceA) { this.serviceA = serviceA; } // 链式调用的参数设置方法,方便使用 public MyClass setParams(String a, String b) { this.a = a; this.b = b; return this; } public Runnable newRunnable() { // 用Lambda简化Runnable写法 return () -> serviceA.innerMethod(a, b); } }
- 在调用类中注入Spring托管的
MyClass实例,再设置参数并提交任务:
// 在你的调用类中,先通过@Autowired注入MyClass @Autowired private MyClass myClass; // 提交任务时动态设置参数 executor.submit(myClass.setParams("abc", "def").newRunnable());
这样myClass是Spring托管的实例,serviceA已经被正确注入,设置参数后创建的Runnable就能正常调用serviceA的方法了。
方案2:手动传递Spring托管的ServiceA实例
如果你的业务需要每次创建新的MyClass实例,可以去掉MyClass的@Component注解,把ServiceA通过构造器传入:
- 修改
MyClass:
// 去掉@Component,让它成为普通类 public class MyClass{ private final ServiceA serviceA; private String a; private String b; // 构造器同时接收ServiceA和业务参数 public MyClass(ServiceA serviceA, String a, String b) { this.serviceA = serviceA; this.a = a; this.b = b; } public Runnable newRunnable() { return () -> serviceA.innerMethod(a, b); } }
- 在调用类中注入
ServiceA,然后手动传入MyClass的构造器:
@Autowired private ServiceA serviceA; // 提交任务时,把Spring托管的serviceA传进去 executor.submit(new MyClass(serviceA, "abc", "def").newRunnable());
这种方式适合需要多个独立MyClass实例的场景,同时保证serviceA是Spring注入的有效实例。
方案3:通过ApplicationContext手动获取Bean(不推荐)
如果因为某些限制必须保留原有的new MyClass(a,b)写法,可以通过实现ApplicationContextAware来手动获取Spring容器中的ServiceA,但这种方式会让代码耦合Spring容器,不利于测试和维护,仅作为临时 workaround:
@Component public class MyClass implements ApplicationContextAware { private static ApplicationContext context; private ServiceA serviceA; private String a; private String b; public MyClass() {} public MyClass(String a, String b) { this.a = a; this.b = b; // 从Spring容器中手动获取ServiceA实例 this.serviceA = context.getBean(ServiceA.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } public Runnable newRunnable() { return () -> serviceA.innerMethod(a, b); } }
优先选择方案1或方案2,遵循Spring的依赖注入原则,避免手动创建非托管实例导致的依赖缺失问题。方案3尽量不要用,除非万不得已。
内容的提问来源于stack exchange,提问作者arqam

