spring loC&DI 详解

文章目录

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

java 复制代码
@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 提供的功能。

java 复制代码
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

程序开发人员不需要为 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 的代码,如下所示:

java 复制代码
import org.springframework.stereotype.Controller;

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

从 Spring 容器中获取对象。

java 复制代码
@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();
    }
}

效果如下:

2.3.1.2 @Service(服务存储):

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

java 复制代码
@Service
public class UserService {
    public void sayHi(){
        System.out.println("hello Service~");
    }
}

从 Spring 容器中获取对象。

java 复制代码
@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();
    }
}

效果如下:

2.3.1.3 @Repository(仓库存储):

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

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

从 Spring 容器中获取对象。

java 复制代码
@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();
    }
}

效果如下:

2.3.1.4 @Configuration(配置存储):

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

java 复制代码
@Configuration
public class UserConfiguration {
    public void sayHi(){
        System.out.println("hi,UserConfiguration...");
    }
}

从 Spring 容器中获取对象。

java 复制代码
@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();
    }
}

效果如下:

2.3.1.5 @Component(组件存储):

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

java 复制代码
@Component
public class UserComponent {
    public void sayHi(){
        System.out.println("hi,UserComponent...");
    }
}

从 Spring 容器中获取对象。

java 复制代码
@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();
    }
}

效果如下:

为什么要这么多类注解?

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

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

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

2.3.1.6 类注解之间的关系:

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

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

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

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

2.3.2 方法注解(@Bean):

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

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

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

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

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

如下代码所示:

java 复制代码
@Configuration
public class UserConfig {
    @Bean
    public UserInfo getUserInfo1(){
        return new UserInfo(1,"zhangsan");
    }
}

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

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

通过 Spring 容器,获取对象。

java 复制代码
@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);
    }
}

效果如下:

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

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

2.4 Bean 重命名:

2.4.1 五大类注解重命名:

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

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

获取 Bean。

java 复制代码
@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();
    }
}

效果如下:

2.4.2 方法注解重命名:

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

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

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

java 复制代码
@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);

    }
}

三、DI 详解(@Autowired)

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

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

  1. 属性注入(Field Injection)

  2. Setter 注入(Setter Injection)

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

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

java 复制代码
@Service
public class UserService {
    public void sayHi(){
        System.out.println("hello Service~");
    }
}

3.1 属性注入:

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

Controller 类的实现代码如下:

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

调用 Controller 中的 sayHi 方法:

java 复制代码
@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();
    }
}

效果如下:

3.2 Setter 注入:

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

java 复制代码
@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 构造方法注入:

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

java 复制代码
@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 会存在问题。

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

报错如下:

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

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

  • @Primary

  • @Qualifier

  • @Resource

3.5.1 @Primary:

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

java 复制代码
@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 使用。

java 复制代码
@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 的名称。

java 复制代码
@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。

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

相关推荐
非 白5 分钟前
【Java】代理模式
java·开发语言·代理模式
Good Note16 分钟前
Golang的静态强类型、编译型、并发型
java·数据库·redis·后端·mysql·面试·golang
我就是我3521 小时前
记录一次SpringMVC的406错误
java·后端·springmvc
向哆哆1 小时前
Java应用程序的跨平台性能优化研究
java·开发语言·性能优化
ekkcole2 小时前
windows使用命令解压jar包,替换里面的文件。并重新打包成jar包,解决Failed to get nested archive for entry
java·windows·jar
handsomestWei2 小时前
java实现多图合成mp4和视频附件下载
java·开发语言·音视频·wutool·图片合成视频·视频附件下载
全栈若城2 小时前
03 Python字符串与基础操作详解
java·开发语言·python
伯牙碎琴2 小时前
二、Spring Framework基础:IoC(控制反转)和DI(依赖注入)
java·spring·log4j
菲力蒲LY2 小时前
输入搜索、分组展示选项、下拉选取,全局跳转页,el-select 实现 —— 后端数据处理代码,抛砖引玉展思路
java·前端·mybatis
南宫生3 小时前
力扣每日一题【算法学习day.130】
java·学习·算法·leetcode