Spring IoCDI

news2025/1/22 12:23:17

在这里插入图片描述

文章目录

  • 前言
  • 什么是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);
        }
    }
}

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

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

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

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

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

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

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

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

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

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 容器了。

在这里插入图片描述
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();
    }
}

在这里插入图片描述
在这里插入图片描述
这里显示出了我们想要的结果,就说明我们使用 @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种。
在这里插入图片描述

1. 根据Bean的名称获取Bean

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
简单来讲就是当类名中前两个字母中大写字母小于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 方法的返回值。

在这里插入图片描述

启动项目的时候,就会发现我们预想中的结果出现在了控制台中,并且这个不需要我们发送什么 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();
    }
}

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

为什么会有这么多类注解

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

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

在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
查看 @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);
    }
}

在这里插入图片描述

同一个类定义多个对象。

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

在这里插入图片描述

@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);

在这里插入图片描述
可以看到我们通过重命名的名字u2获取到了Bean。

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

扫描路径

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

@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 注解。

在这里插入图片描述
而如果 @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);
    }
}

在这里插入图片描述
@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();
    }

}

运行结果:

在这里插入图片描述

构造方法注入

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

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

在这里插入图片描述
注意事项:如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,
那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法。

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
如果没加@Autowired 注解,就会报错。

在这里插入图片描述

三种注入的优缺点

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

属性注入,注入一个final修饰的属性:
在这里插入图片描述
构造方法注入,注入一个final修饰的属性:
在这里插入图片描述
Setter注入,注入一个final修饰的属性:

在这里插入图片描述

为什么有些注入不能注入 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);
    }
}

在这里插入图片描述
如何解决这个一个类型有多个 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;
    }

在这里插入图片描述

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

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

在这里插入图片描述
使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。

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

在这里插入图片描述

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1330478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

less 查看文本时,提示may be a binary file.See it anyway?

解决办法 首先使用echo $LESSCHARSET查看less的编码 看情况设置less的编码格式(我的服务器上使用utf-8查看中文) 还要特别注意一下,Linux中存在的文本文件的编码一定要是utf - 8;(这一步很关键) 例如:要保证windows上传到Linux的…

Ubuntu 常用命令之 ps 命令用法介绍

📑Linux/Ubuntu 常用命令归类整理 ps命令是Linux下的一个非常重要的命令,它用于查看系统中的进程状态。ps是Process Status的缩写,可以显示系统中当前运行的进程的状态。 以下是一些常用的参数 a:显示所有进程(包括…

【3D Max】入门

文章目录 概述界面介绍常用功能保存和导入基本建模编辑模型材质和贴图光源和阴影动画制作渲染设置导出和打印来源 概述 3 ds MAX是由 Discreet (后来被 Autodesk (Autodesk)合并)开发的一款基于 PC系统的3 d Max或3 ds MAX三维动画绘制和制作软件,其主要功能有建模…

选择谷歌SEO公司时怎么避开套路型公司?

选择谷歌SEO公司时,如何避开套路型公司是一个至关重要的话题。在当今数字化时代,优化网站以获得搜索引擎排名的重要性越发凸显,而选择一家信誉良好、专业的SEO公司将对企业的发展产生深远影响。然而,市场上存在许多套路型公司&…

java开发需要掌握的TypeScript相关的知识点,细致简洁版。

Typescript: 介绍: TypeScript(简称 TS)是JavaScript的超集(继承了JS全部语法),TypeScript Type JavaScript。 简单说,就是在JS的基础上,为JS添加了类型支持。是微软开…

构建创新学习体验:企业培训系统技术深度解析

企业培训系统在现代企业中发挥着越来越重要的作用,它不仅仅是传统培训的延伸,更是技术创新的结晶。本文将深入探讨企业培训系统的关键技术特点,并通过一些简单的代码示例,展示如何在实际项目中应用这些技术。 1. 前端技术&#…

springboot集成websocket全全全!!!

一、界面展示 二、前置了解 1.什么是websocket WebSocket是一种在单个TCP连接上进行全双工通信的持久化协议。 全双工协议就是客户端可以给我们服务器发数据 服务器也可以主动给客户端发数据。 2.为什么有了http协议 还要websocket 协议 http协议是一种无状态,非…

基于SpringBoot的APK检测管理系统 JAVA简易版

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 开放平台模块2.3 软件档案模块2.4 软件检测模块2.5 软件举报模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 开放平台表3.2.2 软件档案表3.2.3 软件检测表3.2.4 软件举报表 四、系统展示五、核心代…

学校和老师如何制作自己免费的成绩查询系统

在当今数字化的时代,许多学校都采用信息技术来管理和提高工作效率。其中,成绩查询系统是一个非常实用的工具,它可以让老师和学生们快速、方便地查询成绩。那么,学校和老师如何制作自己免费的成绩查询系统呢?本文将为你…

【Amazon 实验②】使用Amazon WAF做基础 Web Service 防护之自定义规则

文章目录 1. 自定义规则1.1 介绍 2. 实验步骤2.1 测试2.2 输出 上一篇章介绍了使用Amazon WAF做基础 Web Service 防护中的Web ACLs 配置 & AWS 托管规则的介绍和演示操作 【Amazon 实验①】使用Amazon WAF做基础 Web Service 防护,本篇章将继续介绍关于自定义…

webSocket原理及其案例

常见的消息推送方式 1:轮询方式 浏览器以指定的时间间隔向服务器发出HTTP请求,服务器实现试试返回数据给浏览器 缺点:数据有延时、服务器压力较大。 2:长轮询 浏览器发出ajax(异步)请求,服…

【HarmonyOS开发】ArkTs使用Http封装

1、鸿蒙中如何进行网络请求 1.1 三方库请求 ohos/axios ohos/retrofit ohos/httpclient 1.2 鸿蒙原生请求 ohos.net.http 2、ArkTs请求模块ohos.net.http 本模块提供HTTP数据请求能力。应用可以通过HTTP发起一个数据请求,支持常见的GET、POST、OPTIONS、HEAD…

阿里云吴结生:云计算是企业实现数智化的阶梯

云布道师 近年来,越来越多人意识到,我们正处在一个数据爆炸式增长的时代。IDC 预测 2027 年全球产生的数据量将达到 291 ZB,与 2022 年相比,增长了近 2 倍。其中 75% 的数据来自企业,每一个现代化的企业都是一家数据公…

Xcode15 iOS 17 Simulator 离线安装,模拟器安装

Xcode 15 安装包的大小相比之前更小,因为除了 macOS 的 Components,其他都需要动态下载安装,否则提示 iOS 17 Simulator Not Installed。 如果不安装对应的运行模拟库 无法真机和模拟器运行,更无法新建项目。但是由于模拟器安装包…

通过for语句遍历一个简单的数组

一、基本思想 创建一个命名为ArrayDemo的类,然后定义一个合适的数组,使用for语句遍历这个数值,然后进行输出。 注意事项: 最好在每个字符之间留下一个空白。 二、基本代码 public class ArrayDemo {public static void main(St…

【沐风老师】3dMax篮球建模方法详解

3dMax足球、排球和篮球建模系列之:篮球建模。对于足球和排球建模,思路是从一个基础模型开始,利用这个基础模型与最终的足球(或排球)模型的某些相似之处,经过修改编辑,最终完成目标模型的建模。但…

【Amazon 实验②】Amazon WAF功能增强之使用Cloudfront、Lambda@Edge阻挡攻击

文章目录 一、方案介绍二、架构图三、部署方案1. 进入Cloud9 编辑器,新打开一个teminal2. 克隆代码3. 解绑上一个实验中Cloudfront 分配绑定的防火墙4. 使用CDK部署方案5. CDK部署完成6. 关联LambdaEdge函数 四、方案效果 一、方案介绍 采用 LambdaEdge DynamoDB 架…

【Unity】入门

文章目录 概述常用组件各类文件基础知识创建工程工程目录介绍五个窗口面板创建代码和场景 脚本与编程鼠标的输入键盘的输入代码来操作组件获取物体API资源的使用API定时调用与线程向量的基本运算预制体与实例 物理系统与组件案例实操作快捷键来源 Unity已广泛运用到各个领域&am…

使用PE信息查看工具和Dependency Walker工具排查因为库版本不对导致程序启动报错问题

目录 1、问题说明 2、问题分析思路 3、问题分析过程 3.1、使用Dependency Walker打开软件主程序,查看库与库的依赖关系,查看出问题的库 3.2、使用PE工具查看dll库的时间戳 3.3、解决办法 4、最后 VC常用功能开发汇总(专栏文章列表&…