【Spring 核心: IoC&DI】从原理到注解使用、注入方式全攻略

文章目录

    • 一、定义IoC&DI
      • [1.1 Ioc](#1.1 Ioc)
        • [1.1.1 传统程序开发](#1.1.1 传统程序开发)
        • [1.1.2 IoC 程序开发](#1.1.2 IoC 程序开发)
        • [1.1.3 IoC 优势](#1.1.3 IoC 优势)
      • [1.2 DI](#1.2 DI)
    • [二、IoC 详解](#二、IoC 详解)
      • [2.1 Bean 的存储](#2.1 Bean 的存储)
      • [2.2 @Controller (控制器存储)](#2.2 @Controller (控制器存储))
      • [2.3 @Service(服务存储)](#2.3 @Service(服务存储))
      • [2.4 @Repository (仓库存储)](#2.4 @Repository (仓库存储))
      • [2.5 @Component(组件存储)](#2.5 @Component(组件存储))
      • [2.6 @Configuration(配置存储)](#2.6 @Configuration(配置存储))
      • [2.7 五大类注解区别](#2.7 五大类注解区别)
      • [2.8 方法注解 @Bean](#2.8 方法注解 @Bean)
        • [2.8.1 重命名 Bean](#2.8.1 重命名 Bean)
      • [2.9 扫描路径](#2.9 扫描路径)
    • [三、DI 详解](#三、DI 详解)
      • [3.1 属性注入](#3.1 属性注入)
      • [3.2 构造方法注入](#3.2 构造方法注入)
      • [3.3 Setter 注入](#3.3 Setter 注入)
      • [3.4 三种注入方式优缺点](#3.4 三种注入方式优缺点)
      • [3.5 @Autowired 注解问题及解决](#3.5 @Autowired 注解问题及解决)
        • [3.5.1 @Primary 解决](#3.5.1 @Primary 解决)
        • [3.5.2 @Qualifier 解决](#3.5.2 @Qualifier 解决)
        • [3.5.3 @Resource 解决](#3.5.3 @Resource 解决)
        • [3.5.4 @Autowired 与 @Resource 区别](#3.5.4 @Autowired 与 @Resource 区别)

一、定义IoC&DI

1.1 Ioc

Spring 是包含了众多工具方法的IoC容器。IoC 是Spring的核心思想,例如前面章节所示:在类上面添加@RestController 和 @Controller 注解,,就是把这个对象交给Spring管理,Spring框架启动时就会加载该类。把对象交给Spring管理,就是IoC思想。

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

获得依赖对象的过程被反转了,也就是说,当需要某个对象时,传统开发模式中需要自己通过new创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入 (DependencyInjection,DI)就可以了。

这个容器称为:IoC容器。Spring是⼀个IoC容器,所以有时Spring也称为Spring容器。

下面进行 IoC 案例说明

1.1.1 传统程序开发

实现下面的需求:从右向左依次依赖。

按照每一个模块设计一个类,代码如下:

java 复制代码
public class Car {
    private Framework framework;
    public Car(){
        framework = new Framework();
        System.out.println("car init...");
    }

    public void run() {
        System.out.println("car run...");
    }
}
public class Framework {
    private Bottom bottom;

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

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

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

这样设计的可维护性太低,耦合度太高,当需求变更时,对上述代码修改起来会非常麻烦,当修改尺寸时,修改如下:

从上述得出,当底层代码改动后,整个链上的所有代码都需要进行修改。

  • 解决方案
    可以尝试不在每个类中自己创建下级类,如果自己创建下级类就会出现当下级类发生改变操作,
    自己也要跟着修改。此时,我们只需要将原来由自己创建的下级类,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦。
1.1.2 IoC 程序开发

基于上述方案,将程序进行改造,将创建子类的方式改为注入传递的方式,代码如下:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire(17);
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);

        car.run();
    }
}
public class Tire {
    private int size;

    public Tire(int size){
       this.size = size;
        System.out.println("size : " + 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...");
    }
}

经过以上调整,无论底层类中如何变化,整个调用链不用做任何变化,就完成了代码之间的解耦合,使得更加灵活。这就是IoC思想。

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

IoC容器也就是控制反转器。IoC 容器具有以下优点:

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

1.2 DI

DI: DependencyInjection(依赖注入)

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

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

IoC 是一种思想,也是"目标",而思想只是一种指导原则,最终还是要有可行的落地方案,而DI就属于

具体的实现。所以也可以说,DI是IoC的一种实现。

二、IoC 详解

2.1 Bean 的存储

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

共有两类注解类型:

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

2.2 @Controller (控制器存储)

使用@Controller 注解存储Bean ,代码如下:

java 复制代码
@Controller
public class UserController {
	public void sayHello(){
        System.out.println("do controller..");
    }
}

加上注解后,此时这个对象就已经存储在Spring容器中了。

通过获取Spring上下文对象:发现成功获取到Controller对象

java 复制代码
public class SpringIocDemoApplication {

   	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
		UserController bean = context.getBean(UserController.class);
		System.out.println(bean);
    }
}
  • 获取Bean 对象的其他方式,ApplicationContext 也提供了其他方式进行获取:

可以根据名称来获取bean,那么bean 命名的规则是?

五大类注解让Spring管理Bean对象的默认取名方式如下:

  1. bean名称以小写字母开头,小驼峰命名。例:UserController, Bean的名称为:userController
  2. 当有多个字符并且第一个和第二个字符都是大写时,Bean对象名就是类名。例:UController, Bean的名称为:UController

2.3 @Service(服务存储)

使用@Service 注解存储bean代码如下,获取bean对象方式不变:

java 复制代码
@Service
public class UserService {
    public void doService(){
        System.out.println("do service...");
    }
}

2.4 @Repository (仓库存储)

使用@Repository 注解存储bean代码如下,获取bean对象方式不变:

java 复制代码
@Repository
public class UserRepository {
    public String doRepository(){
        return "do repository...";
    }
}

2.5 @Component(组件存储)

使用@Component 注解存储bean代码如下,获取bean对象方式不变:

java 复制代码
@Component
public class UserComponent {
    public String doComponent(){
        return "do component...";
    }
}

2.6 @Configuration(配置存储)

使用@Configuration 注解存储bean代码如下,获取bean对象方式不变:

java 复制代码
@Configuration
public class UserConfig {
    public String doConfig(){
        return "do config...";
    }
}

2.7 五大类注解区别

为什么要用这么多类注解呢,既然都是将对象交给Spring容器,使用哪个注解功能不是都一样吗,这样使用不同的注解是为了什么?

使用这么多类注解是为了应用的分层,当看到这些不同的注解,就能直接了解当前类的用途:

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

@Controller,@Service,@Repository,@Configuration这四个注解都是@Component注解的衍生注解,可以理解为是父子关系。

2.8 方法注解 @Bean

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

  1. 使用外部包中类,没办法添加类注解
  2. 一个类,需要多个对象,无法使用类注解

此时需要使用方法注解 @Bean

使用示例:

java 复制代码
@Configuration
public class BeanConfig {

    @Bean
    public UserInfo user1(){
        return new UserInfo("zhangsan",17);
    }
}
  • 方法注解 @Bean 需要配合类注解才能将对象正常的存储到Spring容器中。
    通过获取Spring上下文对象可以得到:
java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
		UserInfo bean1 = context.getBean("user1",UserInfo.class);
		System.out.println(bean1);
    }  
}
2.8.1 重命名 Bean

可以通过设置name属性给Bean对象进行重命名操作,如下代码示例:

java 复制代码
@Configuration
public class BeanConfig {

    @Bean(name = {"u1","userInfo"})
    public UserInfo user1(){
        return new UserInfo("zhangsan",17);
    }
}

此时可以使用 u1 获取UserInfo 对象:

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
		UserInfo bean = (UserInfo) context.getBean("u1");
		System.out.println(bean);
    }
}

重命名代码也可以进行省略:@Bean({"u1","userInfo"})@Bean("u1")

2.9 扫描路径

使用五大注解声明的Bean,生效必须配置扫描路径,让Spring扫描到这些注解,也就是通过 @ComponentScan 来配置这些路径。

但是默认扫描范围是SpringBoot 启动类所在的包及其子包,因此将启动类 SpringIocDemoApplication 放在希望扫描的包的路径下,那么定义的Bean 就都可以被扫描到。

三、DI 详解

依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。简单来说,就是把对象取出来放到某个类的属性中。

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

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

3.1 属性注入

属性注入使用 @Autowired 实现,示例:

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserInfo userInfo;

    public void doService(){
        System.out.println(userInfo);
        System.out.println("do service...");
    }
}
java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
		UserService bean = context.getBean(UserService.class);
		bean.doService();
    }
}

3.2 构造方法注入

构造方法注入是在类的构造方法中实现注入,示例:

java 复制代码
@Controller
public class UserController {

    //构造方法注入
    private UserService userService;

    public UserController(){

    }
    
    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }

    public void sayHello(){
        userService.doService();
        System.out.println("do controller..");
    }
}
  • 当类中只有一个构造方法时,那么 @Autowired 注解可以省略;当类中有多个构造方法时,那么需要加上 @Autowired 明确指定使用哪个构造方法。只能在一个构造方法上加上 @Autowired 注解。

3.3 Setter 注入

Setter 注入和属性的 Setter 方法实现类似,只是在set方法时加上 @Autowired 注解,示例:

java 复制代码
@Controller
public class UserController {

    //Setter 注入
    private UserService userService;
    
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void sayHello(){
        userService.doService();
        System.out.println("do controller..");
    }

}

3.4 三种注入方式优缺点

  1. 属性注入:
    优点:简洁,使用方便
    缺点:是能用于IoC 容器,并且只有在使用时才会出现异常;不能注入一个Final 修饰的属性。
  2. 构造函数注入
    优点:可以注入Final 修饰的属性;注入的对象不会被修改;依赖对象在使用前一定会被完全初始化;通用性好,适用于所有框架。
    缺点:注入多个对象时,代码比较繁琐,需要多个对象参数。
  3. Setter 注入
    优点:方便在类示例之后重新对该对象进行配置或注入。
    缺点:不能注入一个 Final 修饰的属性;注入对象可能会被改变,因为Setter 方法可能会被多次调用。

3.5 @Autowired 注解问题及解决

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

java 复制代码
@Configuration
public class BeanConfig {

    @Bean({"u1"})
    public UserInfo user1(){
        return new UserInfo("zhangsan",17);
    }

    @Bean({"u2"})
    public UserInfo user2(){
        return new UserInfo("lisi",20);
    }
}
java 复制代码
@Service
public class UserService {

    @Autowired
    private UserInfo userInfo;

    public void doService(){
        System.out.println(userInfo);
        System.out.println("do service...");
    }
}
java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
		UserService bean = context.getBean(UserService.class);
		bean.doService();
	}

}

此时,当有两个userInfo 对象时,获取上下文发现启动失败,信息如下:

报错原因是需要一个单一对象,但是找到了两个,因此Spring 提供了以下几种解决方案。

3.5.1 @Primary 解决

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

java 复制代码
@Configuration
public class BeanConfig {

    @Bean({"u1"})
    public UserInfo user1(){
        return new UserInfo("zhangsan",17);
    }

    @Bean({"u2"})
    @Primary
    public UserInfo user2(){
        return new UserInfo("lisi",20);
    }
}

此时默认获取到的对象就是 user2 。

3.5.2 @Qualifier 解决

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

  • @Qualifier 注解不能单独使用,必须配合@Autowired 注解使用。
java 复制代码
@Service
public class UserService {

    @Autowired
    @Qualifier("u1")
    private UserInfo userInfo;

    public void doService(){
        System.out.println(userInfo);
        System.out.println("do service...");
    }
}

此时获取到对象为 user1。

3.5.3 @Resource 解决

使用@Resource 注解:是按照Bean 的名称进行注入。通过name属性指定要注入的bean 的名称。示例:

java 复制代码
@Service
public class UserService {

    @Resource(name = "u2")
    private UserInfo userInfo;

    public void doService(){
        System.out.println(userInfo);
        System.out.println("do service...");
    }
}

此时获取到的对象为 user2。

3.5.4 @Autowired 与 @Resource 区别
  • @Autowired 是Spring框架提供的注解,而 @Resource 是JDK 提供的注解。
  • @Autowired 默认是按照类型注入,而@Resource 是按照名称注入。相比于@Autowired 来说,@Resource 支持更多的参数设置,例如name设置,根据名称获取Bean。
相关推荐
likuolei7 小时前
Spring AI框架完整指南
人工智能·python·spring
毕设源码_郑学姐7 小时前
计算机毕业设计springboot基于HTML5的酒店预订管理系统 基于Spring Boot框架的HTML5酒店预订管理平台设计与实现 HTML5与Spring Boot技术驱动的酒店预订管理系统开
spring boot·后端·课程设计
梵得儿SHI7 小时前
(第四篇)Spring AI 核心技术攻坚:多轮对话与记忆机制,打造有上下文的 AI
java·人工智能·spring·springai生态·上下文丢失问题·三类记忆·智能客服实战案
希忘auto7 小时前
SpringBoot之统一数据返回格式
java·spring
不吃香菜学java7 小时前
spring-依赖注入
java·spring boot·后端·spring·ssm
ja哇7 小时前
Spring AOP 详细讲解
java·后端·spring
南部余额7 小时前
Spring Boot 整合 MinIO:封装常用工具类简化文件上传、启动项目初始化桶
java·spring boot·后端·文件上传·工具类·minio·minioutils
海南java第二人7 小时前
Spring Bean生命周期深度剖析:从创建到销毁的完整旅程
java·后端·spring
QQ19632884757 小时前
ssm基于Springboot+的球鞋销售商城网站vue
vue.js·spring boot·后端