[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失效的解决方案。

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

相关推荐
LSL666_10 分钟前
JVM面试题——垃圾收集器
java·jvm·面试·垃圾收集器
chools21 分钟前
Java后端拥抱AI开发之个人学习路线 - - Spring AI【第二期】
java·人工智能·学习·spring·ai
※DX3906※29 分钟前
SpringBoot之旅5| 快速上手SpringAOP、深入刨析动态/静态两种代理模式
java·数据库·spring boot·后端·spring·java-ee·代理模式
難釋懷1 小时前
Redis缓存预热
redis·spring·缓存
庞轩px1 小时前
后端开发面试题总结
java·jvm·面试·并发编程·mysql与redis·spring与消息队列·网络协议与设计模式
希望永不加班1 小时前
SpringBoot 整合 MyBatis 完整实战
java·spring boot·后端·spring·mybatis
wuqingshun3141591 小时前
说说事务的隔离级别
java·spring
小红的布丁2 小时前
Redis 内存淘汰与过期策略
java·spring·mybatis
huihuihuanhuan.xin2 小时前
spring循环依赖以及补充相关知识
java·后端·spring
学编程就要猛3 小时前
JavaEE进阶:Spring Boot快速上手
java·spring boot·java-ee