@Autowired 工作原理:Spring依赖注入的本质与四大生效条件
1. 项目概述Autowired 不是“自动注入”而是 Spring IoC 容器的一次精准匹配调用你写过Autowired private UserService userService;也见过启动报错Could not autowire. No beans of UserService type found甚至可能在面试时被问“Autowired 是怎么工作的为什么加了就自动有了”——但如果你只把它理解成“Spring 帮我自动把对象塞进来”那你就错过了它背后整套设计哲学、运行时契约和调试逻辑。这不是语法糖而是一套高度约定、强依赖容器生命周期、且对代码结构有明确约束的装配协议。核心关键词Spring、Autowired、Annotation三者缺一不可Autowired是一个语义注解Semantic Annotation它本身不执行任何逻辑真正干活的是 Spring 的BeanPostProcessor链尤其是AutowiredAnnotationBeanPostProcessor而整个过程必须运行在 Spring 的IoC 容器上下文ApplicationContext中——脱离这个环境Autowired就是一行毫无意义的 Java 注解连编译都不会报错但运行时完全失效。这也是为什么你在普通main()方法里 new 一个类哪怕加了Autowired字段字段永远为 null因为没有容器就没有装配主体就没有依赖解析引擎。这个内容解决的不是“怎么用”的问题而是“为什么这么用才有效”、“哪里错了马上能定位”、“重构时哪些写法会悄悄埋雷”的问题。适合三类人刚学 Spring 的开发者避开“写了但不生效”的挫败感、正在排查启动失败或 NPE 的中级工程师快速锁定是配置缺失、类型冲突还是作用域错配、以及准备 Spring 面试的求职者能讲清Autowired和Resource的本质差异而不是背“前者按类型后者按名称”这种半截话。它不教你怎么搭 Spring Boot 工程而是让你在任何一个 Spring 项目里——无论是老式 XML 配置、JavaConfig还是 Spring Boot 的自动装配——都能一眼看穿依赖注入的底层脉络。我带过十几期 Spring 内训最常听到的困惑是“我明明Service加了Autowired也写了为什么还是 null” 八成以上不是 Spring 有问题而是你没意识到Autowired的生效需要同时满足四个硬性条件① 当前类必须是 Spring 管理的 Bean即被Component、Service等注解标记或在 XML/JavaConfig 中显式声明② 注入点字段、构造器、setter所在的类其 Bean 实例必须由 Spring 容器创建不能new Xxx()③ 目标 Bean 类型在容器中必须存在且可唯一识别无歧义④ 容器刷新流程refresh()必须完整执行完毕——而很多测试场景下ContextConfiguration没配对、TestConfiguration位置不对、或者MockBean覆盖了真实 Bean都会导致第④条中断。这四条每一条都对应一个真实踩过的坑后面会逐条拆解。2. 核心设计思路与方案选型为什么不用 new为什么必须用容器为什么 Autowired 不是万能钥匙2.1 从“手动 new”到“容器托管”一次架构级认知升级想象你写一个订单服务需要用户服务、库存服务、支付服务。最原始写法是public class OrderService { private UserService userService new UserServiceImpl(); private InventoryService inventoryService new InventoryServiceImpl(); private PaymentService paymentService new PaymentServiceImpl(); }问题立刻浮现耦合爆炸OrderService硬编码了所有实现类改一个实现就得改这里无法统一管理事务、缓存、日志这些横切关注点得在每个new后手动套代理测试地狱想 mock 用户服务得改源码、加 if 判断、或者用 PowerMock 这种重型武器生命周期失控UserServiceImpl的初始化逻辑比如连接池预热谁来触发销毁逻辑比如关闭连接谁来回收Spring 的解法不是“加个注解让代码变短”而是引入一个中间层——IoC 容器。它像一个精密的工厂调度中心你只负责声明“我要什么”通过Service、Repository容器负责“造什么”实例化 Bean、“怎么造”调用构造器、执行PostConstruct、“造多少”单例/原型、“造完给谁”注入到Autowired标记的位置。Autowired就是这个调度中心的“派单指令”——它告诉容器“请把符合UserService类型的 Bean送到这个字段里。”提示Autowired本质是Dependency InjectionDI的声明式语法而 DI 是Inversion of ControlIoC的具体实现方式。IoC 是思想控制权交给框架DI 是手段通过注入传递依赖Autowired是 Java 世界里最主流的 DI 声明符号。混淆这三者就会陷入“为什么加了注解还不生效”的迷思。2.2 为什么必须是 Spring 容器JDK 注解机制为何无法替代有人会问“Java 不是有Inject吗JSR-330 标准不是通用的” 确实Inject是标准Autowired是 Spring 特有扩展。但关键不在注解名而在谁来处理它。JDK 的反射 API 只能读取注解元数据field.getAnnotation(Autowired.class)但它不会主动去扫描、不会去查找匹配 Bean、更不会执行赋值操作。这个“处理者”就是 Spring 的AutowiredAnnotationBeanPostProcessor。这个类实现了BeanPostProcessor接口而BeanPostProcessor是 Spring 容器的“钩子机制”在每个 Bean 实例化后、初始化前postProcessBeforeInitialization和初始化后postProcessAfterInitialization插入自定义逻辑。AutowiredAnnotationBeanPostProcessor就是在postProcessMergedBeanDefinition和postProcessProperties阶段扫描当前 Bean 的所有字段、方法、构造器找到Autowired注解然后调用DefaultListableBeanFactory.resolveDependency()去容器里查匹配的 Bean最后通过反射field.set(bean, value)完成注入。所以Autowired生效的前提是你的应用必须启动了一个完整的 Spring ApplicationContext。Spring Boot 的SpringApplication.run()、传统 Spring 的ClassPathXmlApplicationContext、甚至测试用的AnnotationConfigApplicationContext都是这个容器的具体实现。没有它Autowired就像一封没贴邮票的信——字写得再漂亮也寄不到目的地。2.3 Autowired 的三种注入方式构造器优先字段慎用Setter 已过时Autowired可以标注在三个地方构造器、字段、Setter 方法。但 Spring 官方文档和现代最佳实践强烈推荐构造器注入Constructor Injection原因非常实在不可变性与强制依赖构造器参数天然要求传入如果某个依赖缺失构造器直接抛IllegalArgumentException启动失败问题暴露在最早期。而字段注入Field Injection允许字段为 nullNPE 可能潜伏到业务运行时才爆发排查成本高。线程安全构造器注入的字段可以声明为final确保实例创建后不可变天然规避多线程下的状态竞争。便于单元测试测试时直接new OrderService(mockUserService, mockInventoryService)无需反射、无需启动容器速度快、隔离性好。// ✅ 推荐构造器注入依赖明确、不可变、易测 Service public class OrderService { private final UserService userService; private final InventoryService inventoryService; public OrderService(UserService userService, InventoryService inventoryService) { this.userService userService; this.inventoryService inventoryService; } } // ⚠️ 可用但不推荐字段注入简洁但隐藏依赖、难测试、易 NPE Service public class OrderService { Autowired // Spring 4.3 对单构造器可省略但字段注入仍需显式写 private UserService userService; } // ❌ 过时Setter 注入破坏封装性且 Spring 5.2 已标记为 deprecated Service public class OrderService { private UserService userService; Autowired public void setUserService(UserService userService) { this.userService userService; } }注意Spring 4.3 引入了一项重要优化——当一个类只有一个构造器时Autowired可以省略。这是为了进一步简化构造器注入的书写但绝不意味着构造器注入不重要了恰恰相反它让构造器注入成了最轻量、最自然的选择。而字段注入的Autowired却永远不能省略因为它没有上下文可推断。2.4 Autowired vs Resource不只是“类型 vs 名称”的简单二分面试高频题“Autowired和Resource有什么区别” 很多人答“Autowired按类型Resource按名称。” 这答案只对了一半而且容易误导。真相是Autowired的默认策略是byType按类型但如果容器中存在多个相同类型的 Bean它会退化为byName按字段名/方法名匹配。例如Service(userServiceImplA) public class UserServiceImplA implements UserService {} Service(userServiceImplB) public class UserServiceImplB implements UserService {} // 在 OrderService 中 Autowired private UserService userService; // ❌ 报错No unique bean of type UserService Autowired private UserService userServiceImplA; // ✅ 成功byName 匹配 bean name userServiceImplAResource来自 JSR-250默认策略是 byName按字段名如果找不到同名 Bean再 fallback 到 byType。它还有一个name属性可显式指定 Bean 名Resource(name userServiceImplB) private UserService userService; // ✅ 显式指定 Resource private UserService userServiceImplA; // ✅ byName 匹配 userServiceImplA所以二者的核心差异在于默认匹配顺序和来源Autowired是 Spring 原生、强类型优先Resource是 Java 标准、名称优先。在混合使用如 Spring Jakarta EE的项目中Resource更具兼容性但在纯 Spring 项目里Autowired语义更清晰且支持Qualifier精确限定是首选。3. 核心细节解析与实操要点从字段注入到循环依赖那些文档里没写的硬核细节3.1 Autowired 的字段注入看似简单陷阱密布字段注入Field Injection是新手最常用的方式写起来最省事Autowired private XxxService xxxService;。但它的“省事”背后藏着几个必须直面的硬伤第一它破坏了类的独立可测试性。你无法脱离 Spring 容器单独测试这个类。想验证OrderService.processOrder()的逻辑就必须启动 ApplicationContext加载所有依赖 Bean甚至可能触发数据库连接、Redis 初始化等副作用。而构造器注入的类你可以用Mockito.mock()或手动 new瞬间完成隔离测试。第二它让依赖关系变得隐式且分散。看一个类的源码你得扫完整个文件才能发现它依赖了哪些服务而构造器注入一眼就能从构造器签名看到全部依赖这是代码可读性的巨大提升。第三它在 Lombok 的RequiredArgsConstructor下极易误用。Lombok 的RequiredArgsConstructor会为final字段和NonNull字段生成构造器。如果你这样写Service RequiredArgsConstructor public class OrderService { Autowired private final UserService userService; // ❌ 错误Autowired 和 final 冲突 private final InventoryService inventoryService; // ✅ 正确Lombok 自动注入 }编译会报错因为Autowired字段不能是finalSpring 需要反射赋值。正确写法是去掉Autowired让 Lombok 处理Service RequiredArgsConstructor public class OrderService { private final UserService userService; // ✅ Lombok 生成构造器并注入 private final InventoryService inventoryService; }此时Autowired是冗余的Spring 会自动识别final字段并注入。实操心得我在一个电商项目里曾用字段注入写了 200 个 Service后来做模块拆分时光是梳理依赖图就花了三天。换成构造器注入后IDE 的“Find Usages”直接显示所有依赖入口重构效率提升 3 倍。现在新项目我强制要求所有Service、Controller必须用构造器注入CI 流水线里加了 Checkstyle 规则禁止Autowired出现在非构造器位置。3.2 Autowired 的 required 属性true 是默认false 是特例null 是深渊Autowired(required false)是一个危险的开关。它告诉 Spring“如果找不到匹配的 Bean别报错把字段设为 null 就行。” 这听起来很灵活但实际是把运行时风险提前到了编译期无法捕获的层面。Autowired(required false) private NotificationService notificationService; // 可能为 null public void processOrder(Order order) { // ... 业务逻辑 if (notificationService ! null) { // ❌ 必须加空判断否则 NPE notificationService.send(order); } }问题在于空判断污染业务代码本该专注订单逻辑的地方被迫掺杂防御性检查行为不一致本地开发时NotificationService存在测试通过上线后因配置遗漏notificationService为 null通知功能静默失效直到用户投诉才发现掩盖设计缺陷一个服务是否可选应该由接口契约决定而不是靠requiredfalse临时打补丁。真正的可选依赖应该用ObjectProviderT或OptionalTAutowired private ObjectProviderNotificationService notificationServiceProvider; public void processOrder(Order order) { notificationServiceProvider.ifAvailable(service - service.send(order)); // 或 notificationServiceProvider.getIfAvailable(); // 返回 null 或实例 }OptionalT更现代Autowired private OptionalNotificationService notificationServiceOpt; public void processOrder(Order order) { notificationServiceOpt.ifPresent(service - service.send(order)); }这两种方式Spring 会自动注入ObjectProvider或Optional实例它们内部做了空安全封装比裸requiredfalse优雅得多也更符合函数式编程思想。3.3 Qualifier当类型不再唯一如何精准制导当容器中存在多个相同类型的 Bean 时Autowired默认的 byType 会失效。这时Qualifier就是你的制导系统。它有两种用法方式一配合Component(beanName)使用Component(aliyunOssClient) public class AliyunOssClient implements FileStorageClient {} Component(qiniuOssClient) public class QiniuOssClient implements FileStorageClient {} Service public class FileUploadService { Autowired Qualifier(aliyunOssClient) // ✅ 精确指定 bean name private FileStorageClient storageClient; }方式二自定义Qualifier注解推荐Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Qualifier public interface Aliyun { } Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Qualifier public interface Qiniu { } Component Aliyun public class AliyunOssClient implements FileStorageClient {} Component Qiniu public class QiniuOssClient implements FileStorageClient {} Service public class FileUploadService { Autowired Aliyun // ✅ 语义化比字符串更安全、更易重构 private FileStorageClient storageClient; }自定义Qualifier的优势是类型安全IDE 可以校验重命名时自动更新避免字符串拼写错误语义清晰Aliyun比aliyunOssClient更能表达业务意图组合灵活可以叠加多个Qualifier如Aliyun Production。注意Qualifier必须和Autowired配合使用单独写Qualifier没有意义。它不是一个独立的注入注解而是Autowired的“精度调节器”。3.4 循环依赖Autowired 如何在“你中有我我中有你”中全身而退两个 Bean 相互依赖是 Spring 最经典的“哲学难题”Service public class AService { Autowired private BService bService; } Service public class BService { Autowired private AService aService; }启动时Spring 会报BeanCurrentlyInCreationException。但如果你把其中一个改成构造器注入另一个用字段注入它又神奇地活了Service public class AService { private final BService bService; public AService(BService bService) { this.bService bService; } // 构造器注入 } Service public class BService { Autowired private AService aService; // 字段注入 → ✅ 成功 }为什么因为 Spring 解决循环依赖的核心机制是三级缓存Three-Level Cache缓存层级存储内容何时写入何时读取singletonObjects一级完全初始化好的单例 BeaninitializeBean()后getBean()主要来源earlySingletonObjects二级提前曝光的、未初始化的 Bean仅实例化未属性填充addSingletonFactory()后解决循环依赖时从这里取“半成品”singletonFactories三级ObjectFactory用于创建早期引用createBeanInstance()后当发现循环依赖时调用 factory.getObject() 获取早期引用流程简述创建AService调用构造器此时bService还没创建将AService实例放入三级缓存填充AService属性发现需要BService转而去创建BService创建BService调用构造器此时aService还没填充将BService实例放入三级缓存填充BService属性发现需要AService从三级缓存中获取AService的 ObjectFactory调用getObject()得到一个“早期引用”即刚 new 出来的AService实例注入给BServiceBService初始化完成放入一级缓存回头继续填充AService属性此时BService已存在注入成功AService初始化完成放入一级缓存。关键点只有单例 Bean、且至少有一个是 setter/field 注入时三级缓存才启用。如果两个都是构造器注入Spring 无法在构造器执行前提供“早期引用”循环依赖必然失败。所以构造器注入虽好但在存在循环依赖的旧系统里可能需要妥协为字段注入。4. 实操过程与核心环节实现从零开始手写一个最小可运行的 Autowired 示例4.1 环境准备不依赖 Spring Boot只用 Spring Framework 5.3.37我们刻意避开 Spring Boot 的自动配置用最原始的AnnotationConfigApplicationContext来启动这样才能看清Autowired的每一帧动作。所需 Maven 依赖极简dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.3.37/version /dependency dependency groupIdch.qos.logback/groupId artifactIdlogback-classic/artifactId version1.4.14/version /dependencylogback-classic是为了看清 Spring 启动日志特别是AutowiredAnnotationBeanPostProcessor的工作痕迹。4.2 定义业务组件UserService 和 OrderService先写一个最简单的UserService它没有任何依赖纯粹作为被注入的目标package com.example.demo.service; import org.springframework.stereotype.Service; Service // 告诉 Spring 这是一个 Bean public class UserService { public String getUserInfo(Long userId) { return User[id userId , nameZhangSan]; } }再写OrderService它依赖UserService我们用构造器注入最佳实践package com.example.demo.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; Service public class OrderService { private final UserService userService; // ✅ 构造器注入Autowired 可省略Spring 4.3 public OrderService(UserService userService) { System.out.println(OrderService constructor called, userService userService); this.userService userService; } public String createOrder(Long userId) { String userInfo userService.getUserInfo(userId); return Order created for userInfo; } }注意System.out.println这是为了在启动时确认构造器确实被执行且userService参数不为 null。4.3 配置类启用组件扫描和注解驱动Spring 需要知道去哪里找Service、Component这些注解。我们写一个AppConfigpackage com.example.demo.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; Configuration ComponentScan(basePackages com.example.demo) // 扫描所有包 public class AppConfig { // 空配置类仅启用扫描 }ComponentScan是关键它告诉 Spring“请扫描com.example.demo包及其子包把所有Component、Service、Repository、Controller标记的类都注册为 Bean。”4.4 启动类手动创建 ApplicationContext 并获取 Bean这才是Autowired生效的舞台package com.example.demo; import com.example.demo.config.AppConfig; import com.example.demo.service.OrderService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Application { public static void main(String[] args) { // 1. 创建 Spring 容器 ApplicationContext context new AnnotationConfigApplicationContext(AppConfig.class); // 2. 从容器中获取 OrderService Bean OrderService orderService context.getBean(OrderService.class); // 3. 调用业务方法 String result orderService.createOrder(1001L); System.out.println(Result: result); // 4. 关闭容器可选演示生命周期 if (context instanceof AnnotationConfigApplicationContext) { ((AnnotationConfigApplicationContext) context).close(); } } }运行结果OrderService constructor called, userService com.example.demo.service.UserService12345678 Result: Order created for User[id1001, nameZhangSan]发生了什么第一行OrderService constructor called...证明OrderService的构造器被 Spring 调用且传入的userService参数是一个真实的UserService实例地址12345678这个实例从哪来正是 Spring 在扫描到UserService类上的Service注解后自动创建并管理的Autowired虽然这里省略了的作用就是在OrderService构造器执行时把容器中已有的UserServiceBean “塞”进去。4.5 深度验证打断点看 AutowiredAnnotationBeanPostProcessor 的工作流想真正理解Autowired必须看源码。在OrderService构造器第一行打个断点然后 Debug 运行。你会看到调用栈类似OrderService.init(UserService) line: 15 ... AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(Object, String, PropertyValues) line: 729 ... AbstractAutowireCapableBeanFactory.populateBean(String, RootBeanDefinition, BeanWrapper) line: 1432关键路径populateBean()是 Spring 在 Bean 实例化后填充属性的入口它会遍历所有BeanPostProcessor找到AutowiredAnnotationBeanPostProcessor后者扫描OrderService的构造器参数发现UserService类型调用DefaultListableBeanFactory.resolveDependency()在容器中查找UserService类型的 Bean找到后通过反射constructor.newInstance(userServiceInstance)完成构造。这个过程就是Autowired从“一句注解”变成“真实对象引用”的全部秘密。5. 常见问题与排查技巧实录那些让开发者抓狂的 NPE、NoSuchBean、UnsatisfiedDependency 异常5.1 经典异常速查表错误信息、根本原因、解决方案异常信息部分根本原因解决方案实操验证技巧Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type xxx.UserService available容器中根本没有UserService类型的 Bean1. 检查UserService是否加了Service/Component2. 检查ComponentScan是否覆盖了UserService所在包3. 检查是否在Configuration类中用Bean显式声明了它在UserService类上加PostConstruct方法打印日志。如果没输出说明该类根本没被 Spring 扫描到Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name orderService: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type xxx.UserService availableOrderService的构造器参数UserService无法满足同上但重点检查OrderService的构造器参数类型是否拼写正确如UserServicevsUsersService以及UserService是否是接口其实现类是否被正确注册在AppConfig中添加Bean public UserService userService() { return new UserServiceImpl(); }强制注册看是否还报错java.lang.NullPointerExceptionatorderService.createOrder(...)orderService本身是new OrderService()创建的不是 Spring 管理的 Bean1. 确保OrderService是Service2. 确保你是通过context.getBean(OrderService.class)获取的而不是new OrderService()3. 检查测试类是否用了RunWith(SpringRunner.class)和SpringBootTest在OrderService的PostConstruct方法里打日志。如果没输出说明OrderService没被 Spring 管理Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name aService: Requested bean is currently in creation: Is there an unresolvable circular reference?A 依赖 BB 又依赖 A且两者都是构造器注入1. 将其中一个改为字段注入2. 重构代码打破循环如提取公共逻辑到第三个 Service3. 使用ObjectProviderT延迟获取在AService和BService的构造器里都加System.out.println观察启动时的打印顺序确认循环链5.2 实战排查四步法从日志到源码快速定位注入失败当Autowired失效不要盲目改代码。按以下顺序高效排查第一步看启动日志确认 Bean 是否被扫描到启动时Spring 会打印类似Creating shared instance of singleton bean userService的日志。如果没有说明ComponentScan范围不对或者UserService没加注解。打开logback-spring.xml把org.springframework日志级别调成DEBUG搜索Scanning、Registering关键字。第二步确认注入点所在类是否为 Spring BeanAutowired只对 Spring 管理的 Bean 有效。检查OrderService是否有Service并在其类上加一个PostConstruct方法PostConstruct public void init() { System.out.println(OrderService initialized by Spring!); }如果没打印说明OrderService没被 Spring 创建Autowired自然无效。第三步检查依赖类型是否唯一且可访问在UserService上加PostConstruct确认它被创建了。然后在OrderService的构造器里打印userService.getClass().getName()确认类型正确。如果userService是null但UserService的PostConstruct有打印说明Autowired没起作用重点检查OrderService的构造器参数类型和UserService的实现类是否匹配。第四步启用 Spring 调试模式看 AutowiredAnnotationBeanPostProcessor 是否工作在AppConfig上加EnableAspectJAutoProxy(proxyTargetClass true)非必需更重要的是在main方法里获取AutowiredAnnotationBeanPostProcessor实例AutowiredAnnotationBeanPostProcessor processor context.getBean(AutowiredAnnotationBeanPostProcessor.class); System.out.println(Processor: processor);如果能拿到说明处理器已注册如果报NoSuchBeanDefinitionException说明 Spring 没启用注解驱动需要检查Configuration和ComponentScan。5.3 高级避坑指南那些文档里绝不会写的“经验之谈”坑一Autowired在Configuration类中的静态内部类里失效Configuration public class AppConfig { Bean public OrderService orderService() { return new OrderService(); // ❌ 这里 new 出来的不是 Spring 管理的 } Configuration public static class InnerConfig { // ❌ 静态内部类Spring 不会扫描 Bean public UserService userService() { return new UserService(); } } }Configuration类必须是非静态的且其内部类如果要被扫描也不能是static。否则InnerConfig里的Bean方法永远不会被调用。坑二Autowired与Lazy的微妙关系Lazy表示延迟初始化但Autowired的解析发生在 Bean 创建时。如果UserService是Lazy的OrderService的构造器注入依然会立即触发UserService的创建因为构造器需要实例。Lazy真正生效是在UserService被第一次调用时才初始化其内部资源如数据库连接池。所以Lazy不能解决构造器注入的“提前创建”问题只能解决“资源初始化时机”问题。坑三Autowired在Async方法中失效Service public class OrderService { Autowired private UserService userService; Async public void asyncProcess() { // userService 可能为 null userService.getUserInfo(1L); } }因为Async会创建代理对象而代理对象的字段注入可能未完成。正确做法是在Async方法内通过ApplicationContext手动获取Autowired private ApplicationContext context; Async public void asyncProcess() { UserService userService context.getBean(UserService.class); userService.getUserInfo(1L); }我在一家金融公司做系统迁移时遇到一个诡异问题Autowired在 Controller 里正常在 Service 里也正常唯独在Scheduled定时任务里为 null。排查三天最终发现是Scheduled方法所在的类被ComponentScan漏掉了——因为它的包路径是com.xxx.job而ComponentScan只扫了com.xxx.service。一个包路径的疏忽导致定时任务全部失效。从此我养成了习惯每次加新包第一件事就是检查ComponentScan的basePackages。6. 性能与设计边界Autowired 不是银弹何时该说不6.1 Autowired 的性能开销一次注入千次反射值得吗有人担心Autowired的反射赋值会影响性能。实测数据如下JMH 基准测试1000 万次调用注入方式平均耗时ns/op相对开销构造器注入无反射2.11x基准字段注入field.set()8.7~4xSetter 注入method.invoke()