Spring IoC&DI~

一、Spring IoC&DI

1. Spring 是什么?

通过前面的学习,我们知道了Spring是一个开源框架,他让我们的开发更加简单,他支持广泛的应用场景,有着活跃而庞大的社区,这也是Spring能够长久不衰的原因,但是这个概念相对来说,还是比较抽象

spring中的核心思想是IoC和AOP

我们用一句更具体的话来概括Spring,那就是: Spring 是包含了众多工具方法的 IoC 容器

1. 什么是容器?

生活中的水杯,垃圾桶,冰箱等等这些都是容器

在编程语言中,Map,List等都是容器

2. 什么是 IoC?

IoC 是Spring的核心思想,也是常见的面试题,那什么是IoC呢?

其实IoC我们在前面已经使用了,我们在前面讲到,在类上面添加 @RestController 和

@Controller 注解,就是把这个对象交给Spring管理,Spring 框架启动时就会加载该类,把对象交给Spring管理,就是IoC思想

IoC:Inversion of Control (控制反转),也就是说 Spring 是一个控制反转的容器

什么是控制反转呢?也就是控制权反转,什么的控制权发生了反转?获得依赖对象的过程被反转了,也就是说,当需要某个对象时,传统开发模式中需要自己通过 new 创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入 (Dependency Injection,DI)就可以了,这个容器称为:IoC容器,Spring是一个IoC容器,所以有时Spring 也称为Spring 容器

2. IoC 介绍

接下来我们通过案例来了解一下什么是IoC

需求:造一辆车

1. 传统程序开发

先设计轮子(Tire),然后根据轮子的大小设计底盘(Bottom),接着根据底盘设计车身(Framework),最后根据车身设计好整个汽车(Car),这里就出现了一个依赖关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子

java 复制代码
public class Main {

    public static void main(String[] args) {
        Car car = new Car(20);
        car.run();
    }
}


public class Car {
    private Framework framework;
    public Car(int 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(int size){
        this.bottom = new Bottom(size);
        System.out.println("framework init");
     }
}

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

    }
}

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

2. 问题分析

这样的设计看起来没问题,但是可维护性却很低

接下来需求有了变更:随着对的车的需求量越来越大,个性化需求也会越来越多,我们需要加工多种尺寸的轮胎,那么由于依赖性,Tire之前的代码都要就行改动,耦合性高

3. 解决方案

我们尝试换一种思路,我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子,这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身,车身依赖汽车

基于以上思路,我们把调用汽车的程序示例改造一下,把创建子类的方式,改为注入传递的方式

java 复制代码
public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire(20, "red");
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}

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");
    }
}

public class Framework {
    private Bottom bottom;

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

public class Bottom {
    private Tire tire;

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

public class Tire {
    private int size;

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

代码经过以上调整,无论底层类如何变化,整个调用链是不用做任何改变的,这样就完成了代码之间的解耦,从而实现了更加灵活、通用的程序设计了

4. IoC 优势

在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire

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

我们发现了一个规律,通用程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了Framework,Framework 创建并创建了 Bottom,依次往下,而改进之后的控制权发生的反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了,这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转 ,也就是 IoC 的实现思想

IoC容器的优点:

解耦: 您的业务逻辑代码不再需要关心对象的实例化和依赖查找,只专注于业务实现

可维护性高: 依赖关系集中在配置中,修改依赖时不需要修改源代码

3. DI 介绍

DI: Dependency Injection(依赖注入)

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

程序运行时需要某个资源,此时容器就为其提供这个资源

从这点来看,依赖注入(DI)和控制反转(IoC)是从不同的角度的描述的同一件事情,依赖注入是从应用程序的角度来描述,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦

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

IoC 是一种思想,也是目标, 而思想只是一种指导原则,最终还是要有可行的落地方案,而 DI 就属于具体的实现。所以也可以说,DI 是IoC的一种实现

二、 IoC & DI 使用

既然 Spring 是一个 IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能: 存和取

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

目标:把BookDao,BookService 交给Spring管理,完成Controller层、Service层、Dao层的解耦

步骤:

  1. Service层及Dao层的实现类,交给Spring管理: 使用注解: @Component
  2. 在Controller层 和Service层 注入运行时依赖的对象: 使用注解 @Autowired
java 复制代码
@Component
public class BookDao {
    public List<BookInfo> mockData(){
        List<BookInfo> bookInfos = new ArrayList<>();
        for (int i=1; i<=15; i++){
            BookInfo bookInfo = new BookInfo();
            bookInfo.setId(i);
            bookInfo.setBookName("图书"+ i);
            bookInfo.setBookAuthor("作者"+i);
            bookInfo.setCount(new Random().nextInt(100));
            bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
            bookInfo.setPublish("出版社"+i);
            bookInfo.setStatus(i%5==0?2:1);
            bookInfos.add(bookInfo);
        }
        return bookInfos;
    }
}


@Component
public class BookService {
    @Autowired
    private BookDao bookDao;
    public List<BookInfo> getList(){
//        BookDao bookDao = new BookDao();
        List<BookInfo> bookInfos = bookDao.mockData();
        //处理图书状态
        for (BookInfo bookInfo:bookInfos){
            //此处只考虑1和2两种情况
            if (bookInfo.getStatus()==1){
                bookInfo.setStatusCN("可借阅");
            }else {
                bookInfo.setStatusCN("不可借阅");
            }
        }
        return bookInfos;
    }
}

@RestController
@RequestMapping("/book")
public class BookController {
    @Autowired
    BookService bookService;
    @RequestMapping("/getList")
    public List<BookInfo> getList() {
        //查询图书信息, 并返回
//        BookService bookService = new BookService();
        List<BookInfo> bookInfos = bookService.getList();
        return bookInfos;
    }
}

三、IoC 详解

通过上面的案例,我们已经知道了Spring IoC 和DI的基本操作,接下来我们来系统的学习Spring IoC和DI的操作

前面我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。也就是bean的存储

1. Bean的存储

Spring框架为了更好的服务web应用程序,提供了丰富的注解

共有两类注解类型可以实现:

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

@Controller(控制器存储)

java 复制代码
public class UserContraller {

    public String sayHello() {
        return "hello";
    }
}

获取bean的三种方式


@Service(服务存储)

java 复制代码
@Service
public class UserService {
    public String doservice() {
        return "do service...";
    }
}


@Repository(仓库存储)

java 复制代码
@Repository
public class UserRepository {
    public String dorepository() {
        return "dorepository";
    }
}

@Component(组件存储)

java 复制代码
@Component
public class UesrComponent {
    public String doComponent() {
        return "doComponent";
    }
}

@Configuration(配置存储)

java 复制代码
@Configuration
public class UesrConfig {
    public String doconfig() {
        return "doconfig";
    }
}

2. 为什么要这么多类注解?

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

• @Controller:表现层,接收请求,对请求进行处理,并进行响应

• @Servie:业务逻辑层,处理具体的业务逻辑

• @Repository:数据访问层,也称为持久层,负责数据访问操作

• @Configuration:配置层,处理项目中的一些配置信息
类注解之间的关系

其实这些注解里面都有一个注解 @Component ,说明它们本身就是属@Component 的"子类"。@Component 是一个元注解,也就是说可以注解其他类注解,如 @Controller,@Service ,@Repository 等. 这些注解被称为 @Component 的衍生注解

3. 方法注解 @Bean

类注解是添加到某个类上的, 但是存在两个问题:

  1. 使用外部包里的类, 没办法添加类注解
  2. 一个类, 需要多个对象, 比如多个数据源
    这种场景, 我们就需要使用方法注解 @Bean

1. 方法注解要配合类注解使用

java 复制代码
@ComponentScan(basePackages = "com.bit.ioc")
@Configuration
public class BeanConfig {
    @Bean({"u2", "userInfo2"})
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 17);
    }
}
java 复制代码
UserInfo bean2 = context.getBean(UserInfo.class);
		System.out.println(bean2);

2. 定义多个对象

java 复制代码
@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan", 16);
    }
    @Bean
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 17);
    }
}
java 复制代码
UserInfo bean2 = context.getBean(UserInfo.class);
		System.out.println(bean2);

报错信息显示:期望只有一个匹配,结果发现了两个,userInfo,userInfo2

从报错信息中,可以看出来,@Bean 注解的bean,bean的名称就是它的方法名

接下来我们根据名称来获取bean对象

java 复制代码
UserInfo bean1 = (UserInfo) context.getBean("userInfo");
		System.out.println(bean1);
		UserInfo bean2 = (UserInfo) context.getBean("userInfo2");
		System.out.println(bean2);

3. 重命名 Bean

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

java 复制代码
@Bean({"u2", "userInfo2"}) 
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 17);
    }
java 复制代码
UserInfo bean1 = context.getBean("u2", UserInfo.class);
		System.out.println(bean1);
		UserInfo bean2 = context.getBean("userInfo2", UserInfo.class);
		System.out.println(bean2);

4. 扫描路径

使用前面学习的四个注解声明的bean,一定会生效吗?

不一定,bean想要生效,还需要被Spring扫描


加上@ComponentScan(basePackages = "com.flower.ioc")

在配置类上添加 @ComponentScan 注解,该注解默认会扫描启动类所在的包下所有的配置类
把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到

四、 DI 详解

依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象,我们使用 @Autowired 这个注解,完成依赖注入的操作,简单来说,就是把对象取出来放到某个类的属性中

关于依赖注入,Spring也给我们提供了三种方式:

  1. 属性注入
  2. 构造方法注入
  3. Setter 注入

1. 属性注入

属性注入是使用 @Autowired 实现的,将 Service 类注入到 Controller 类中

java 复制代码
//属性注入
    @Autowired
    private UserService userService;
    @Autowired
    private UserRepository userRepository;
    public void sayHello(){
        userService.doService();
        userRepository.doRepository();
        System.out.println("do controller");
    }
java 复制代码
     UserController bean = context.getBean(UserController.class);
		bean.sayHello();

2. 构造方法注入

java 复制代码
    private UserService userService;
    private UserRepository userRepository;
    public UserController() {
        System.out.println("执行UserController 无参构造方法....");
    }
    public UserController(UserService userService) {
        System.out.println("执行UserController 有参构造方法1....");
        this.userService = userService;
    }
    public UserController(UserRepository userRepository) {
        System.out.println("执行UserController 有参构造方法3....");
        this.userRepository = userRepository;
    }

    @Autowired
    public UserController(UserService userService, UserRepository userRepository) {
        System.out.println("执行UserController 有参构造方法2....");
        this.userService = userService;
        this.userRepository = userRepository;
    }

若有一个构造方法,则执行该构造方法

若有多个构造方法,必须要有无参构造,否则报错,通过 @Autowired 明确指定到底使用哪个构造方法

3. Setter 注入

java 复制代码
    private UserService userService;
    private UserRepository userRepository;
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

4. 三种注入优缺点分析

属性注入

优点: 简洁,使用方便;

缺点:

1.只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现空指针异常

2.不能注入一个Final修饰的属性
构造函数注入

优点:

1.可以注入final修饰的属性

2.注入的对象不会被修改

3.依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法

4.通用性好,构造方法是JDK支持的,所以更换任何框架,他都是适用的

缺点:

注入多个对象时,代码会比较繁琐
Setter注入

优点: 方便在类实例之后,重新对该对象进行配置或者注入

缺点:

1.不能注入一个Final修饰的属性

2.注入对象可能会被改变,因为setter方法可能会被多次调用, 就有被修改的风险

5. @Autowired存在问题

当同一类型存在多个bean时,使用 @Autowired 会存在问题

java 复制代码
@Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan", 16);
    }

    @Bean({"u2", "userInfo2"})
    public UserInfo userInfo2(){
        return new UserInfo("lisi", 17);
    }
java 复制代码
    @Autowired
    private UserInfo u1;
    public void doService() {
        System.out.println(u1);
        System.out.println("doService");
    }

报错的原因是,非唯一的 Bean 对象

如何解决上述问题呢?Spring提供了以下几种解决方案

@Primary

@Qualifier

@Resource

1. @Primary

java 复制代码
    @Bean({"u2", "userInfo2"})
    @Primary
    public UserInfo userInfo2(){
        return new UserInfo("lisi",   17);
    }

2. @Qualifier

java 复制代码
    @Autowired
    @Qualifier("u2")
//    @Resource(name = "u2")
    private UserInfo u1;
    public void doService() {
        System.out.println(u1);
        System.out.println("doService");
    }

3. @Resource

java 复制代码
    @Resource(name = "u2")
    private UserInfo u1;
    public void doService() {
        System.out.println(u1);
        System.out.println("doService");
    }

4. 若将引入的u1改为方法名,结果会如何呢?

java 复制代码
    @Autowired
    private UserInfo userInfo;
    public void doService() {
        System.out.println(userInfo);
        System.out.println("doService");
    }
java 复制代码
@Autowired
    private UserInfo userInfo2;
    public void doService() {
        System.out.println(userInfo2);
        System.out.println("doService");
    }


由此可见,方法名也可以避免冲突
常见面试题:

@Autowird 与 @Resource的区别

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

@Autowired 默认是按照类型注入,而@Resource是按照名称注入. 相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean

五、总结

1. Spring, Spring Boot 和Spring MVC的关系以及区别

Spring :简单来说,Spring 是一个开发应用框架,什么样的框架呢,有这么几个标签:轻量级、一站式、模块化,其目的是用于简化企业级应用程序开发
Spring MVC :Spring MVC是Spring的一个子框架,Spring诞生之后,大家觉得很好用,于是按照MVC模式设计了一个 MVC框架(一些用Spring 解耦的组件),主要用于开发WEB应用和网络接口,所以,Spring MVC 是一个Web框架
Spring Boot : Spring Boot是对Spring的一个封装,为了简化Spring应用的开发而出现的,中小型企业,没有成本研究自己的框架,使用Spring Boot 可以更加快速的搭建框架,降级开发成本,让开发人员更加专注于Spring应用的开发,而无需过多关注XML的配置和一些底层的实现

当你创建一个现代的 Java Web 项目(使用 Spring Boot)时,实际发生的流程是:

Spring Boot 启动,扫描项目依赖。

它发现你有 spring-boot-starter-web,于是自动配置好 Spring MVC(配置 DispatcherServlet)

它初始化 Spring Framework 的容器(ApplicationContext),开始管理你的 Bean。

它启动内置的 Tomcat 服务器,监听 8080 端口

当请求进来时,Spring MVC 的 Controller 接收请求,调用由 Spring 管理的 Service 进行业务处理

Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的一个
MVC 框架,而Spring Boot 是基于Spring的一套快速开发整合包

2. bean 的命名

  1. 五大注解存储的bean
    前两位字母均为大写, bean名称为类名
    其他的为类名首字母小写
    通过 value属性设置 @Controller(value = "user")
  2. @Bean 注解存储的bean
    bean名称为方法名
    通过name属性设置 @Bean(name = {"u2","userInfo2"})

3. 常见面试题

  1. 三种注入方式的优缺点(参考上面)
  2. 常见注解有哪些? 分别是什么作用?
    web url映射: @RequestMapping
    参数接收和接口响应: @RequestParam, @RequestBody, @ResponseBody
    bean的存储: @Controller, @Service, @Repository, @Component, @Configuration,
    @Bean
    bean的获取: @Autowired, @Qualifier, @Resource
  3. @Autowired 和 @Resource 区别
  4. 说下你对Spring,SpringMVC,Springboot的理解(参考上面)

本期内容到此为止,喜欢的话请点个赞,谢谢观看!!!

相关推荐
曹牧6 分钟前
文档格式:OFD
java
豆角焖肉31 分钟前
Maven进阶与搭建私服
java·maven
空中湖32 分钟前
Spring AI Agent 编排:ReAct 模式 + 多 Agent 协作实战
人工智能·spring·react.js
大不点wow42 分钟前
Java序列化与反序列化:让对象走出JVM
java·开发语言·jvm
止语Lab1 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活1 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
噢,我明白了1 小时前
Java中日期和字符串的处理
java·开发语言·日期
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
dkbnull1 小时前
Spring Boot请求处理组件对比详解
java·spring boot