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

spring loCDI 详解

文章目录

  • 一、IoC & DI 基本知识
    • 1.1 IoC 的基本概念:
    • 1.2 IoC 的优势:
    • 1.3 DI 介绍:
  • 二、IoC 详解
    • 2.1 Spring 容器:
    • 2.2 被存储 Bean 的命名约定:
    • 2.3 Bean 的存储方式:
      • 2.3.1 五大类注解:
        • 2.3.1.1 @Controller(控制器存储):
        • 2.3.1.2 @Service(服务存储):
        • 2.3.1.3 @Repository(仓库存储):
        • 2.3.1.4 @Configuration(配置存储):
        • 2.3.1.5 @Component(组件存储):
        • 2.3.1.6 类注解之间的关系:
      • 2.3.2 方法注解(@Bean):
    • 2.4 Bean 重命名:
      • 2.4.1 五大类注解重命名:
      • 2.4.2 方法注解重命名:
  • 三、DI 详解(@Autowired)
    • 3.1 属性注入:
    • 3.2 Setter 注入:
    • 3.3 构造方法注入:
    • 3.4 三种注入优缺点:
    • 3.5 处理一个类多个对象的注入情况:
      • 3.5.1 @Primary:
      • 3.5.2 @Qualifier:
      • 3.5.3 @Resource:

一、IoC & DI 基本知识

使用一句话概括 Spring:

Spring 是包含了众多工具方法的 IoC 容器。

1.1 IoC 的基本概念:

其实 IoC 我们在前面已经使用了,我们在前面讲到,在类上面添加 @RestController 和 @Controller 注解,就是把这个对象交给 Spring 管理,Spring 框架启动时就会加载该类。把对象交给 Spring 管理,就是 IoC 思想。

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

控制反转的概念:

控制反转更准确的说是控制权反转。当需要某个对象时,传统开发模式中需要自己通过 new 创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入(Dependency Injection,DI)就可以。

1.2 IoC 的优势:

资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。

  1. 资源集中管理,实现资源的可配置和易管理。

  2. 降低了使用资源双方的依赖程度,也就是我们说的解耦合。

Spring 就是一种 IoC 容器,帮助我们来做了这些资源管理。

1.3 DI 介绍:

DI:Dependency Injection(依赖注入)。

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

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

二、IoC 详解

在 Spring 框架中,Bean 和对象是等价的 。也就是说,下面我们提到的 Bean 其实就是指对象。

2.1 Spring 容器:

这里我们来学习一下如何从 Spring 容器中获取对象。

假设 UserController 对象已经被存储在 Spring 容器中。

我们通过下面的代码即可获取到 UserController 对象。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        //从 Spring 上下文中获取对象
        UserController userController = context.getBean(UserController.class);
        //使用对象
        userController.sayHi();
    }
}

ApplicationContext 翻译过来就是:Spring 上下文。

因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下文。

关于 getBean 要传递的参数,下面有详细解释。

  • getBean 的详解:

上述代码是根据类型来查找对象。

如果 Spring 容器中,同一个类型存在多个 bean 的话,怎么来获取呢?

ApplicationContext 也提供了其他获取 bean 的方式,ApplicationContext 获取 bean 对象的功能,是父类 BeanFactory 提供的功能。

public interface BeanFactory {
 
 //以上省略... 
 
 // 1. 根据bean名称获取bean 
 Object getBean(String var1) throws BeansException;
 // 2. 根据bean名称和类型获取bean 
 <T> T getBean(String var1, Class<T> var2) throws BeansException;
 // 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean 
 Object getBean(String var1, Object... var2) throws BeansException;
 // 4. 根据类型获取bean 
 <T> T getBean(Class<T> var1) throws BeansException;
 // 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的
bean
 <T> T getBean(Class<T> var1, Object... var2) throws BeansException;
 
 //以下省略... 
}

常用的是上述1,2,4种,这三种方式,获取到的 bean 是一样的。

其中 1,2 种都涉及到根据名称来获取对象。

bean 的名称是什么呢?

2.2 被存储 Bean 的命名约定:

我们看下官方文档的说明:https://docs.spring.io/spring-framework/reference/core/beans/definition.html#beans-beanname

image-20240930150345091

程序开发人员不需要为 bean 指定名称,如果没有显式的提供名称,Spring 容器将为该 bean 生成唯一的名称。

Bean 默认名称的具体生成规则如下:

  • 五大类注解:

**普通:类名的小驼峰表示法。**例如:类名:UserController,Bean 的名称为:userController。

**如果前两位为大写:类名。**例如:类名:UController,Bean 的名称为:UController。

  • 方法注解(@Bean):

**Bean 的名称为方法名。**例如:方法名:getUserInfo,Bean 的名称为:getUserInfo。

上面都是 spring 自动帮助我们生成的,如果觉得不合适,程序员可以自己指定,只要不重复就行。

2.3 Bean 的存储方式:

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

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

2.3.1 五大类注解:

2.3.1.1 @Controller(控制器存储):

使用 @Controller 存储 bean 的代码,如下所示:

import org.springframework.stereotype.Controller;

@Controller // 将对象存储到 Spring 中
public class UserController {
    public void sayHi(){
        System.out.println("hi,UserController...");
    }
}

从 Spring 容器中获取对象。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        //从 Spring 上下文中获取对象
        UserController userController = context.getBean(UserController.class);
        //使用对象
        userController.sayHi();
    }
}

效果如下:

image-20240930124238715
2.3.1.2 @Service(服务存储):

使用 @Service 存储 bean 的代码,如下所示:

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

从 Spring 容器中获取对象。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        //从 Spring 上下文中获取对象
        UserService userService = context.getBean(UserService.class);
        //使用对象
        userService.sayHi();
    }
}

效果如下:

image-20240930124639168
2.3.1.3 @Repository(仓库存储):

使用 @Repository 存储 bean 的代码,如下所示:

@Repository
public class UserRepository {
    public void sayHi(){
        System.out.println("Hi,Repository~");
    }
}

从 Spring 容器中获取对象。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        //从 Spring 上下文中获取对象
        UserRepository userRepository = context.getBean(UserRepository.class);
        //使用对象
        userRepository.sayHi();
    }
}

效果如下:

image-20240930125226452
2.3.1.4 @Configuration(配置存储):

使用 @Configuration 存储 bean 的代码,如下所示:

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

从 Spring 容器中获取对象。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        //从 Spring 上下文中获取对象
        UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);
        //使用对象
        userConfiguration.sayHi();
    }
}

效果如下:

image-20240930125837295
2.3.1.5 @Component(组件存储):

使用 @Component 存储 bean 的代码,如下所示:

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

从 Spring 容器中获取对象。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        //从 Spring 上下文中获取对象
        UserComponent userComponent = context.getBean(UserComponent.class);
        //使用对象
        userComponent.sayHi();
    }
}

效果如下:

image-20240930130255621

为什么要这么多类注解?

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

  • @Controller:控制层。接收请求,对请求进行处理,并进行响应。
  • @Servie:业务逻辑层。处理具体的业务逻辑。
  • @Repository:数据层,也称为持久层。负责数据访问操作。
  • @Configuration:配置层。处理项目中的一些配置信息。
  • @Component:组件层。实际开发中,如果实在分不清是什么层的,就用 @Component(除了控制层,@Controller 有特殊的含义)。

程序的应用分层,调用流程如下:

image-20240930131117962
2.3.1.6 类注解之间的关系:

查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:

image-20240930131647518

其实这些注解里面都有一个注解 @Component ,说明它们本身就是属于 @Component 的"子类"。

@Component 是一个元注解,也就是说可以注解其他类注解,如 @Controller,@Service ,@Repository 等。这些注解被称为 @Component 的衍生注解。

@Controller,@Service 和 @Repository 用于更具体的用例(分别在表现层,业务逻辑层,数据层),在开发过程中,如果你要在业务逻辑层使用 @Component 或 @Service,显然 @Service 是更好的选择。比如杯子有喝水杯,刷牙杯等,但是我们更倾向于在日常喝水时使用水杯,洗漱时使用刷牙杯。

2.3.2 方法注解(@Bean):

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

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

这种场景,我们就需要使用方法注解 @Bean。

注意:方法注解要配合类注解使用。

在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中。

如下代码所示:

@Configuration
public class UserConfig {
    @Bean
    public UserInfo getUserInfo1(){
        return new UserInfo(1,"zhangsan");
    }
}

通过上面这段代码的写法,我们就能获取到外部包里的类。无需在外部包里面加上类注解。

  • 同一个类,定义多个对象:
@Configuration
public class UserConfig {
    @Bean
    public UserInfo getUserInfo1(){
        return new UserInfo(1,"zhangsan");
    }
    @Bean
    public UserInfo getUserInfo2(){
        return new UserInfo(2,"lisi");
    }
}

通过 Spring 容器,获取对象。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        //获取对应的对象
        UserInfo getUserInfo1 = (UserInfo)context.getBean("getUserInfo1");
        UserInfo getUserInfo2 = (UserInfo) context.getBean("getUserInfo2");
        //进行打印
        System.out.println(getUserInfo1);
        System.out.println(getUserInfo2);
    }
}

效果如下:

image-20240930151701751

可以看到,@Bean 可以针对同一个类,定义多个对象。

这时如果通过类型来获取 Bean 就会报错,显示这个类没有唯一的 Bean。

image-20240930153536495

2.4 Bean 重命名:

2.4.1 五大类注解重命名:

直接在注解里面加上名字即可,或者加上 value = 名字。

@Repository("u1")
public class UserRepository {
    public void sayHi(){
        System.out.println("Hi,Repository~");
    }
}

获取 Bean。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        
        UserRepository userRepository = context.getBean("u1", UserRepository.class);
        
        userRepository.sayHi();
    }
}

效果如下:

image-20240930154221500

2.4.2 方法注解重命名:

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

@Configuration
public class UserConfig {
    @Bean(name = {"h1","h2"})
    public UserInfo getUserInfo1(){
        return new UserInfo(1,"zhangsan");
    }
}

其中 name 可以省略(只有一个参数的情况),如果只有一个名称时{},也可以省略。

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);

        UserInfo userInfo = context.getBean("h1", UserInfo.class);

        System.out.println(userInfo);

    }
}

image-20240930154804164

三、DI 详解(@Autowired)

依赖注入是一个过程,是指 IoC 容器在创建 Bean 时,去提供运行时所依赖的资源,而资源指的就是对象。、

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

  1. 属性注入(Field Injection)

  2. Setter 注入(Setter Injection)

  3. 构造方法注入(Constructor Injection)

后续的注入演示代码将采用 Service 类注入到 Controller 类中。为了帮助大家理解,下面先给出 Service 类。

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

3.1 属性注入:

属性注入是使用 @Autowired 实现的。

Controller 类的实现代码如下:

@Controller // 将对象存储到 Spring 中
public class UserController {
    @Autowired
    private UserService userService;
    public void sayHi(){
        System.out.println("hi,UserController...");
        userService.sayHi();
    }
}

调用 Controller 中的 sayHi 方法:

@SpringBootApplication
public class Spring20240928iocApplication {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = SpringApplication.run(Spring20240928iocApplication.class, args);
        
        UserController userController = context.getBean(UserController.class);
        
        userController.sayHi();
    }
}

效果如下:

image-20240930160443244

3.2 Setter 注入:

Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候,需要加上 @Autowired 注解,如下代码所示:

@Controller // 将对象存储到 Spring 中
public class UserController {

    private UserService userService;
    @Autowired
    public void setUserService(UserService userService){
        this.userService = userService;
    }
    public void sayHi(){
        System.out.println("hi,UserController...");
        userService.sayHi();
    }
}

由于获取 Bean 的方式和效果是一样的所以 Setter 注入和构造方法注入,就不再赘述。

3.3 构造方法注入:

构造方法注入是在类的构造方法中实现注入,如下代码所示:

@Controller // 将对象存储到 Spring 中
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }
    public void sayHi(){
        System.out.println("hi,UserController...");
        userService.sayHi();
    }
}

注意:如果类只有一个构造方法,那么 @Autowired 注解可以省略,如果类中有多个构造方法, 那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。

3.4 三种注入优缺点:

  • 属性注入:

优点:简洁,使用方便。

缺点:

  1. 只能用于 IoC 容器,如果是非 IoC 容器不可用。

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

  • Setter注入(Spring 3.X推荐):

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

缺点:不能注入一个 Final 修饰的属性。

  • 构造函数注入(Spring 4.X推荐):

优点:

  1. 可以注入 final 修饰的属性。
  2. 通用性好,构造方法是 JDK 支持的,所以更换任何框架,它都是适用的。

缺点:注入多个对象时,构造方法会写很长。

3.5 处理一个类多个对象的注入情况:

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

@Configuration
public class UserConfig {
    @Bean
    public UserInfo getUserInfo1(){
        return new UserInfo(1,"zhangsan");
    }
    @Bean
    public UserInfo getUserInfo2(){
        return new UserInfo(2,"lisi");
    }
}

报错如下:

image-20240930190918799

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

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

  • @Primary

  • @Qualifier

  • @Resource

3.5.1 @Primary:

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

@Configuration
public class UserConfig {
    @Primary
    @Bean
    public UserInfo getUserInfo1(){
        return new UserInfo(1,"zhangsan");
    }
    @Bean
    public UserInfo getUserInfo2(){
        return new UserInfo(2,"lisi");
    }
}

3.5.2 @Qualifier:

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

注意:@Qualifier 注解不能单独使用,必须配合 @Autowired 使用。

@Controller // 将对象存储到 Spring 中
public class UserController {


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

    public void printUserInfo(){
        System.out.println(userInfo);
    }
}

3.5.3 @Resource:

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

@Controller // 将对象存储到 Spring 中
public class UserController {
    
    @Resource(name = "getUserInfo1")
    private UserInfo userInfo;

    public void printUserInfo(){
        System.out.println(userInfo);
    }
}

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

  1. @Autowired 是 spring 框架提供的注解,而 @Resource 是 JDK 提供的注解。
  2. @Autowired 默认是按照类型注入,而 @Resource 是按照名称注入。相比于 @Autowired 来说,@Resource 支持更多的参数设置。

使用习惯:

如果一个类的 Bean 只有一个的话,使用 @Autowired。

如果一个类的 Bean 有多个的话,使用 @Resource。

结语:
其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话,还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

在这里插入图片描述


http://www.kler.cn/news/329071.html

相关文章:

  • 文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于节点碳势响应的新型电力系统鲁棒优化调度 》
  • springbot,JWT令牌的使用。实现http请求拦截校验。
  • 如何使用ssm实现影院管理系统的设计与实现
  • vscode中配置python虚拟环境
  • 大数据-151 Apache Druid 集群模式 配置启动【上篇】 超详细!
  • [深度学习]基于YOLO高质量项目源码+模型+GUI界面汇总
  • 如何通过Dockfile更改docker中ubuntu的apt源
  • Linux 搭建与使用yolov5训练和检验自建模型的步骤
  • Jenkins pipeline配置示例
  • NLP任务一些常用的数据集集锦
  • 解决 Adobe 盗版弹窗
  • 【Linux 从基础到进阶】HBase数据库安装与配置
  • 【DAY20240926】06从入门到精通:掌握 Git 分支操作的实用指南
  • 修复OpenSSH远程代码执行漏洞:版本升级到9.9p1
  • springboot启动流程
  • vue基于Spring Boot框架的高校实验室预约管理系统
  • 论文阅读:多模态医学图像融合方法的研究进展
  • golang rpc
  • TCP\IP标准与OSI标准
  • JS进阶 1——作用域、解构、箭头函数
  • 滚雪球学MySQL[3.2讲]:MySQL聚合函数与分组详解:COUNT、SUM、AVG、MAX、MIN及GROUP BY与HAVING
  • Qt 每日面试题 -5
  • 手机实时提取SIM卡打电话的信令声音-新的篇章(二、USB音频线初步探索)
  • XML与JSON的用法与区别
  • IP数据云—IP定位可以精准到什么程度?
  • 深度学习:(八)深层神经网络参数与流程
  • Win10系统插入带有麦克风的耳机_麦克风不起作用_解决方法_亲测成功---Windows运维工作笔记054
  • 面试题1-fail-safe机制与fail-fast 机制
  • 以太网开发基础-MAC和PHY
  • js设计模式(26)