Spring控制反转与依赖注入

控制反转与容器管理

反射机制

Java 反射机制(Reflection)允许程序在运行时 获取类元信息,核心 API 在 java.lang.reflect 包。

类/接口 用途
Class<T> 表示类或接口的元数据
Field 表示类的字段(成员变量)
Method 表示类的方法
Constructor<T> 表示类的构造方法
Modifier 解析修饰符(public, private, static 等)
Array 动态创建和访问数组
Parameter 方法参数信息(Java 8+)
  • 类反射:直接通过类本身(Class 对象)反射到元信息
  • 对象反射:通过对象实例,先反射到类本身(Class 对象),再获取其类的元信息
java 复制代码
class Person {
    private String name;
    public Person(String name) { this.name = name; }
    public String getName() { return name; }
}

// 类反射 - 直接通过类获取元信息
Class<Person> clazz = Person.class;
Field[] fields = clazz.getDeclaredFields();

// 对象反射 - 通过实例获取类元信息
Person person = new Person();
Class<?> personClass = person.getClass();
Field nameField = personClass.getDeclaredField("name");

Spring 的高级特性(依赖注入、动态代理)基于反射实现,提供了更高级的反射工具类及 API,诸如 ReflectionUtilsBeanUtilsAnnotationUtilsResolvableType 等。

控制反转思想

控制反转(Inversion of Control, IoC)是设计思想 ,核心目标是解耦,将对象创建、配置和生命周期的控制权从应用代码转移给外部容器 ,而依赖注入(Dependency Injection, DI)是实现手段,IoC 容器 (框架)则是依赖注入的实施者。

  • 传统方式 :对象 A 需要依赖对象 B 时,由 A 内部通过 new B() 主动创建和管理 B。
  • IoC 方式 :对象 A 仅声明它需要 B。由 IoC 容器 负责在适当的时候创建 B 的实例并"注入"给 A。

Spring IoC 容器的核心接口包括 BeanFactory(根接口)、ApplicationContext(是前者子接口),而"容器"本身就是实现他们的对象,原生 Spring 需要手动创建 IoC 容器实例(显式指定 XML/配置类、手动管理依赖、借助外部 Servlet 容器等),而 Spring Boot 已把过程封装在 SpringApplication.run() 方法中自动创建(自动扫描约定路径、Starter POM 自动管理依赖、内置 Servlet 容器等)。

Spring IoC 容器需要根据配置(XML、注解、Java Config)定义 Bean(内部维护成注册表,可以类比成一个全局哈希表,根据名称映射实例)。

Spring IoC 容器在初始化 Bean、进行依赖注入(如 @Autowired 字段注入)时,底层大量使用了 FieldMethodConstructor 等反射 API获取和分析元数据。

容器启动流程

  1. JVM 启动:加载应用主类。
  2. 启动类静态代码块 :执行主类中的 static {} 代码块。
  3. Spring 启动监听SpringApplicationRunListener.starting() 被触发,标志 Spring 启动开始。
  4. 环境准备 :加载配置文件(如 application.yml)、初始化环境变量等。
  5. 应用上下文初始化 :执行 ApplicationContextInitializer.initialize(),对 Spring 上下文进行早期定制。
  6. 主方法执行 :启动类的 main() 方法被调用。注意:此时 Spring 容器尚未初始化。
  7. IoC 容器启动 (SpringApplication.run())核心阶段 ,开始加载和装配 Bean。
    1. 容器首先识别启动类(因被 @SpringBootApplication 标注,它本身也是一个 Bean)并进行处理。
    2. 根据组件扫描、配置等,实例化并装配其他所有 Bean。
  8. Bean 初始化后回调 :在所有 Bean 装配完成后,容器调用所有被 @PostConstruct 标记的方法。
  9. 应用就绪回调 :最后,执行所有 CommandLineRunnerApplicationRunner 的实现,此时应用已完全启动,可以执行业务逻辑。

依赖注入

配置方式

方式 配置方式 说明
XML 配置 <bean> 标签,<property><constructor-arg> 传统方式,显式声明 Bean 和依赖关系
Java 配置 @Configuration + @Bean 方法 类型安全,可编程控制创建逻辑
注解自动装配 @Component, @Autowired, @ComponentScan 主流方式,声明式,简洁高效

注入方式

属性注入 ,注解于属性,最为简洁,但无法注入 final 属性(因其需要在对象构造完成之前被初始化)

java 复制代码
@Controller
public class ClassA {
    @Autowired
    private ClassB classB;
}

构造器注入 ,注解于构造函数,可注入 final 属性,线程安全,方便调试

java 复制代码
@Component
public class ClassA {
    private final ClassB classB;
    
    // 构造器注入
    @Autowired // 在Spring 4.3以后,如果只有一个构造器,可以省略@Autowired
    public ClassA(ClassB classB) {
        this.classB = classB;
    }
}

Setter 注入 ,注解于 Setter 函数,无法注入 final 属性,非线程安全

java 复制代码
@Component
public class ClassA {
    private ClassB dependencyB;
    
    // Setter方法注入
    @Autowired
    public void setDependencyB(ClassB b) {
        this.dependencyB = b;  // 依赖可以在对象创建后设置
    }
}

生命周期

Spring Bean 的生命周期指的是 Bean 从被容器创建、初始化、提供服务,到最终被容器销毁的整个过程 。Spring 容器在这个过程的关键节点 提供了回调机制(Callback Hooks),允许你介入并执行自定义逻辑(如初始化资源、加载配置、释放连接等)。这种管理带来了极大的灵活性和控制力。

特性 类的生命周期 Bean 的生命周期
主体 类型(Class) 实例(Bean)
管理者 JVM 类加载子系统 Spring IoC 容器
触发 首次主动使用(new、访问静态成员等)或反射加载 容器根据配置和依赖关系触发创建
内存区域 方法区(元空间) 堆内存(对象实例本身)
数量 每个加载器下唯一 可根据作用域(如 singleton, prototype)有多个
关键阶段 加载 -> 链接 -> 初始化 -> 使用 -> 卸载 实例化 -> 属性填充 -> 初始化 -> 使用 -> 销毁
阶段 方法/接口 执行时机 作用
实例化 构造器调用 Bean 创建时 创建 Bean 实例
属性注入 @Autowired, @Value 实例化后 注入依赖和配置
Aware 接口 BeanNameAware 注入完成后 获取容器信息
初始化前 @PostConstruct 使用 Bean 前 早期初始化逻辑
初始化 InitializingBean @PostConstruct 标准初始化逻辑
初始化后 BeanPostProcessor 初始化完成后 生成代理对象等
销毁 @PreDestroy 容器关闭时 清理资源

装配优先级

@Autowired 是Spring 框架提供的注解,@Resource 是 JDK 提供的注解

  • @Autowired 优先级链:类型匹配 → @Qualifier → 字段名匹配 → @Primary → 抛异常
  • @Resource 优先级链:名称匹配(显式 name)→ 名称匹配(字段名)→ 类型匹配 → 抛异常

@Autowired @Resource 是 否 是 否 是 否 是 否 是 否 是 否 唯一匹配 无或多例 开始依赖注入 使用哪个注解? 按类型匹配 按名称匹配 唯一类型匹配? 直接注入 使用@Qualifier? 按限定名称注入 按字段名匹配? 注入匹配Bean 存在@Primary Bean? 注入@Primary Bean 抛出异常 指定name属性? 按name精确匹配 用字段名匹配 匹配成功? 注入名称匹配Bean 按类型回退匹配? 注入类型匹配Bean 抛出异常 注入成功 注入失败

循环依赖问题

java 复制代码
@Component
public class ServiceA {
    @Autowired
    private ServiceB serviceB;  // 依赖 ServiceB
}

@Component
public class ServiceB {
    @Autowired
    private ServiceA serviceA;  // 又依赖 ServiceA,形成循环
}

Spring 自身通过三级缓存解决单例 Bean 的循环依赖

  1. ServiceA 实例化 → 放入三级缓存(工厂对象)
  2. ServiceA 注入 ServiceB → 开始创建ServiceB
  3. ServiceB 注入 ServiceA → 从三级缓存获取早期引用
  4. ServiceB 完成创建 → ServiceA 完成注入
java 复制代码
// 1. 一级缓存:存放完全初始化好的 Bean(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

// 2. 二级缓存:存放早期暴露的 Bean(半成品,已实例化但未完成属性注入)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

// 3. 三级缓存:存放 ObjectFactory,用于创建代理对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

此外,还可以在设计层面解耦

java 复制代码
// 提取公共逻辑到第三方类
@Service
public class CommonService {} // A和B都依赖C,而非彼此

@Service
public class ServiceA {
    @Autowired
    private CommonService commonService;
}

接口隔离

java 复制代码
public interface Processor {
    void process();
}

@Service
public class ServiceA implements Processor {
    // 依赖接口而非具体实现
}

使用 Setter 注入

java 复制代码
@Service
public class ServiceA {
    private ServiceB serviceB;
    
    @Autowired // Setter允许延迟注入
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

应用上下文获取

java 复制代码
@Service
public class ServiceA implements ApplicationContextAware {
    private ApplicationContext context;
    
    public void doSomething() {
        ServiceB b = context.getBean(ServiceB.class); // 需要时获取
    }
}

使用 @Lazy 延迟注入,直到第一次实际使用时才创建,但不适用于 prototype 作用域的 Bean

java 复制代码
// 打破构造器循环依赖
@Service
public class ServiceA {
    private final ServiceB serviceB;
    
    // 延迟注入解决构造器循环依赖
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

// 减少启动开销
@Configuration
public class AppConfig {
    @Bean
    @Lazy  // 首次调用时初始化
    public HeavyService heavyService() {
        return new HeavyService();
    }
}
相关推荐
喜欢流萤吖~2 小时前
Lambda 表达式
java
无限大62 小时前
验证码对抗史
后端
ZouZou老师2 小时前
C++设计模式之适配器模式:以家具生产为例
java·设计模式·适配器模式
用户2190326527352 小时前
Java后端必须的Docker 部署 Redis 集群完整指南
linux·后端
曼巴UE52 小时前
UE5 C++ 动态多播
java·开发语言
VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue音乐管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
程序员鱼皮2 小时前
刚刚,IDEA 免费版发布!终于不用破解了
java·程序员·jetbrains