Spring IoC详解

1.什么是IoC

Spring IoC(Inversion of Control,控制反转)是Spring框架的核心之一,它是一种设计模式,也称为依赖注入(Dependency Injection,DI)。在传统的程序设计中,对象之间的依赖关系通常由程序员在类内部直接创建和管理,而在IoC容器中,对象的创建和管理由容器来负责,程序员只需要定义对象的依赖关系,由容器来实现对象的创建和组装,即把对象的控制权交给IoC容器,所以叫控制反转。

接下来我们通过一个例子来类比解释什么是IoC

1.1 传统程序的开发

需求:造一辆汽车。

实现思路:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘大小设计车身,最后根据车身设计好整个汽车。在这个过程中出现了一个“依赖”关系,汽车依赖车身,车身依赖底盘,底盘依赖轮子。

最后实现的代码如下:

public class Main {public static void main(String[] args) {Car car = new Car();car.run();}
}class Car {private Framework framework;public Car() {framework = new Framework();System.out.println("car init");}public void run() {System.out.println("car run run!");}
}//车身
class Framework {private Bottom bottom;public Framework() {bottom = new Bottom();System.out.println("Framework init");}
}//底盘
class Bottom {private Tire tire;public Bottom() {tire = new Tire();System.out.println("bottom init");}
}//轮子
class Tire {//轮子尺寸private int size;public Tire() {this.size = 15;System.out.println("tire init");}
}

如果现在要求轮子的大小要为20,我们就要给Tire类添加一个有参数的构造方法,与此同时,Bottom类,Framework类,Car类都需要修改,如果,我现在又要给轮子添加一个属性 颜色 此时所有依赖Tire类的类又都要修改,由此可见这样的开发方式是存在弊端的。 

1.2 IoC 程序开发

public class Main {public static void main(String[] args) {Tire tire = new Tire();Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);car.run();}
}class Car {public Car(Framework framework) {System.out.println("car init");}public void run() {System.out.println("car run run!");}
}//车身
class Framework {public Framework(Bottom bottom) {System.out.println("Framework init");}
}//底盘
class Bottom {public Bottom(Tire tire) {System.out.println("bottom init");}
}//轮子
class Tire {//轮子尺寸private int size;public Tire() {this.size = 15;System.out.println("tire init");}
}

我们把创建对象交给Main类去做,达到了其他类之间的解耦合效果,无论其他类怎么变化,整个的调用链是不需要调整的,这里我们的Main类就可以称作IoC容器。

在Spring中,IoC容器负责管理Java对象之间的依赖关系。当应用程序启动时,IoC容器会读取配置文件或注解信息,根据这些信息来实例化对象,并将这些对象之间的依赖关系注入到对象中。这种方式使得应用程序的组件之间解耦,易于维护和扩展。

1.3 DI

DI,全称为依赖注入(Dependency Injection),是面向对象编程中的一种设计模式。它是控制反转(Inversion of Control,IoC)思想的一种具体实现方式。

简单来说,依赖注入是指在创建对象时,将其所依赖的其他对象的引用注入到对象中,而不是由对象自己创建或查找依赖的对象。这样做的目的是降低模块之间的耦合度,使得对象之间的依赖关系更加灵活、可维护和易于测试。

2. IoC/DI 使用

我们已经对IoC和DI有初步的了解,接下来我们具体学习Spring IoC 和 DI的代码实现。

Spring 容器管理的主要是对象,这些对象,我们称之为“Bean” ,我们把这些对象交由Spring管理,由Spring来负责对象的创建和销毁,我们程序只需要告诉Spring,哪些需要存,以及如何从Spring取出对象。

使用@Component注解,可以表示把某个类交给Spring来管理,使用@Autowired注解注入运行时依赖的对象:

public class Main {public static void main(String[] args) {Car car = new Car();car.run();}
}
@Component
class Car {@Autowiredprivate Framework framework;public Car() {System.out.println("car init");}public void run() {System.out.println("car run run!");}
}//车身
@Component
class Framework {@Autowiredprivate Bottom bottom;public Framework() {System.out.println("Framework init");}
}//底盘
@Component
class Bottom {@AutowiredTire tire;public Bottom() {System.out.println("bottom init");}
}//轮子
@Component
class Tire {//轮子尺寸private int size;public Tire() {this.size = 15;System.out.println("tire init");}
}

3. IoC详解

3.1 Bean的存储

在上面的案例中,我们知道要把某个对象交给IoC容器管理,需要在类上添加一个注解:@Component 而Spring框架为了更好的服务Web程序,提供了更丰富的注解:

  1. 类注解:@Controller,@Service,@Repository,@Component,@Configuration
  2. 方法注解:@Bean
3.1.1  类注解
  1. @Controller
    @Controller 注解用于标识一个类作为Spring MVC框架中的控制器,负责处理用户请求,并返回相应的视图。通常用于标识处理HTTP请求的控制器类,可以与 @RequestMapping 注解结合使用,实现请求映射和页面跳转。
  2. @Service
    @Service 注解用于标识一个类作为业务层的组件,通常用于标识服务层的类,通过该注解告诉Spring该类是业务逻辑处理的组件,可以被注入到其他类中使用。
  3. @Repository
    @Repository 注解用于标识一个类作为数据访问层的组件,通常用于标识数据访问对象(DAO)类,通过该注解告诉Spring该类是用于数据库访问的组件,可以捕获数据库异常并将其重新抛出为Spring的数据访问异常。
  4. @Component
    @Component 注解是Spring中所有具体组件的通用形式,用于标识一个类作为Spring容器管理的组件,表示该类会自动被Spring加载,并可以通过依赖注入来使用。
  5. @Configuration
    @Configuration 注解用于标识一个类作为配置类,通常用于定义Bean的创建和依赖关系。该注解通常与 @Bean 注解一起使用,用于替代XML配置文件,实现Java Config的方式进行配置。

 这个和我们上期讲的应用分层是呼应的,让程序员看到类注解后,就能知道当前类的用途:

使用演示:

这些注解的使用方式都相同,我们通过@Controller来演示:

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

如上述代码,我们只需在类上面加上@Controltroller 注解即可把该类的对象储存在Spring中。

我们如何知道这个对象已经存在Spring容器中了呢,接下来我们学习如何从Spring容器中获取对象:

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

可以看到,项目启动后成功打印了 Hello @Controller

解释:

ApplicationContext:译为程序上下文,用于表示 Spring 容器,并且负责管理 Bean 的生命周期和配置信息。即可以理解为包含了Spring容器里的所有内容,所有我们可以从ApplicationContext对象中获取Bean.

getBean()是BeanFactory接口中的一个方法,ApplicationContext实现了BeanFactory接口,我们打开BeanFactory的源码:

我们发现重载了五个getBean方法 ,我们刚才是通过类对象获取的Bean,所以我们使用的是第四个,我们接下来再学习第一个和第二个的使用:

1. getBean(String name)

根据Bean的名称获取Bean,Spring会给管理的Bean按照小驼峰的方式命名:

@SpringBootApplication
public class J20240323SpringIocApplication {public static void main(String[] args) {//获取Spring上下文对象ApplicationContext context = SpringApplication.run(J20240323SpringIocApplication.class, args);//从Spring上下文中获取对象//通过类对象获取UserController userController1 = context.getBean(UserController.class);userController1.say();//通过对象名获取UserController userController2 = (UserController)context.getBean("userController");userController2.say();}
}

注意:Spring官方文档中关于Bean的名称有这样一个注意事项:

 当类名的前两个字符都是大写字母时,将按照类起初的名字来命名。

2. getBean(String name, Class<T> requiredType)

通过名称和类对象获取Bean:

@SpringBootApplication
public class J20240323SpringIocApplication {public static void main(String[] args) {//获取Spring上下文对象ApplicationContext context = SpringApplication.run(J20240323SpringIocApplication.class, args);//从Spring上下文中获取对象//通过类对象获取UserController userController1 = context.getBean(UserController.class);userController1.say();//通过对象名获取UserController userController2 = (UserController)context.getBean("userController");userController2.say();//通过类对象和对象名获取UserController userController3 = context.getBean("userController", UserController.class);userController3.say();}
}

注意:上述几种方式获取到的对象都是同一个对象,把类交给Spring管理时,默认是单例模式。 

ApplicationContext对比BeanFactory:

上面我们演示的是使用ApplicationContext来获取Bean,我们也可以使用BeanFactory获取Bean,接下来我们讲解一下它们的区别和联系

区别:

  1. 初始化时机:BeanFactory 是在第一次请求获取 Bean 时才进行实例化和初始化,延迟加载;而 ApplicationContext 在容器启动时就将所有 Bean 进行实例化和初始化。
  2. 性能:ApplicationContext 比 BeanFactory 更强大也更复杂,因为 ApplicationContext 支持更多的特性,如国际化处理、事件传播、AOP 管理等,所以在性能上 BeanFactory 会更轻量级一些。
  3. 功能:ApplicationContext 继承自 BeanFactory,并且提供了更多的高级特性,如自动装配、消息资源处理、事件传播等。因此,ApplicationContext 功能更加强大。

联系:

  1. ApplicationContext 可以看作是 BeanFactory 的扩展版本,提供了更多功能和便利,是更高级的容器接口。
  2. 无论是 BeanFactory 还是 ApplicationContext,它们都负责管理 Bean 实例的生命周期、依赖注入等工作,是 Spring 框架中重要的组件。
  3. 在实际开发中,如果应用对性能要求较高,可以使用 BeanFactory;如果需要更多的企业级特性和功能,建议使用 ApplicationContext。通常情况下,我们更倾向于使用 ApplicationContext,因为它提供了更多的便利和功能,能够更好地支持复杂的应用场景。 
3.1.2 方法注解

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

  • 使用外部包里的类,没办法添加类注解
  • 一个类需要多个对象

这种场景就需要使用方法注解@Bean

使用方法:

@Bean注解需要在类注解注解下的类中使用,使用时创建一个函数,返回一个想要交给Spring管理的对象即可:

@Controller
public class UserController {public void say() {System.out.println("Hello @Controller");}@Beanpublic User getUser1() {return new User();}@Beanpublic User getUser2() {return new User();}
}

注意:使用@Bean注解交给Spring管理的对象的名称和方法名一致,获取Bean时,如果对应类的对象在Spring中存在多个,则不能只通过类对象获取:

@SpringBootApplication
public class J20240323SpringIocApplication {public static void main(String[] args) {User user1 = (User) context.getBean("getUser1", User.class);user1.say();User user2 = (User) context.getBean("getUser2", User.class);user2.say();System.out.println(user1 == user2);}
}

3.2 扫描路径

一个对象要被Spring管理,不仅需要加上注解,还需要被Spring扫描到才行,Spring的默认扫描路径是启动类所在的目录及子目录,我们可以通过@ComponentScan注解修改扫描路径:

@ComponentScan(basePackages = "com.example")
@SpringBootApplication
public class J20240323SpringIocApplication {public static void main(String[] args) {SpringApplication.run(J20240323SpringIocApplication.class, args);}
}

4. DI 详解

4.1 属性注入

属性注入就是通过@Autowired注解,注入,前面我们简单的演示过,只需把@Autowired写在对应的引用上即可获取对应对像的引用

@Service
public class User {public void say() {System.out.println("helle");}
}
@Controller
public class UserController {@Autowiredprivate User user;public void say() {user.say();}
}

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

4.2 构造方法注入

我只需在需要引入依赖的类中添加一个初始化的构造方法,即可完成依赖注入

@Controller
public class UserController {private User user;public UserController(User user) {this.user = user;}public void say() {user.say();}
}

运行代码后同样能打印hello

注意:交给Spring管理的类,Spring会使用反射获取到对应类的构造方法来创建对象,如果使用构造方法注入,并且本类中存在多个构造函数,需要使用@Autowired指定使用哪个,否者可能会出错:

@Controller
public class UserController {private User user;public UserController() {}@Autowiredpublic UserController(User user) {this.user = user;}public void say() {user.say();}
}

4.3 Setter 方法注入

给需要注入依赖的类添加带@Autowired注解的set方法也可以注入依赖:

@Controller//将对象储存到Spring中
public class UserController {@Autowiredpublic UserController(User user) {this.user = user;}public void say() {user.say();}
}

4.4 三种注入方式的优缺点 

4.4.1 属性注入
  • 优点:

    • 简洁,使用方便。
  • 缺点:

    • 只能用于IoC容器,非IoC容器不可用,并且只有在使用的时候才会出现空指针异常(NPE)。
    • 不能注入一个Final修饰的属性。
4.4.2 构造函数注入
  • 优点:

    • 可以注入final修饰的属性。
    • 注入的对象不会被修改。
    • 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法。
    • 通用性好,构造方法是JDK支持的,所以更换任何框架,它都是适用的。
  • 缺点:

    • 注入多个对象时,代码会比较繁琐。
4.4.3 Setter注入
  • 优点:

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

    • 不能注入一个Final修饰的属性。
    • 注入对象可能会被改变,因为setter方法可能会被多次调用,就有被修改的风险。

根据具体情况和需求,可以选择适合的注入方式来管理对象之间的依赖关系。

4.5 @Autowired存在的问题

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

public class User {String name;public User() {}public User(String name) {this.name = name;}public void say() {System.out.println(name);}
}
@Configuration
public class UserConfig {@Beanpublic User User1() {return new User("a");}@Beanpublic User User2() {return new User("b");}@Beanpublic User User3() {return new User("c");}
}
@Controller//将对象储存到Spring中
public class UserController {@Autowiredpublic void say() {user.say();}
}

项目启动失败,我们在描述这里看到,Spring找到了三个对象 。使用@Autowired注入时,Spring会先根据对象的类型去寻找,如果只找到一个就直接用,如果找到多个,再根据名称(这里我们是user)来寻找,如果没有对应名称就会抛异常。

我们可以使用 @Primary ,@Qualifier,@Resource 这三个注解来解决。

@Primary:指定默认使用哪一个Bean

@Configuration
public class UserConfig {@Primary@Beanpublic User User1() {return new User("a");}@Beanpublic User User2() {return new User("b");}@Beanpublic User User3() {return new User("c");}
}

@Qualifier:指定使用Bean的名称

@Controller//将对象储存到Spring中
public class UserController {@Qualifier("User2")@Autowiredprivate User user;public void say() {user.say();}
}

使用@Resource注入依赖:

@Controller//将对象储存到Spring中
public class UserController {@Resource(name = "User3")private User user;public void say() {user.say();}
}

5. 总结

  1. Spring: Spring 是一个开源的Java框架,它提供了丰富的功能和组件,用于简化企业级应用程序的开发。Spring 框架提供了依赖注入、面向切面编程、事务管理、数据访问等功能,是一个全面的企业应用开发解决方案。
  2. Spring MVC: Spring MVC 是 Spring 框架中的一个模块,即Spring功能的一部分,用于构建 Web 应用程序的 MVC(Model-View-Controller)架构。Spring MVC 提供了基于注解的方式来定义控制器、处理请求和渲染视图,使得开发 Web 应用程序更加简单和灵活。
  3. Spring Boot: Spring Boot 是基于 Spring 框架的快速开发框架,它简化了 Spring 应用程序的搭建和部署过程。Spring Boot 提供了自动配置、约定优于配置、快速启动等特性,使得开发者可以更快速地搭建和运行 Spring 应用程序,而不需要进行繁琐的配置。简单来说Spring Boot 就是对Spring的使用做了一些简化使我们开发效率更高,但其所有的功能都是基于Spring实现的。

 

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

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

相关文章

算法学习——LeetCode力扣动态规划篇10(583. 两个字符串的删除操作、72. 编辑距离、647. 回文子串、516. 最长回文子序列)

算法学习——LeetCode力扣动态规划篇10 583. 两个字符串的删除操作 583. 两个字符串的删除操作 - 力扣&#xff08;LeetCode&#xff09; 描述 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个…

HarmonyOS 应用开发之Stage模型绑定FA模型ServiceAbility

本小节介绍Stage模型的两种应用组件如何绑定FA模型ServiceAbility组件。 UIAbility关联访问ServiceAbility UIAbility关联访问ServiceAbility和UIAbility关联访问ServiceExtensionAbility的方式完全相同。 import common from ohos.app.ability.common; import hilog from o…

Qt源码调试步骤记录

1.源码&#xff1a; 两种方式&#xff0c;要么安装qt时选择source&#xff0c;要么从官网下载源码&#xff0c;然后在qt creator中设置路径。二选一即可。我选的第二种。 1.1.第一种&#xff0c;安装时选择source&#xff1a; 1.2.第二种&#xff0c;下载源码设置路径&#x…

安装部署MariaDB数据库管理系统

目录 一、初始化MariaDB服务 1、安装、启动数据库服务程序、将服务加入开机启动项中。 2、为保证数据库安全性和正常运转&#xff0c;需要对数据库程序进行初始化操作。 3、配置防火墙&#xff0c;放行对数据库服务程序的访问请求&#xff0c;允许管理员root能远程访问数据…

灵动翻译音频文件字幕提取及翻译;剪映视频添加字幕

参考&#xff1a;视频音频下载工具 https://tuberipper.com/21/save/mp3 1、灵动翻译音频文件字幕提取及翻译 灵动翻译可以直接chorme浏览器插件安装&#xff1a; 点击使用&#xff0c;可以上传音频文件 上传后自动翻译&#xff0c;然后点击译文即可翻译成中文&#xff0c;…

跨越界限:AI大模型在关键技术领域的综合应用

AI大模型学习 在当前技术环境下&#xff0c;AI大模型学习不仅要求研究者具备深厚的数学基础和编程能力&#xff0c;还需要对特定领域的业务场景有深入的了解。通过不断优化模型结构和算法&#xff0c;AI大模型学习能够不断提升模型的准确性和效率&#xff0c;为人类生活和工作带…

深入理解MySQL:拼接字符串、查询、删除表和创建索引的关键命令

MySQL是一种功能强大的关系型数据库管理系统&#xff0c;广泛应用于各种类型的应用程序中。本文将介绍MySQL中一些常用的关键命令&#xff0c;包括拼接字符串、查询、删除表和创建索引&#xff0c;帮助读者更好地理解和利用MySQL数据库。 mysql拼接字符串 在MySQL中&#xf…

android WMS服务

android WMS服务 WMS的定义 窗口的分类 WMS的启动 WindowManager Activity、Window、DecorView、ViewRootImpl 之间的关系 WindowToken WMS的定义 WMS是WindowManagerService的简称&#xff0c;它是android系统的核心服务之一&#xff0c;它在android的显示功能中扮演着…

python安装删除以及pip的使用

目录 你无法想象新手到底会在什么地方出问题——十二个小时的血泪之言&#xff01; 问题引入 python modify setup 隐藏文件夹 环境变量的配置 彻底删除python 其他零碎发现 管理员终端 删不掉的windous应用商店apps 发现问题 总结 你无法想象新手到底会在什么地方…

StructStreaming Batch mode和Continuous mode

StructStreaming Batch mode和Continuous mode 让我们把目光集中到 Structured Streaming&#xff0c;也就是流处理引擎本身。Structured Streaming 与 Spark MLlib 并列&#xff0c;是 Spark 重要的子框架之一。值得一提的是&#xff0c;Structured Streaming 天然能够享受 S…

JWFD流程图转换为矩阵数据库的过程说明

在最开始设计流程图的时候&#xff0c;请务必先把开始节点和结束节点画到流程图上面&#xff0c;就是设计器面板的最开始两个按钮&#xff0c;先画开始点和结束点&#xff0c;再画中间的流程&#xff0c;然后保存&#xff0c;这样提交到矩阵数据库就不会出任何问题&#xff0c;…

本地搭建多人协作ONLYOFFICE文档服务器并结合Cpolar内网穿透实现公网访问远程办公

文章目录 1. 安装Docker2. 本地安装部署ONLYOFFICE3. 安装cpolar内网穿透4. 固定OnlyOffice公网地址 本篇文章讲解如何使用Docker在本地服务器上安装ONLYOFFICE&#xff0c;并结合cpolar内网穿透实现公网访问。 Community Edition允许您在本地服务器上安装ONLYOFFICE文档&…

【Vue】搭建第一个vue3+vite前段项目

不要奇怪我为啥突然开始写前端的文章&#xff0c;唉&#xff0c;一切都是公司的任务罢了。 其实这周学习了前端和coverity&#xff0c;但是后者就算学了我也不能写在我博客里&#xff0c;所以还是写一写前端吧。 node.js 和 npm npm是一个类似于 maven 的包管理器。 去以下…

Excel数据分子-----------VLOOKUP函数

选择区域的时候一定要把查询条件所处的列放在第一列。 跨表使用 一定要在括号外边摁回车键。 通配符查找 列后面跟上**&“*”** 近似查找 数值格式问题 HLOOP函数 这个是在行中进行搜索 indexmatch实现匹配查找 先利用match查找出在哪一行&#xff0c;再利用index定…

ssm007亚盛汽车配件销售业绩管理统+jsp

亚盛汽车配件销售业绩管理系统设计与实现 摘 要 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;因此传统管理方式就不适合。为了让亚盛汽车配件销售信息的管理模式进行升级&#xff0c;也为了更好的维护亚盛汽车配件销售信息&am…

【Java八股学习】Redis持久化 思维导图

说明 文章内容通过学习小林Coding内的优质文章后整理而来&#xff0c;整理成思维导图的方式是为了帮助自己理解、记忆和复习。如若侵权请联系删除&#xff0c;再次对小林Coding内的优质文章表示感谢。参考文章如下&#xff1a; AOF 持久化是怎么实现的&#xff1f;RDB 快照是…

seldom 实战技巧

seldom 是我一直在维护的自动化测试框架。目前GitHub已经 500 star。 最近在项目中使用Seldom poium 编写自动化测试用例。接下来&#xff0c;我就分享一些使用技巧。 如何参数化测试用例 网站的首页&#xff0c;如上面的导航。于是&#xff0c;开始利用poium编写元素定位。…

基于 FFmpeg 和 SDL 的音视频同步播放器

基于 FFmpeg 和 SDL 的音视频同步播放器 基于 FFmpeg 和 SDL 的音视频同步播放器前置知识音视频同步简介复习DTS、PTS和时间基 程序框架主线程解复用线程音频解码播放线程视频解码播放线程 音视频同步逻辑源程序结果工程文件下载参考链接 基于 FFmpeg 和 SDL 的音视频同步播放器…

Linux: 进程优先级

Linux: 进程优先级 一、进程优先级概念二、如何查看进程优先级三、如何修改进程的优先级&#xff08;PRL vs NI&#xff09;四、为何优先级PRL必须限定范围五、进程其他特性 一、进程优先级概念 优先级的本质就是排队&#xff0c;而排队则是资源不足所引起的。在计算机中&#…

鸿蒙ARKTS--简易的购物网站

目录 一、media 二、string.json文件 三、pages 3.1 登录页面&#xff1a;gouwuPage.ets 3.2 PageResource.ets 3.3 商品页面&#xff1a;shangpinPage.ets 3.4 我的页面&#xff1a;wodePage.ets 3.5 注册页面&#xff1a;zhucePage.ets 3. 购物网站主页面&#xff…