当前位置: 首页 > news >正文

Spring IoC DI 终极指南:从造车模型到企业级开发实战

目录

  • 1. IoC & DI的概念
  • 2. IoC
      • 2.1 传统程序开发
      • 2.2 解耦合形式开发(IoC)
      • 2.3 DI形式开发(IoC)
  • * Spring IoC和DI
  • 3. Bean的存储
      • 3.1 @Controller(存储控制器)
      • 3.2 @Service(存储服务)
      • 3.3 @Repository(存储仓库)
      • 3.4 @Component(存储组件)
      • 3.5 @Configuration(存储配置)
      • 3.6 关于这些类注解
      • 3.7 方法注解@Bean
      • 3.8 重命名Bean
      • 3.9 更改扫描路径@ComponentScan
  • 4. DI
      • 4.1 属性注入
      • 4.2 构造方法注入
      • 4.3 Setter注入
      • 4.4 @Autowired存在的问题
      • 4.5 三种方式的区别

关注我,学习更多企业开发和面试内容~

在这里插入图片描述

1. IoC & DI的概念

Spring是一个包含众多工具的IoC容器,容器能装东西。比如List/Map是数据存储容器,Tomcat是Web容器。

  • IoC:控制反转。
    • Spring也是一个容器,装的是对象。控制反转,又称为控制权反转,也就是获取依赖对象的过程被反转了。之前需要通过自己new创建对象,现在是直接把创建对象的任务交给容器,程序中只需要依赖注入(DI)就可以了。这个容器称为IoC容器,所以有时Spring也称为Spring容器。
    • 控制反装是一种思想,生活中比如,自动驾驶(比如驾驶员不需要驾驶,让自动化系统来控制)。
  • IoC容器:对类添加特定的注解,就是把这个对象交给Spring管理,Spring框架启动时会加载该类,把对象交给Spring管理,就是IoC思想。

【面试题】Spring两大核心思想:IoC和AOP。


2. IoC

以造车为例。

2.1 传统程序开发

轮胎(Tire)依赖底盘 (Bottom)依赖车身(Framework)依赖汽车(Car)。我们可以尝试不在每个类中自己创建new下级对象,因为如果自己创建下级类就会出现当下级类发生改变操作,自己也要跟着修改。
在这里插入图片描述
只是给car类的构造方法加了一个参数,所有调用者都需要修改,这就是耦合太高了。

public class Tire {private Integer size;public Tire(Integer size) {this.size = size;System.out.println("Tire init...");}
}public class Bottom {private Tire tire;//修改代码,添加size参数    //调用方第1次修改代码public Bottom(int size) {this.tire = new Tire(size);System.out.println("framework init...");}
}public class Framework {private Bottom bottom;//    public Framework() {
//        this.bottom = new Bottom();
//        System.out.println("framework init...");
//    }public Framework(int size) {this.bottom = new Bottom(size);  //调用方第1次修改代码System.out.println("framework init...");}
}public class Car {private Framework framwork;//    public Car() {
//        this.framwork = new Framework();
//        System.out.println("car init...");
//    }public Car(int size) {this.framwork = new Framework(size); //调用方第1次修改代码System.out.println("car init...");}public void run() {System.out.println("car run...");}
}public class Main {public static void main(String[] args) {
//        Car car = new Car();Car car = new Car(4);car.run();}
}

综上所述,增加了1个参数,却需要修改3次代码。


2.2 解耦合形式开发(IoC)

此时,我们只需要将原来由自己创建下级类的对象,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦。

public class Tire {private Integer size;private String color;public Tire(Integer size,String color) {this.size = size;this.color = color;System.out.println("Tire init...");}
}public class Bottom {private Tire tire;public Bottom(Tire tire) {this.tire = tire;}
}public class Framework {private Bottom bottom;public Framework(Bottom bottom) {this.bottom = bottom;}
}public class Car {Framework framework;public Car(Framework framework) {this.framework = framework;}public void run() {System.out.println("car run...");}
}public class Main {public static void main(String[] args) {//Spring做的事情如下,创建对象Tire tire = new Tire(4,"red");Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);}
}

在传统的代码中对象创建顺序是:Car->Framework->Bottom->Tire。
改进之后解耦的代码的对象创建顺序是:Tire->Bottom->Framework->Car。


2.3 DI形式开发(IoC)

IoC是思想,DI是实现方式。实现的IoC的方式很多,上面传统的解耦合代码是一种,DI也是一种解耦合。通过依赖注入(DI),将依赖关系的控制权交给外部容器(如 Spring),实现解耦。

  • 依赖:当前类需要的东西,比如jar包。

  • 注入:将依赖作为当前类的属性。

  • 将依赖注入当前类,使当前类和依赖注入的对象打配合,实现IoC,如下图。
    在这里插入图片描述

    • @Autowired:告诉Spring,从容器中取出这个对象,赋值给当前对象的属性。

* Spring IoC和DI

Spring是一个IoC容器,能存和取对象。
Spring容器管理的主要是对象,这些对象,我们称之为"Bean"。我们把这些对象交由Spring管理,由Spring来负责对象的创建和销毁。我们程序只需要告诉Spring,哪些需要存,以及如何从Spring中取出对象。

IoC容器已经存在,它有IoC思想。所以只要学如何把对象交给Spring容器管理(如@Service等),如何把对象从容器取出来(如@Autowired等等),也就是DI。

3. Bean的存储

  1. 类注解: @Controller、@Service、@Repository、@Component、@Configuration。
  2. 方法注解: @Bean。

3.1 @Controller(存储控制器)

想被外界访问到,必须用@Controller,不能用其他注解替代,原因是实现原理不一样,注解声明实现看似一样,实则Spring源码完全不一样。

@Controller
public class UserController {}

先不做验证,只在Service层做验证。

如果不写注解,会报NoSuchBeanDefinitionException,如下。

//不加@Controller
public class BookController {
@Autowiredprivate BookService bookService;
}

3.2 @Service(存储服务)

@Service
public class UserService {public void doService(){System.out.println("do Service...");}
}

在这里插入图片描述
将类存后,取出Bean来验证,因为还没讲DI,先不用@Autowired,而是用其他方式获取。

按类型获取(推荐):

  • context.getBean(UserService.class)
  • 明确知道 Bean的类型时使用。
  • 类型必须唯一,否则抛NoUniqueBeanDefinitionException。

按名称获取(需转型):

  • (UserService) context.getBean(“userService”)
  • 需要动态获取或处理同名 Bean 时使用,需手动转型。
  • 名称错误抛 NoSuchBeanDefinitionException。

名称+类型获取(推荐):

  • context.getBean(“userService”, UserService.class)
  • 自动类型匹配。
  • 需要确保名称和类型双重匹配时使用。

名称默认为小驼峰形式,特殊情况:若类名前两位都为大写,bean的名称为类名(如EXam)。


3.3 @Repository(存储仓库)

@Repository
public class UserRepository {public void doRepository(){System.out.println("do Repository...");}
}

3.4 @Component(存储组件)

@Component
public class UserComponent {public void doComponent(){System.out.println("do Component...");}}

3.5 @Configuration(存储配置)

@Configuration
public class UserConfig {public void doConfig(){System.out.println("do Config...");}
}

3.6 关于这些类注解

【为什么要这么多类注解】

这个也是和咱们前面讲的应用分层是呼应的。让程序员看到类注解之后,就能直接了解当前类的用途。

  • @Controller:控制层,接收请求,对请求进行处理,并进行响应。
  • @Service:业务逻辑层,处理具体的业务逻辑。
  • @Repository:数据访问层,也称为持久层。负责数据访问操作。
  • @Configuration:配置层。处理项目中的一些配置信息。
  • @Component:不属于标准分层(Controller/Service/Repository)的辅助类,所有类注解最终都是 @Component 的“特化版本”。

【类注解之间的关系】

在这里插入图片描述
源码里这些只是注解的声明,注解真正功能是在Spring源码里实现的,不在注解声明里。

五大注解只能加在类上,并且只能加在自己的代码上,不能加在。

五大注解取多少次,都是同一个地址。也就是说都是同一个对象。


3.7 方法注解@Bean

【使用@Bean的场景】

  • 五大注解只能加在类上,并且只能加在自己的代码上,如果我引入了一个第三方Jar包,也希望交给Spring管理,是没有办法加五大注解的。
  • 五大注解只能定义一个对象,而@Bean可以对同一个类定义多个对象。对于一个类,需要定义多个对象时,比如数据库操作,定义多个数据源。

@Bean是方法注解,必须搭配五大注解(类注解)来使用。原因:Spring最开始扫描注解时,只扫描类,不扫描方法,若类上有注解,才会扫描类内的方法。

@Configuration
public class BeanConfig {@Bean//存在Spring容器里的对象名默认是 namepublic String name(){return "塞尔达";}@Bean//存在Spring容器里的对象名默认是 name333public String name333(){return "达";}
}

【多个@Bean】

  • 当一个类型存在多个Bean时,就不能用类型获取对象了,会报NoUniqueBeanDefinitionException。对象名默认是@Bean修饰的方法名,这时可以用名称获取,或名称加类型,唯独不能用类型获取对象。
  • 因为两个对象是同类型,Spring不知道取哪个使用名称,不会出错。

根据方法加类型获取,不需要强转。
根据类型获取,不需要强转。
根据方法获取,不需要强转。

3.8 重命名Bean

可以通过设置name属性给Bean对象进行重命名操作。

//重命名的对象名可以有多个
@Bean(name = {"u1","user1"})
public User user1(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;
}
//简化版本
@Bean({"u1","user1"})
public User user2(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;
}
//只有一个名称时,{}可以省略
@Bean("u1")
public User user3(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;
}

3.9 更改扫描路径@ComponentScan

启动类(Application)的扫描范围:默认路径是启动类所在的目录极其子目录。
将启动类放入子目录,再次启动,会报错。

src/main/java
└── com└── example└── myapp  ← 这是项目的根包(root package)├── Application.java  ← 启动类应该放在这├── controller│   └── UserController.java├── service│   └── UserService.java └── repository└── UserRepository.java
src/main/java
└── com└── example└── myapp├── config│   └── Application.java  ← 错误:启动类在子包├── controller├── service└── repository

【扫描范围对比】

  • 当启动类在 com.example.myapp 时:

    • 自动扫描:com.example.myapp 及其所有子包(controller/service/repository)
  • 当启动类在 com.example.myapp.config 时:

    • 自动扫描:com.example.myapp.config 及其子包
    • 不会扫描:com.example.myapp.controller 等兄弟包

用注解指定路径:@ComponentScan(“com.example”)
在这里插入图片描述
属于Spring的注解才会被Spring管理,@Data不是Spring的注解。
先扫描@ComponentScan(“com.example”)确定路径。


4. DI

4.1 属性注入

不写@Autowired会报异常。

@Controller
public class UserController {@Autowiredprivate UserService userService;public void doController(){userService.doService();System.out.println("do Controller...");}}

4.2 构造方法注入

只有一个,可以省略掉@Autowired。如果存在多个构造方法,必须用@Autowired指定。

@Controller
public class UserController2 {//构造方法注入private final UserService userService;@Autowired // Spring 4.3+ 可省略@Autowired(当只有一个构造方法时)public UserController2(UserService userService) {this.userService = userService;}public void sayHi() {System.out.println("hi, UserController2...");userService.sayHi(); }
}

有无参构造方法时Spring会优先用无参。


4.3 Setter注入

就算只有一个set方法也不能省略@Autowired。

【@Autowired 的作用】
明确告诉 Spring 此方法需要依赖注入。若省略,Spring会认为这是一个普通方法,不会自动调用它来注入依赖。

【Setter 注入的本质】
需要 Spring 主动调用 Setter 方法并传入依赖对象,而 @Autowired就是触发这一行为的标记。

@Controller
public class UserController3 {// 注入方式3:Setter方法注入private UserService userService;@Autowired // 必须保留注解(即使只有一个Setter方法)public void setUserService(UserService userService) {this.userService = userService;}public void sayHi() {System.out.println("hi, UserController3...");userService.sayHi(); // 调用Service方法}
}

4.4 @Autowired存在的问题

为什么官方不推荐用属性注入?
属性注入以类型进行匹配,与注入的属性名称无关。
无法注入final修饰的属性,定义时就需要赋值或者构造方法中赋值。

如何解决:

  • @Primary
  • @Qualifier
  • @Resource

4.5 三种方式的区别

static类型的变量与类无关,就算没有类他也存在,只是指定了一个路径而已。

http://www.dtcms.com/a/336556.html

相关文章:

  • 嵌入式开发入门—电子元器件~半导体
  • Linux中iSCSI存储配置与管理指南
  • Java的网络攻防仿真演练系统
  • 深度学习·GFSS
  • C语言字符串操作汇总
  • 线程相关知识
  • NokoPrint:安卓平台上的便捷无线打印解决方案
  • 存储引擎 InnoDB
  • 【Python】Python 面向对象编程详解​
  • k8s-单主机Master集群部署+单个pod部署lnmp论坛服务(小白的“升级打怪”成长之路)
  • 集成电路学习:什么是SIFT尺度不变特征变换
  • oom 文件怎么导到visualvm分析家
  • 双指针和codetop2(最短路问题BFS)
  • 闭区间是否存在一个开区间包含之
  • ESP32S3在圆形240x240 1.8寸GC9A01 SPI显示屏显示双眼睛表情
  • 寻找数组的中心索引
  • ai测试(六)
  • [Java恶补day50] 174. 地下城游戏
  • 数据结构03(Java)--(递归行为和递归行为时间复杂度估算,master公式)
  • 数学建模 13 SVM 支持向量机
  • 原子操作及基于原子操作的shared_ptr实现
  • PYTHON让繁琐的工作自动化-PYTHON基础
  • 【撸靶笔记】第五关:GET - Double Injection - Single Quotes - String
  • 基于STM32单片机智能RFID刷卡汽车位锁桩设计
  • Qt同步处理业务并禁用按钮
  • linux系统------kubenetes单机部署
  • LeetCode 分类刷题:2962. 统计最大元素出现至少 K 次的子数组
  • 5G虚拟仿真平台
  • [激光原理与应用-292]:理论 - 波动光学 - 驻波的本质是两列反向传播的相干波通过干涉形成的能量局域化分布
  • 安全多方计算(MPC)简述