Spring IoC&DI

Spring IoC&DI_第1张图片

文章目录

  • 前言
  • 什么是Spring
    • 1. 什么是 IoC 容器
      • 1.1 什么是容器
      • 1.2 什么是 IoC
    • 2. 什么是DI
  • IoC & DI 的使用
  • IoC详解
    • Bean的存储
      • @Controller注解
      • 如何获取Bean
        • 1. 根据Bean的名称获取Bean
        • 2. 根据Bean类型获取Bean
        • 3. 根据Bean名和Bean类型获取Bean
      • @Service注解
      • @Repository注解
      • @Component注解
      • @Configuration注解
      • 为什么会有这么多类注解
      • 方法注解
      • 重命名Bean
      • 扫描路径
  • DI 详解
    • 1. 属性注入
    • 构造方法注入
    • Setter 注入
    • 三种注入的优缺点
    • Autowired 存在的问题

前言

前面我们大概知道了什么是 Spring,以及 Spring 家族中 Spring Boot 和 Spring MVC的开发,但是 Spring 到底是什么呢?

什么是Spring

前面我为大家简单介绍了什么是 Spring 【Spring】什么是Spring,不过前面的介绍较为简单,要想知道Spring 的原理,这些知识不不足以帮助我们了解 Spring 的,所以这篇文章我将详细为大家介绍什么是 Spring。

通过前面的学习,我们知道了 Spring 是一个开源的框架,它让我们的开发变得更加简单,它支持广泛的应用场景,有着活跃而庞大的社区,这也是 Spring 能够经久不衰的原因。

但是这个概念对于我们来说,还是太抽象了,用一句话概括:Spring 是包含了众多工具的 IoC 容器。那么什么是 IoC 容器呢?

1. 什么是 IoC 容器

1.1 什么是容器

容器是指能够容纳某种物品的装置。在生活中,储物箱、垃圾桶、冰箱等这些都属于容器,而在计算机中,我们前面学习的List/map就是数据存储的容器,Tomcat就是Web容器。

1.2 什么是 IoC

IoC 是 Spring 的核心思想。

IoC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IoC这个概念。对于面向对象设计及编程的基本思想,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。

在传统的程序设计中,对象的创建和管理都是由代码直接完成的。而在IoC中,对象的创建和管理权交给了IoC Service Provider(IoC思想的具体实现),我们只需要告诉它需要什么对象,它就会为我们准备好。这种机制的引入,使得应用程序的各个部分之间的依赖关系变得非常清晰,并且可以将各个部分解耦,提高代码的可重用性和可维护性。

给大家举个例子,传统的汽车开发过程是这样的:

在这里插入图片描述
用代码体现就是这样的:

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

    /**
     * 汽车对象
     */
    static class Car {
        private Framework framework;
        public Car() {
            framework = new Framework();
            System.out.println("Car init...");
        }

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

    /**
     * 车身类
     */
    static class Framework {
        private Bottom bottom;

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

    /**
     * 底盘类
     */
    static class Bottom {
        private Tire tire;

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

    /**
     * 轮胎类
     */
    static class Tire {
        private int size;

        public Tire() {
            this.size = 17;
            System.out.println("轮胎尺寸:" + size);
        }
    }
}

如果我们在造车的时候,需要造车的一方指定轮胎大小的话,那么这个生产车的代码进行较大的改动。

在这里插入图片描述
Spring IoC&DI_第2张图片
Spring IoC&DI_第3张图片
Spring IoC&DI_第4张图片
Spring IoC&DI_第5张图片
可以看到,当需要造车方指定轮胎的大小的时候,基本上所有的零件的代码都需要做出更改,这就叫做 高耦合

什么叫做高内聚、低耦合呢?

相比大家经常会听到高内聚、低耦合这句话吧,那么它们到底代表的什么意思呢?

“高内聚、低耦合”是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。

  • 高内聚:内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。一个模块内各个元素彼此结合的紧密程度高,则内聚性高。所谓高内聚就是一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
  • 低耦合:耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。一个程序结构中各模块的内聚程度越高,模块间的耦合程度就越低。

上面我们设计的代码的耦合程度就比较高,那么应该如何降低耦合度呢?

我们可以将各个零件之间的依赖关系给改变一下。

在这里插入图片描述
我们先根据需要,创造出指定大小的轮胎,然后将造好的轮胎给底盘创造厂,然后再造好底盘,将造好的底盘交给车身制造厂,制造出车身,最后将造好的车身交给汽车制造厂,最终制造出来一个汽车。

public class NewCarExample {
    public static void main(String[] args) {
        Tire tire = new Tire(20);
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }

    /**
     * 汽车对象
     */
    static 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...");
        }
    }

    /**
     * 车身类
     */
    static class Framework {
        private Bottom bottom;

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

    /**
     * 底盘类
     */
    static class Bottom {
        private Tire tire;

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

    /**
     * 轮胎类
     */
    static class Tire {
        private int size;

        public Tire(int size) {
            this.size = size;
            System.out.println("轮胎尺寸:" + size);
        }
    }
}

通过更改各个类之间的依赖关系,那么就算底层轮胎如何变化,也不会影响整个产业链,这样就实现了代码之间的解耦,从而实现了更加灵活、通用的程序设计了。

通过上面的优化,我们发现:类的创建顺序是相反的,之前是 Car 控制并创建了 Framework,Framework 创建并控制创建了 Bottom,Bottom 创建并控制创建了 Tire,改进之后的控制权发生了反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入到当前对象中,依赖对象的控制权不再由当前类控制了。

这样,即使依赖对象发生任何变化,当前类都是不受影响的,这就是典型的控制反转,也就是是 IoC 的实现思想。

知道了什么是容器以及什么是 IoC 之后我们就知道了什么叫做 IoC 容器了。

Spring IoC&DI_第6张图片
IoC 容器的优点:

通过上面的案例我们可以看出来,使用 IoC 容器,资源不再由使用资源的双方管理,而是由不使用资源的第三方进行管理,这样可以带来以下好处:1. 实现资源的集中统一管理;2. 降低了使用资源的双方的依赖程度,也就是耦合程度。

  1. 资源集中管理:IoC容器会帮我们管理一些资源(对象)等,我们在使用的时候只需要从IoC中去取就可以了。
  2. 我们在创建实例的时候不需要了解其中的具体细节,降低了使用资源的双方的依赖程度(耦合程度)。

2. 什么是DI

DI(Dependency Injection)即依赖注入,是面向对象编程中的一种设计模式,用来减少代码之间的耦合度。

具体来说,依赖注入将对象的创建和管理权从代码中转移到了外部容器,通过外部容器来创建对象并注入需要的依赖。这种方式可以降低代码的耦合度,提高代码的可重用性和可维护性。

在Java中,Spring框架是使用依赖注入最广泛的开源框架之一。通过使用依赖注入,Spring可以将应用程序中的各个组件解耦,使得它们之间的依赖关系变得更加清晰和易于管理。

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

从这点来看,依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。

在造汽车的过程中,将 Tire 这个依赖注入到 Bottom 中造出 Bottom,然后将造好的 Bottom 依赖注入到 Framework 中造出 Framework,最后将造好的 Framework 依赖注入到 Car 中,最终创建出 Car。

IoC 是⼀种思想,也是"⽬标",⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就属于具体的实现。所以也可以说, DI 是 IoC 的⼀种实现。

IoC & DI 的使用

Spring 既然是一个 IoC 容器,那么他肯定具有两个基本的功能:存和取。

Spring 容器管理的主要是对象,这些对象我们称之为“Bean”,这个跟我们前面学习的 Bean 不一样。我们把这些 Bean 交给 Spring 进行管理,由 Spring 来负责对象的创建和销毁,我们在写 Spring 代码的时候只需要告诉 Spring,哪些对象是我们要交给 Spring 管理,我们又要取出哪些对象进行使用。

那么在 Spring 中,如何存储和取出 Bean 呢?

  1. 将类存储进 Spring IoC 容器中需要使用 @Component 注解,其实还有很多注解,这里我们先为大家介绍这个注解,本文后面再为大家介绍另外几种存储 Bean 的注解
  2. 取出依赖对象使用注解 @Autowired
package com.example.springiocdi20231209;

import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    public void sayHi() {
        System.out.println("hello spring");
    }
}
package com.example.springiocdi20231209;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/component")
public class GetMessage {
    @Autowired
    private UserComponent userComponent;
    
    @RequestMapping("/get")
    public void get() {
        userComponent.sayHi();
    }
}

Spring IoC&DI_第7张图片
在这里插入图片描述
这里显示出了我们想要的结果,就说明我们使用 @Componnet 注解和 @Autowired 注解对 Bean 实现了存储和取出。

需要注意的是:当我们在使用 @Autowired 注解的时候,需要保证这个类有 Controller 或者 RestController 注解,因为我们既然要想使用 Spring 的 IoC 容器肯定要保证这个类是被 Spring 管理的。

IoC详解

上面为大家展示了 IoC 和 DI 的基本使用,接下来将为大家详细的讲解一下 IoC。

Bean的存储

上面我们存储 Bean 使用的是 @Component 注解,而 Spring 框架为了更好的服务 Web 应用程序,提供了更丰富的注解。

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

@Controller注解

package com.example.springiocdi20231209.Controller;

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    public void sayHi() {
        System.out.println("hi, spring");
    }
}

使用 @Controller 就将这个 Bean 给存储到 IoC 容器中了,那么我们如何获取这个 Bean 呢?

如何获取Bean

获取 Bean 的方法有很多种,我们只要介绍下面的第1、2、4种。
Spring IoC&DI_第8张图片

1. 根据Bean的名称获取Bean

我们可以根据 Bean 的名字来获取到指定的 Bean,但是某个 Bean 的名称是什么,我们该怎么知道呢?我们来看看官方的解释。

Spring IoC&DI_第9张图片
Spring IoC&DI_第10张图片
Spring IoC&DI_第11张图片
简单来讲就是当类名中前两个字母中大写字母小于2的时候,那么该类交给 IoC 后就会以第一个字母小写的小驼峰形式命名,当类名的前两个字母都为大写字母的时候,那么该 Bean 名就是原类名。

所以要以 Bean 名获取到 UserController 这个 Bean 的话,就需要将 userController 作为参数。

package com.example.springiocdi20231209;

import com.example.springiocdi20231209.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringIoCDi20231209Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
        UserController userController = (UserController) context.getBean("userController");
    }
}

我们这个代码是在 项目名称+Application 这个类中写的,准确来说是在有 @SpringBootApplication 这个注解的类中写的,并且 SpringApplication.run() 方法是可以有返回值也可以没有返回值的,我们可以根据需要使用变量来接收这个方法的返回值。要想获取到 IoC 容器中的 Bean,需要依靠 ApplicationContext 这个类,所以我们就用这个类的变量来接收 run 方法的返回值。

Spring IoC&DI_第12张图片

启动项目的时候,就会发现我们预想中的结果出现在了控制台中,并且这个不需要我们发送什么 Http 请求,而是启动项目就会自动执行这个类当中的代码。并且通过 Bean 名获取到的 Bean 名返回的是一个 Object 类型,所以在拿变量进行接收的时候就需要进行类型的转换。

2. 根据Bean类型获取Bean

可以通过 Bean 类型来获取到 Bean。

package com.example.springiocdi20231209;

import com.example.springiocdi20231209.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringIoCDi20231209Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
        //1. 根据Bean名字来获取Bean
        //UserController userController = (UserController) context.getBean("userController");
        //2. 根据Bean类型来获取Bean
        UserController userController = context.getBean(UserController.class);
        userController.sayHi();
    }
}

Spring IoC&DI_第13张图片

3. 根据Bean名和Bean类型获取Bean

通过 Bean 名获取 Bean 需要进行类型的转换,可以在传递参数的时候就指定返回值的类型。

package com.example.springiocdi20231209;

import com.example.springiocdi20231209.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringIoCDi20231209Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
        //1. 根据Bean名字来获取Bean
        //UserController userController = (UserController) context.getBean("userController");
        //2. 根据Bean类型来获取Bean
        //UserController userController = context.getBean(UserController.class);
        //3. 根据Bean名和Bean类型获取Bean
        UserController userController = context.getBean("userController", UserController.class);
        userController.sayHi();
    }
}

Spring IoC&DI_第14张图片

@Service注解

通过这个注解,也可以将类交给 Spring 进行管理。

package com.example.springiocdi20231209.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;

@Service
public class UserController {
    public void sayHi() {
        System.out.println("hi, spring");
    }
}

Spring IoC&DI_第15张图片

@Repository注解

package com.example.springiocdi20231209.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

@Repository
public class UserController {
    public void sayHi() {
        System.out.println("hi, spring");
    }
}

Spring IoC&DI_第16张图片

@Component注解

package com.example.springiocdi20231209.Controller;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

@Component
public class UserController {
    public void sayHi() {
        System.out.println("hi, spring");
    }
}

Spring IoC&DI_第17张图片

@Configuration注解

package com.example.springiocdi20231209.Controller;

import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

@Configuration
public class UserController {
    public void sayHi() {
        System.out.println("hi, spring");
    }
}

Spring IoC&DI_第18张图片

为什么会有这么多类注解

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

  • @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应
  • @Servie:业务逻辑层,处理具体的业务逻辑
  • @Repository:数据访问层,也称为持久层.负责数据访问操作
  • @Configuration:配置层.处理项⽬中的⼀些配置信息

Spring IoC&DI_第19张图片

并且通过观察这五个注解的源码我们可以发现一些问题。

Spring IoC&DI_第20张图片
Spring IoC&DI_第21张图片
Spring IoC&DI_第22张图片
Spring IoC&DI_第23张图片
Spring IoC&DI_第24张图片
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:

其实这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于@Component 的"⼦类".@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service ,@Repository 等.这些注解被称为 @Component 的衍⽣注解.

@Controller @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更好的选择。

方法注解

类注解是写在我们项目代码中的类上的,但是存在两个问题:

  1. 使用外部包里的类,没办法添加类注解
  2. 一个类,需要多个对象,比如多个数据源

上面两个问题是无法使用类注解来解决的。所以也就出现了方法注解 @Bean

假设我们这里的 User 类是一个外部包里的类,那么我们就无法在这个类中添加类注解,这是就需要使用到方法注解。

package com.example.springiocdi20231209;

import org.springframework.context.annotation.Bean;

public class BeanConfig {
    @Bean
    public User user() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}

运行 @SpringApplication 注解的代码,看看什么效果。

在这里插入图片描述
这里报错说这个 Bean 没有被定义。其实使用方法注解 @Bean 的时候,需要保证该方法所在的类也有被类注解注释。

package com.example.springiocdi20231209;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class BeanConfig {
    @Bean
    public User user() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}
package com.example.springiocdi20231209;

import com.example.springiocdi20231209.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringIoCDi20231209Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

Spring IoC&DI_第25张图片

同一个类定义多个对象。

package com.example.springiocdi20231209;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class BeanConfig {
    @Bean
    public User user() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("liis");
        user.setAge(20);
        return user;
    }
}

当我们使用方法注解,并且一个类有多个相同类型的 Bean 类型的时候,并且我们通过 Bean 类型获取 Bean 的话就会出错。

在这里插入图片描述
所以这里获取 Bean 的话就需要指定 Bean 名称。

User user = context.getBean("user2", User.class);

Spring IoC&DI_第26张图片

@Bean 注解的 Bean,Bean 的,名称就是方法名。

重命名Bean

前面我们呢说了 Bean 的默认名称,但其实我们可以指定 Bean 的名称。那么如何重命名 Bean 呢?

@Bean("beanName")
package com.example.springiocdi20231209;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class BeanConfig {
    @Bean("u1")
    public User user() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

    @Bean("u2")
    public User user2() {
        User user = new User();
        user.setName("liis");
        user.setAge(20);
        return user;
    }
}
ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
User user = context.getBean("u2", User.class);
System.out.println(user);

Spring IoC&DI_第27张图片
可以看到我们通过重命名的名字u2获取到了Bean。

不仅如此,通过观察 @Bean 的源码我们可以发现,这里的name参数是一个字符串数组,也就是说一个 Bean 可以有多个名字。

Spring IoC&DI_第28张图片

@Bean({"u2", "s2"})

Spring IoC&DI_第29张图片

同样的类注解也可以重命名,但是类注解只支持一个名字。

Spring IoC&DI_第30张图片

@Configuration("c1")
public class UserController {
    public void sayHi() {
        System.out.println("hi, spring");
    }
}

Spring IoC&DI_第31张图片

扫描路径

其实并不是项目下的所有文件中的加了注解的类都会被 Spring 进行管理,而是需要看扫描路径在哪。假设我们将 @SpringBootApplication 注解所在的类给换个路径。

Spring IoC&DI_第32张图片

Spring IoC&DI_第33张图片

Spring IoC&DI_第34张图片

@SpringBootApplication
public class SpringIoCDi20231209Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
        User user = context.getBean("u1", User.class);
        System.out.println(user);
    }
}

在这里插入图片描述
这里就报错说找不到 u1 这个 Bean,说明这个注解没有被扫描到,那么为什么呢?

这其实跟 @ComponentScan 注解配置的扫描路径有关,但是我们 SpringBootApplication 注解的类当中不是没有这个注解吗?其实这个注解继承了 @ComponentScan 注解。

Spring IoC&DI_第35张图片
而如果 @ComponnetScan 没有配置的话,就默认的是当前 @ComponentScan 注解的文件所在的路径。

这里 @SpringBootApplication 注解的类所在的路径是这个 package com.example.springiocdi20231209.springiocdi20231209.Controller;

而我们的 u1 Bean 所在的路径是 package com.example.springiocdi20231209.springiocdi20231209;,所以这个 Bean 是 Component 无法扫描到的。

要想扫描到这个路径,我们可以对 @ComponentScan 注解进行配置。

@ComponentScan({"com.example.springiocdi20231209.springiocdi20231209"})
@SpringBootApplication
public class SpringIoCDi20231209Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIoCDi20231209Application.class, args);
        User user = context.getBean("u1", User.class);
        System.out.println(user);
    }
}

Spring IoC&DI_第36张图片
@ComponentScan 也是可以配置多个扫描路径的。

DI 详解

依赖注⼊是⼀个过程,是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源,⽽资源指的就是对象。在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作。简单来说,就是把对象取出来放到某个类的属性中。

在⼀些⽂章中,依赖注⼊也被称之为"对象注⼊",“属性装配”,具体含义需要结合⽂章的上下⽂来理解。

关于依赖注入,Spring 为我们提供了三种方法:

  1. 属性注入(Filed Injection)
  2. 构造方法注入(Constructor Injection)
  3. Setter 注入(Setter Injection)

1. 属性注入

属性注⼊是使⽤ @Autowired 实现的。

package com.example.springiocdi2;

import org.springframework.stereotype.Service;

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

package com.example.springiocdi2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
	//属性注入
    @Autowired
    private UserService userService;
    public void sayHi() {
        System.out.println("Hi UserController...");
        userService.sayHi();
    }
}

package com.example.springiocdi2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringIoCDi2Application {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIoCDi2Application.class, args);
        UserController userController = context.getBean("userController", UserController.class);
        userController.sayHi();
    }

}

运行结果:

Spring IoC&DI_第37张图片

构造方法注入

构造⽅法注⼊是在类的构造⽅法中实现注⼊。

package com.example.springiocdi2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController2 {
    //构造方法注入
    private UserService userService;
    
    @Autowired
    public UserController2(UserService userService) {
        this.userService = userService;
    }
    
    public void sayHi() {
        System.out.println("Hi UserController2");
        userService.sayHi();
    }
}

Spring IoC&DI_第38张图片
注意事项:如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,
那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法。

Spring IoC&DI_第39张图片
Spring IoC&DI_第40张图片

Setter 注入

Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注
解。

package com.example.springiocdi2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController3 {
    //setter方法注入
    private UserService userService;
    
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    
    public void sayHi() {
        System.out.println("Hi UserController3");
        userService.sayHi();
    }
}

Spring IoC&DI_第41张图片
如果没加@Autowired 注解,就会报错。

Spring IoC&DI_第42张图片

三种注入的优缺点

  1. 属性注入
  • 优点:简洁,使用方便
  • 缺点:
    • 只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
    • 不能注入一个 Final 修饰的属性
  1. 构造函数注入(Spring 4x推荐)
  • 优点:
    • 可以注入 Final 修饰的属性
    • 注入的对象不会被修改
    • 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法
    • 通用性好,构造方法是JDK支持的,所以更换任何框架,他都是适用的
  • 缺点:
    • 注入多个对象的时候,代码会比较繁琐
  1. Setter注入(Spring 3x推荐)
  • 优点:⽅便在类实例之后,重新对该对象进⾏配置或者注⼊
  • 缺点:
    • 不能注⼊⼀个Final修饰的属性
    • 注⼊对象可能会被改变,因为setter⽅法可能会被多次调⽤,就有被修改的⻛险

属性注入,注入一个final修饰的属性:
Spring IoC&DI_第43张图片
构造方法注入,注入一个final修饰的属性:
Spring IoC&DI_第44张图片
Setter注入,注入一个final修饰的属性:

Spring IoC&DI_第45张图片

为什么有些注入不能注入 final 修饰的属性?

如果一个属性被final关键字修饰,那么这个属性就成为了一个常量,它的值就不能被改变。而依赖注入的属性注入需要动态地修改属性的值,所以不能对被final关键字修饰的属性进行依赖注入。但是,在构造方法中,final属性可以被赋值。这是因为构造方法是在对象创建时执行的,此时final属性还没有被赋值。因此,在构造方法中可以对final属性进行赋值操作。

Autowired 存在的问题

当同一个类类型存在多个 Bean 时,就会出现问题。

package com.example.springiocdi2;

import lombok.Data;

@Data
public class User {
    private String name;
    private Integer age;
}

package com.example.springiocdi2;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class BeanConfig {
    @Bean("u1")
    public User user1() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(17);
        return user;
    }

    @Bean("u2")
    public User user2() {
        User user = new User();
        user.setName("lisi");
        user.setAge(18);
        return user;
    }
}


package com.example.springiocdi2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserController4 {
    @Autowired
    private User user;

    public void sayHi() {
        System.out.println("Hi UserController4");
        System.out.println(user);
    }
}

Spring IoC&DI_第46张图片
如何解决这个一个类型有多个 bean 的问题呢?Spring 提供了以下的几种方案:

  • @Primary
  • @Qualifier
  • Resource

使⽤@Primary注解:当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现。

	@Bean("u1")
    @Primary  //指定该bean为默认实现
    public User user1() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(17);
        return user;
    }

Spring IoC&DI_第47张图片

使⽤@Qualifier注解:指定当前要注⼊的bean对象。在@Qualifier的value属性中,指定注⼊的bean
的名称。

  • @Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
	@Qualifier("u2")
    @Autowired
    private User user;

Spring IoC&DI_第48张图片
使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。

	@Resource(name = "u2")
    private User user;

Spring IoC&DI_第49张图片

常见面试题:
@Autowird 与 @Resource的区别:

  • @Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
  • @Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊。相⽐于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean

你可能感兴趣的:(JavaEE,spring,java,后端,IoC,DI)