一、Spring装配Bean的方式Spring 装配 BeanBean Wiring指的是在 Spring 容器中建立 Bean 与 Bean 之间依赖关系的过程。简单来说就是告诉 Spring“哪些类需要交给容器管理”以及“它们之间如何互相注入”。Spring 提供了 3 种主流的装配方式自动装配最常用、Java 配置类装配和XML 配置装配。1、 自动装配基于注解 - 现代开发标配这是目前开发中最常用的方式Spring 会自动扫描特定的注解并自动将依赖对象注入进去。1.1. 组件扫描Component Scanning让 Spring 知道哪些类需要作为 Bean 实例化。Component通用组件注解。Repository数据访问层DAO组件。Service业务逻辑层Service组件。Controller/RestController控制层MVC组件。激活扫描在配置类上加ComponentScan(basePackages com.example)。在 Spring Boot 中启动类上的SpringBootApplication已经默认集成了这一步会自动扫描启动类所在包及其子包。1.2. 自动注入Dependency Injection让 Spring 自动把一个 Bean 塞进另一个 Bean 里面。AutowiredSpring 原生注解。默认按类型byType装配。如果同一个类型有多个 Bean则按名称byName匹配。ResourceJDK 原生注解JSR-250 规范。默认按名称byName装配。如果找不到名称相同的 Bean才会退化为按类型byType装配。辅助注解Qualifier当Autowired遇到同一接口有多个实现类时例如Payment接口有Alipay和WechatPay可以用Qualifier(alipay)指定具体要注入的 Bean 名称。2、 Java 配置类装配基于显式代码当你无法修改第三方库的源码例如想把 Redis 的Jedis、或者是系统的RestTemplate引入 Spring 管理时无法在类上直接加Component此时就需要用到 Java 配置类。实现方式编写一个普通的类在类上标记Configuration在方法上标记Bean。方法返回值就是注入容器的 Bean方法名默认就是 Bean 的 ID。示例代码Configuration public class ThirdPartyConfig { Bean public RestTemplate restTemplate() { return new RestTemplate(); // 手动 new由 Spring 容器接管其生命周期 } }3、 XML 配置装配传统老旧项目使用这是最古老的方式将 Bean 的定义和依赖关系全部写在applicationContext.xml文件中。目前在新项目中已基本绝迹但在维护十几年前的老系统时仍会遇到。构造器注入通过constructor-arg标签。Setter 注入通过property标签底层调用setXxx()方法。示例配置!-- 声明一个 UserService 的 Bean -- bean iduserService classcom.example.UserService !-- 将名为 userDao 的 Bean 注入到 userService 的属性中 -- property nameuserDao refuserDao/ /bean bean iduserDao classcom.example.UserDaoImpl/4、 避坑指南自动装配的三种常用位置在使用Autowired自动注入时通常有三种写法其优缺点大不相同属性注入不推荐直接在成员变量上加Autowired。缺点不能用final修饰脱离 Spring 容器环境如写单元测试时容易报空指针异常NPE。Setter 注入很少用在 set 方法上加Autowired。适用场景允许外部在后续动态修改依赖。构造器注入Spring 官方推荐在构造方法上加Autowired。优点依赖可以用final修饰保证了 Bean 的不可变性组件在初始化时就必须提供所有依赖保证不会出现空指针。好消息如果类里只有一个构造方法从 Spring 4.3 开始Autowired注解可以省略不写。二、Bean生命周期的4个阶段一个 Spring Bean 的生命周期非常复杂但核心可以概括为以下四个关键阶段实例化-属性赋值-初始化-销毁。[1. 实例化] --- [2. 属性赋值] --- [3. 初始化] --- [4. 销毁](分配内存空间) (依赖注入/DI) (各种AOP与自定义加工) (容器关闭释放资源)阶段1实例化Instantiation做什么Spring 容器根据 BeanDefinition “图纸”通过反射调用构造函数在堆内存中为对象分配内存空间。状态此时对象仅仅是个“空壳”所有的成员变量都还是默认值如 null 或 0。阶段2属性赋值Populate / Dependency Injection做什么Spring 开始解析对象中的 Autowired、Value 或 XML 配置。依赖注入将该 Bean 所依赖的其他外部 Bean 注入进来并填充用户自定义的普通属性值。阶段3初始化Initialization这是整个生命周期中扩展性最强、最复杂的阶段包含了各种 Aware 接口的感知和后置处理器的加工激活Aware接口如果 Bean 实现了相关 Aware 接口Spring 会把容器自身的资源传给它。例如实现 BeanNameAware - 传入当前 Bean 的名称。实现 ApplicationContextAware - 传入当前高级容器实例。BeanPostProcessor前置处理调用所有注册的 BeanPostProcessor 的 postProcessBeforeInitialization 方法例如 PostConstruct 注解就是在此处被解析执行的。执行初始化方法如果 Bean 实现了 InitializingBean 接口会调用其 afterPropertiesSet() 方法。如果配置了自定义的 init-method如 Bean(initMethod ...)则执行该自定义方法。BeanPostProcessor后置处理调用 postProcessAfterInitialization 方法。注意Spring的AOP动态代理通常就是在这个时机介入生成代理对象替换掉原始对象的。阶段4销毁Destruction当 Spring 容器关闭如调用 context.close()时会触发单例 Bean 的销毁流程用于释放资源解析销毁注解触发 PreDestroy 标注的方法。接口回调如果 Bean 实现了 DisposableBean 接口调用其 destroy() 方法。自定义销毁如果配置了自定义的 destroy-method则执行该自定义方法。三、Spring Bean的作用域Spring Framework 中提供了 6 种 Bean 的作用域Scope用来决定 Spring 容器如何创建、管理以及何时销毁 Bean 的实例。其中前 2 种是核心基础作用域后 4 种只有在 Web 环境如使用 Spring MVC中才生效。1、 6 种作用域核心对比作用域名称适用环境实例数量与生命周期说明singleton所有环境全局唯一。Spring 容器中每个 Bean ID 仅创建一个实例。prototype所有环境每次请求全新。每次通过getBean或注入时都会创建新实例。requestWeb 环境单次 HTTP 请求。每个独立的 HTTP 请求都有自己的实例请求结束即销毁。sessionWeb 环境HTTP Session 会话。在同一个用户会话内共享一个实例会话失效则销毁。applicationWeb 环境ServletContext 级别。整个 Web 应用内共享一个实例类似单例但属于 Web 上下文。websocketWeb 环境WebSocket 会话。在同一个 WebSocket 生命周期内共享一个实例。2、 核心作用域详解与避坑指南1. 单例模式singleton——默认作用域工作机制Spring 容器在启动并初始化时默认会把这类 Bean 创建好并存入单例池中。线程安全问题singleton并不是线程安全的。如果单例 Bean 中包含可变的成员变量有状态的 Bean在多线程并发访问时例如 Spring MVC 的 Controller会导致数据错乱。最佳实践尽量将单例 Bean 设计为无状态 Bean只包含业务逻辑如 Service、DAO、Controller确保并发安全。2. 原型模式prototype——多例模式工作机制Spring 容器只负责创建并初始化它随后便直接交给调用者管理。生命周期痛点Spring 不负责销毁prototype的 Bean。这意味着即使你在 Bean 中配置了destroy-method或实现了DisposableBean接口销毁回调函数也不会执行。必须由调用者手动释放资源否则容易导致内存泄漏。3. 经典面试陷阱单例 Bean 注入原型 Bean问题描述当一个singleton属性的 Service 注入了一个prototype属性的 User 时由于单例 Service 只会初始化一次导致它内部的 User也固定成了同一个实例原型的“多例特性”直接失效。解决方案使用Lookup注解让 Spring 动态重写方法每次调用时去容器里新拿一个。显式注入ObjectFactoryT或ProviderT每次通过.getObject()延迟获取。3、 如何配置作用域1. 注解方式推荐Component Scope(prototype) // 或者是 ConfigurableBeanFactory.SCOPE_PROTOTYPE public class UserReport { // 业务代码 }2. XML 配置方式bean iduserReport classcom.example.UserReport scopeprototype/四、依赖注入方式在 Spring 框架中依赖注入Dependency Injection简称 DI主要有3种标准方式。1.构造器注入Constructor Injection——官方推荐通过类的构造函数来注入依赖。这是 Spring 官方从 4.x 版本开始强烈推荐的注入方式。代码示例Service public class OrderService { private final OrderMapper orderMapper; // 1. 建议声明为 final保证不可变 // 2. 显式构造器注入注Spring 4.3, 若只有一个构造器Autowired 注解可省略 Autowired public OrderService(OrderMapper orderMapper) { this.orderMapper orderMapper; } }核心优势强契约性与安全性依赖字段可以声明为 final。保证了对象在实例化完成后其依赖项绝对不为 null且在运行期间不可被篡改。利于单元测试在写纯单测时不需要启动复杂的 Spring 容器直接手动 new OrderService(mockMapper) 即可传入依赖。致命缺点当出现循环依赖A-B-A时构造器注入会使三级缓存机制直接失效导致系统抛出异常崩溃。2. Setter方法注入Setter Injection通过类的 setXXX() 方法来注入依赖。代码示例Service public class OrderService { private OrderMapper orderMapper; Autowired public void setOrderMapper(OrderMapper orderMapper) { this.orderMapper orderMapper; } }核心优势灵活性极高允许依赖在对象实例化之后的任何时候进行动态修改或重新注入。支持可选依赖如果不注入对象依然可以被创建出来配合 Autowired(required false)。3.属性/字段注入Field Injection——开发最常用直接在类的成员变量Field上贴 Autowired 或 Resource 注解。这是日常开发中程序员用得最多的方式。代码示例Service public class OrderService { Autowired private OrderMapper orderMapper; // 直接贴注解没有任何繁琐代码 }核心优势极其简洁没有任何臃肿的构造函数或 Setter 代码视觉上最干净。官方不推荐的原因隐患强耦合容器脱离了 Spring 容器比如写纯 Java 单测你很难通过反射以外的方式手动为它塞入 Mock 依赖。违背单一职责因为写起来太容易开发者很容易在一个类里一口气贴几十个 Autowired导致类变得极度臃肿。无法使用final字段不能声明为不可变。五、Spring三级缓存Spring 的三级缓存是其实现依赖注入DI和解决循环依赖的核心机制。它们本质上是DefaultSingletonBeanRegistry类中的三个Map集合通过相互协作来管理单例 Bean 的生命周期。1、核心区别与作用概览缓存层级容器名称存储内容核心作用一级缓存singletonObjects完全初始化好的单例 Bean供应用程序直接使用实现单例模式二级缓存earlySingletonObjects半成品的单例 Bean未填充属性解决循环依赖时确保多次注入拿到的是同一个早期引用三级缓存singletonFactories单例 Bean 的工厂对象ObjectFactory延迟代理对象的创建只有出现循环依赖且需要 AOP 时才生成代理2、分别用来解决什么问题1. 一级缓存解决「单例作用域」与「复用」问题存储对象经历了完整的实例化、属性填充、初始化如执行init-method后的成熟 Bean。解决问题保证在整个 Spring 容器运行期间相同beanName的单例 Bean只被创建一次。后续所有并发获取该 Bean 的请求都直接从一级缓存中读取实现对象的复用。2. 二级缓存解决「循环依赖中的重复创建」问题存储对象已经完成了实例化在内存中分配了空间但还没有进行属性填充和初始化的“半成品” Bean。解决问题在循环依赖场景下当 A 依赖 BB 又依赖 A 时。A 实例化后放入三级缓存接着去注入 BB 实例化后需要注入 A此时 B 会从三级缓存中触发 A 的工厂将 A 的早期引用放入二级缓存。核心价值如果还有另一个 Bean C 也依赖 AC 就可以直接从二级缓存拿到 A 的早期引用而不需要重复调用三级缓存的工厂去重新生成特别是对于 AOP 代理对象重复调用工厂会导致生成不同的代理对象破坏单例性。3. 三级缓存解决「循环依赖与 AOP动态代理的冲突」问题存储对象一个匿名内部类工厂对象ObjectFactory?。这个工厂包裹了刚实例化完的 Bean。解决问题Spring 的设计原则是在 Bean 初始化的最后阶段才创建 AOP 代理。但是如果发生了循环依赖注入给对方的必须是代理对象而不是原始对象。核心价值三级缓存通过延迟执行打破了这个两难局面。如果没有循环依赖三级缓存根本不会被触发执行Bean 按照正常生命周期在最后一步生成代理。如果发生循环依赖通过三级缓存中存放的工厂提前拦截并调用getEarlyBeanReference方法提前把 AOP 代理对象创建出来注入给依赖方从而完美兼容了循环依赖与 AOP。3、三级缓存的查找与迁移流程当 Spring 尝试获取一个 BeangetBean时会按照一级 \(\rightarrow \) 二级 \(\rightarrow \) 三级的顺序依次查找[getBean] │ ▼ ┌─────────────────────────────────┐ │ 1. 检查 一级缓存 singletonObjects │ ────(找到)──── 返回成熟的 Bean └─────────────────────────────────┘ │ (未找到) ▼ ┌──────────────────────────────────────┐ │ 2. 检查 二级缓存 earlySingletonObjects │ ────(找到)──── 返回半成品 Bean └──────────────────────────────────────┘ │ (未找到 当前 Bean 正在创建中) ▼ ┌─────────────────────────────────┐ │ 3. 检查 三级缓存 singletonFactories │ └─────────────────────────────────┘ │ (找到工厂 ObjectFactory) ▼ 执行工厂方法 (如果是 AOP 则在此处生成代理对象) │ ▼ 1. 将生成的早期引用放入 二级缓存 2. 从 三级缓存 中移除该工厂 │ ▼ 返回早期引用4、为什么不能只用二级缓存这是一个经典的面试延伸问题如果不用三级缓存只保留一级和二级缓存能解决循环依赖吗答案是能但无法完美支持 AOP。原因如果要砍掉三级缓存意味着在一级缓存之外所有 Bean 在实例化后必须立刻把早期引用放入二级缓存。如果这个 Bean 需要动态代理AOP你就必须在实例化后立刻创建代理对象。这严重违背了 Spring 的设计原则Spring 期望 Bean 在完全初始化、属性填装完后才被代理。三级缓存的存在本质上是一层缓冲策略只有在确实发生了循环依赖时才被迫提前创建代理如果没有循环依赖则依然维持在初始化末尾创建代理的正常流程。六、Autowired 和 Resource 的区别在进行属性注入时这两个注解的底层查找规则截然不同维度Autowired(Spring原生)Resource(JDK标准/ JSR-250)匹配顺序先按类型(byType)再按名称(byName)先按名称(byName)再按类型(byType)指定名称必须配合 Qualifier(beanName)直接配置属性 Resource(namebeanName)生态脱耦强绑定 Spring 框架换成别的 IoC 容器就失效。属于 Java 规范即使以后项目换成 Quarkus 等框架也能无缝兼容。