[Java EE 进阶] 一文吃透 Spring IoC&DI:核心概念 + 实战用法 + 面试考点(上篇)

一.IOC&DI 介绍

1. 传统程序开发 的问题 : 高耦合

以 "造一辆车" 为例,传统开发中对象的创建和依赖关系由自身控制:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮 ;

所有的对象都通过 new 手动创建 ; 当底层组件(如轮胎尺寸) 发生变化时 , 整个调用链上的所有代码都需要修改 , 程序耦合度高 , 可维护性差

java 复制代码
public class Main {
    public static void main(String[] args) {
        Car car = new Car(21);
        car.run();
    }
}

public class Bottom {
    private Tire tire;

    public Bottom(Integer size) {
        this.tire = new Tire(size);
        System.out.println("bottom init...");
    }
}

public class Car {
    private Framework framework;

    public Car(Integer size) {
        this.framework = new Framework(size);
        System.out.println("car init...");
    }

    public void run() {
        System.out.println("car run...");
    }
}

public class Framework {
    private Bottom bottom;

    public Framework(Integer size) {
        this.bottom = new Bottom(size);
        System.out.println("framework init...");
    }
}

public class Tire {
    int size;
    public Tire(Integer size) {
        this.size = size;
        System.out.println("tire init, size:"+ size);
    }
}

注意 : 上述代码分为 5 个类

随着车辆的个性化需求增多 , 如果我们修改代码 , 会发现修改成本很高 , 例如 :

从上述代码可以看出 , 问题出现在 : 当最底层代码改动后 , 整个调用链上的所有代码都需要修改

在上⾯的程序中, 我们是根据轮⼦的尺⼨设计的底盘,轮⼦的尺⼨⼀改,底盘的设计就得修改.

同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽⻋设计也得改,整个设计⼏乎都得改

2. IoC (Inversion of Control,控制反转)

定义:Spring 是一个 "控制反转" 的容器,本质是对象的创建权 由程序自身反转给 Spring 容器

  • 传统模式 :程序需要主动通过 new 关键字创建对象
  • IoC 模式:对象的创建和管理交给 IoC 容器,程序只需从容器中获取对象即可
  • IoC 的核心价值:实现程序解耦,将对象之间的依赖关系从代码中剥离,由容器统一管理,底层组件变化时,上层代码无需修改

举一个例子直观理解 IoC 思想 : 自动驾驶

  • 传统驾驶 : 车辆的控制权由驾驶员掌握
  • 自动驾驶 : 控制权反转 , 交给驾驶自动化系统处理

3. 解决方式 : DI (Dependency Injection,依赖注入)

它是实现 IoC 的主要方式 , Spring 容器在创建对象时,容器会动态地为程序提供运行时所以来的资源(对象)

  • 关系 : Ioc 是思想/目标 , DI 是现实手段 , 二者在不同的角度描述这同一间事情--解耦对象依赖

举个例子 : 理解 IOC 和 DI 之间的关系

  • "想吃个好的"(Ioc 思想) , 选择"吃火锅"或者"吃烤肉"(DI 具体实现) , 思想指导实现 , 实现落地思想

容器在运行期间 , 动态的为应用程序提供运行时依赖的资源

我们尝试换一种思路 , 先设计车身 , 根据车身来设计底盘 , 根据底盘来设计轮子 ; 得到依赖关系 : 轮子依赖底盘 , 底盘依赖⻋车身 , 车身依赖汽车

只需要将原本由自己创建的下级类 , 改为**传递(注入)**的方式

通过构造函数的方式 , 把依赖对象注入到需要使用的对象中

java 复制代码
public class main {
    //注意别调用成v1版本的构造方法
    public static void main(String[] args) {
        Tire tire = new Tire(2,"red");
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}

public class Tire {
    int size;
    String color;
    public Tire(Integer size , String color){
        this.color = color;
        this.size = size;
        System.out.println("tire init:"+color + size);
    }
}

public class Bottom {
    private Tire tire;
    public Bottom(Tire Tire){
        this.tire = tire;
        System.out.println("bottom init....");
    }
}

public class Framework {
    private Bottom bottom;
    public Framework(Bottom bottom){
        this.bottom = bottom;
        System.out.println("Framework init....");
    }
}

public class Car {
    private Framework framework;
    public Car(Framework framework){
        this.framework = framework;
        System.out.println("car init...");
    }
    public void run(){
        System.out.println("car run...");
    }
}

通过上述调整 : 无论底层如何变化 , 整个调用链不做任何变化 , 这样就 完成了代码的解耦

4.IoC 容器的核心能力

Spring 作为 IoC 容器 , 核心只做两件事 :

  1. 存 : 将对象(Bean)交给 Spring 容器管理
  2. 取 : 程序需要时 , 从 Spring 容器中获取依赖的 Bean 对象

三 . IoC 实战 : Bean 的存储(将对象交给 Spring 管理)

将对象交给 Spring 容器管理 , 即 Bean 的注册 , Spring 提供类注解和方法注解两种方式 , 其中类注解是日常开发的主流 , 方法注解用于特殊场景

1. 类注解

|----------------|----------------|----------------|
| 注释 | 对应分层 | 核心作用 |
| @Controller | 控制层(Web) | 接收请求,处理请求,响应结果 |
| @Service | 业务逻辑层(Service) | 处理具体的业务逻辑 |
| @Repository | 数据访问层(Dao) | 负责数据库/数据源操作 |
| @Configuration | 配置层 | 处理项目的配置信息 |
| @Component | 通用组件层 | 非分层的通用组件注册 |

1.1 @Component(通用注解)

核心作用 : 最基础的注解 , 告诉 Spring "这个类需要被 IoC 容器实例化并管理 " , 是其他 4 个注解的"父注解" (其它注解本质上就是对@Controller 的特殊化)

使用场景 : 当你的类不属于 Controller/Service/Repository/Configuration 任何一层 , 又需要被 Spring 管理时使用(如工具,组件)

java 复制代码
package com.boop.springioc.TestNode.Component;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
    public void print(){
        System.out.println("do Component");
    }
}
java 复制代码
package com.boop.springioc;

import com.boop.springioc.TestNode.Component.UserComponent;
import com.boop.springioc.TestNode.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringIocApplication {
    public static void main(String[] args) {
        //获取Spring上下文
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
        //测试Conponent
        UserComponent userComponent = context.getBean(UserComponent.class);
        userComponent.print();
    }

}

注意 :

① 如果手动删除@Coponent

报错信息 : 找不到类型是'com.boop.springioc.TestNode. Component.UserComponent'的 bean

②ApplicationContext 获取 bean 对象的功能 , 是父类 BeanFactory 提供的

③ 默认 bean 名称 :

根据 Bean 的命名规则 , 来手动获取 Bean

java 复制代码
package com.boop.springioc;

import com.boop.springioc.TestNode.Component.UserComponent;
import com.boop.springioc.TestNode.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {

        //获取Spring上下文
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
        //测试Conponent
        UserComponent userComponent1 = context.getBean(UserComponent.class);
        userComponent1.print();
        UserComponent userComponent2 = (UserComponent)context.getBean("userComponent");
        userComponent2.print();
        System.out.println(userComponent1);
        System.out.println(userComponent2);
    }

}

1.2@Controller(表现层注解)

核心作用 : 标记类为 SpringMVC 的控制器(处理 HTTP 请求) , 并且是 Web 层的组件

额外特性 :

  • 配合@RequestMapping 等注解处理特殊请求
  • Spring MVC 的异常处理器 , 参数绑定等功能会优先识别该类注解标记的类
  • 本质:@Controller = @Component + Web层语义
java 复制代码
package com.boop.springioc.TestNode.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@RequestMapping("/usctrl")
@ResponseBody
@Controller
//@Service
public class UserController {
    @RequestMapping("/sayHi")
    public void sayHi(){
        System.out.println("hi,UserController...");
    }

}
java 复制代码
package com.boop.springioc;

import com.boop.springioc.TestNode.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        //获取Spring上下文
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);

        //测试@Controller
        //使用Spring上下文获取对象
        UserController userController = context.getBean(UserController.class);
        //使用对象
        userController.sayHi();
    }

}

注意事项 :

① Spring 上下文对象 :

Spring 上下文对象(ApplicationContext) :

本质是 : Spring 框架的核心容器+运行上下文环境

是 Spring 整个应用的"总控室"

它继承了多个核心接口 , 具备 : IoC 容器核心 , 环境与配置管理 , 资源加载器 , 事件发布/监听 , 国际化支持 , 应用层上下文整合

② 为什么还要加 @ResponseBody

  1. 如果只加 @Controller 时 , Spring 会把返回值当作视图名取寻找模板(如 xxx.html) ; 示例中的方法 sayHI() 返回值是 void , 如果要直接输出字符串/JSON 给前端 , 必须告诉 Spring 这是响应体 , 而不是页面 ; 此时就需要用到 @ResponseBody , 加在类上 , 并且表示类的所有接口都直接返回数据 , 不找视图
  2. 我还想让方法 sayHI() 成为一个可以访问的 HTTP 接口 , 就必须加@ResponseBody,@RequestMapping 等注解 ; 如果方法 sayHI() 只是在内部调用 , 不对外提供接口 , 那就不需要加@ResponseBody

1.3@Service(业务层注解)

核心作用 : 标记类为业务逻辑层组件 , 语义上明确这是"处理核心业务逻辑" 的类

额外特性 :

  • 语义化强 , 便于团队协作和代码维护
  • 本质 : @Service = @Component + 业务层语义
java 复制代码
package com.boop.springioc.TestNode.Service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
    public void print(){
        System.out.println("do Service");
    }
}
java 复制代码
import...//此处省略
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {


        //获取Spring上下文
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
        //测试@Service
        UserService userService = context.getBean(UserService.class);
        userService.print();
    }
}

省去@Service 同样报错

1.4@Repository(数据访问层注解)

核心作用 : 标记类为数据访问层(Dao)组件 , 负责与数据库/数据源交互

额外特性 :

  • 自动转换 JDBC 相关的异常(将原生 SQL 异常转化为 SPring 统一的 DataAccessException)
  • 语义上明确这是"数据访问层" , 是持久化操作的核心
  • 本质 : @Repository = @Component + 数据访问层语义 + 异常转换
java 复制代码
package com.boop.springioc.TestNode.Respository;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public void print(){
        System.out.println("do Respository");
    }
}
java 复制代码
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {


        //获取Spring上下文
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
        //测试Repository
        UserRepository userRepository = context.getBean(UserRepository.class);
        userRepository.print();
    }
}

删掉@Repository 同样报错

1.5@Configuration(配置类注解)

核心作用 : 标记类为 Spring 的配置类 , 替代传统的 XML 配置文件 , 用于定义 Bean,配置依赖等

额外特性 :

  • 配合@Bean 注释可以手动注册 Bean(方法即注解)
  • 配置类本身也是一个 Bean , 但优先级高于普通 @Component
  • 支持@ComponentScan(扫描指定包下的注解) , @Import(导入其他配置类)等
  • 本质 : @Configuration = @Component + 配置类语义 + 增强的Bean定义能力
java 复制代码
package com.boop.springioc.TestNode.Config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {
    public void print(){
        System.out.println("do config");
    }
}
java 复制代码
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {


        //获取Spring上下文
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
        //测试@Configuration
        UserConfig userConfig = context.getBean(UserConfig.class);
        userConfig.print();
    }
}

同样的删除@Configuration, 也会报错

未完!!!

由于内容较长,下一篇将聚焦剩下的方法注解进行讲解:包括 : @Bean , @Autowired , 以及详细讲解@Autowired失效的解决方案。

关注我,下篇持续更新,避免错过完整实战流程~ 也欢迎在评论区留言你遇到的问题,下篇会针对性解答!

相关推荐
一只叫煤球的猫2 小时前
用这个框架彻底摆脱Controller,从此专注业务——ArcRoute
java·spring·开源
xuansec2 小时前
【JavaEE安全】Java反序列化深度剖析:核心原理、利用链构造与安全风险管控
java·安全·java-ee
xu_ws2 小时前
Spring-ai项目-deepseek-会话日志
java·人工智能·spring
茶杯梦轩2 小时前
HTTP核心:协议、状态码与请求方法详解
后端·网络协议·面试
www_stdio2 小时前
深入理解 React Fiber 与浏览器事件循环:从性能瓶颈到调度机制
前端·react.js·面试
努力学算法的蒟蒻2 小时前
day108(3.9)——leetcode面试经典150
面试·职场和发展
Wect3 小时前
React 中的双缓存 Fiber 树机制
前端·react.js·面试
MardaWang3 小时前
鸿蒙App内存排查与监控全链路实战(工具+方案)
华为·面试·harmonyos·鸿蒙