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

Spring IOC与DI:控制反转与依赖注入详解

文章目录

  • 1. IOC介绍
  • 2. DI介绍
  • 3. IOC详解
    • 3.1 Bean的存储
      • 3.1.1 @Controller
    • Bean命名
      • 3.1.2 @Service(服务存储)
      • 3.1.3 @Repository(仓库存储)
      • 3.1.4 @Component(组件存储)
      • 3.1.5 @Configuration(配置存储)
      • 3.1.6 为什么要这么多类注解
    • 3.2 方法注解@Bean
      • 3.2.1 定义多个对象
      • 3.2.2 重命名Bean
    • 3.3 扫描路径
  • 4. DI详解
      • 4.1.1 属性注入
      • 4.1.2 构造方法注入
      • 4.1.3 Setter注入
    • 4.2 三种注入优缺点分析
    • 4.3 @Autowired存在问题(同类多Bean情况)
  • 5. Spring, SpringMVC, SpringBoot

1. IOC介绍

Spring 是包含了众多工具方法的 IoC 容器
容器: 容器是用来容纳某种物品的(基本)装置

IoC 是Spring的核心思想, 在类上面添加 @RestController, @Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IOC思想

IOC: Inversion of Control (控制反转), 获得依赖对象的过程被反转了也就是说, 当需要某个对象时, 传统开发模式中需要自己通过 new 创建对象, 现在不需要再进行创建, 把创建对象的任务交给容器, 程序中只需要依赖注入 (Dependency Injection,DI)就可以了. 这个容器称为:IoC容器. Spring是一个IoC容器, 所以有时Spring 也称为Spring 容器

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

资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。IoC容器具备以下优点:

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

2. DI介绍

DI: Dependency Injection(依赖注入), 容器在运行期间, 动态的为应用程序提供运行时所依赖的资源,称之为依赖注入.

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

IOC&DI的简单使用

  1. Service层及Dao层的实现类,交给Spring管理: 使用注解: @Component
  2. 在Controller层 和Service层 注入运行时依赖的对象: 使用注解 @Autowired

3. IOC详解

我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI的操作。

前面我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象, 也就是bean的存储

3.1 Bean的存储

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

3.1.1 @Controller

使用 @Controller 存储 bean

@ControllerpublicclassUserController{publicvoidsayHi(){System.out.println("Hello");}}

根据类型获取对象

@SpringBootApplicationpublicclassTestApplication{publicstaticvoidmain(String[]args){ApplicationContextcontext=SpringApplication.run(TestApplication.class,args);// 从上下文中获取对象UserControlleruserController=context.getBean(UserController.class);userController.sayHi();}}

Spring容器中, 同一个类型存在多个bean的话, 怎么来获取呢?
ApplicationContext 也提供了其他获取bean的方式, ApplicationContext 获取bean对象的功能, 是父类BeanFactory提供的功能

// 1. 根据bean名称获取beanObjectgetBean(Stringvar1)throwsBeansException;// 2. 根据bean名称和类型获取bean<T>TgetBean(Stringvar1,Class<T>var2)throwsBeansException;// 3. 按bean名称和构造函数参数动态创建bean,只适用于具有原型(prototype)作用域的beanObjectgetBean(Stringvar1,Object...var2)throwsBeansException;// 4. 根据类型获取bean<T>TgetBean(Class<T>var1)throwsBeansException;// 5. 按bean类型和构造函数参数动态创建bean, 只适用于具有原型(prototype)作用域的bean<T>TgetBean(Class<T>var1,Object...var2)throwsBeansException;

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

Spring bean是Spring框架在运行时管理的对象, Spring会给管理的对象起一个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象.

Bean命名

程序开发人员不需要为bean指定名称(BeanId), 如果没有显式的提供名称(BeanId),Spring容器将为该bean生成唯一的名称.
命名约定使用Java标准约定作为实例字段名. 也就是说,bean名称以小写字母开头,然后使用驼峰式大小写.
也有一些特殊情况, 当有多个字符并且第一个和第二个字符都是大写时, 将保留原始的大小写. 这些规则与java.beans.Introspector.decapitalize (Spring在这里使用的)定义的规则相同.

类名: UserController, Bean的名称为: userController
类名: UController, Bean的名称为: UController

publicstaticvoidmain(String[]args){ApplicationContextcontext=SpringApplication.run(TestApplication.class,args);// 根据bean类型, 从上下文中获取对象UserControlleruserController1=context.getBean(UserController.class);// 根据bean名称, 从上下文中获取对象UserControlleruserController2=(UserController)context.getBean("userController");// 根据bean名称+类型, 从上下文中获取对象UserControlleruserController3=context.getBean("userController",UserController.class);System.out.println(userController1);System.out.println(userController2);System.out.println(userController3);}

地址一样, 说明对象是一个

ApplicationContext VS BeanFactory
• 继承关系和功能方面来说:Spring 容器有两个顶级的接口:BeanFactory 和ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能力,而ApplicationContext 属于 BeanFactory 的子类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化支持、资源访问支持、以及事件传播等方面的支持.
• 从性能方面来说:ApplicationContext 是一次性加载并初始化所有的 Bean 对象,而BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)

3.1.2 @Service(服务存储)

@ServicepublicclassUserService{publicvoidsayHi(){System.out.println("Hello");}}

3.1.3 @Repository(仓库存储)

@RepositorypublicclassUserRepository{publicvoidsayHi(){System.out.println("Hi, UserRepository~");}}

3.1.4 @Component(组件存储)

@ComponentpublicclassUserComponent{publicvoidsayHi(){System.out.println("Hi, UserComponent~");}}

3.1.5 @Configuration(配置存储)

@ConfigurationpublicclassUserConfiguration{publicvoidsayHi(){System.out.println("Hi,UserConfiguration~");}}

3.1.6 为什么要这么多类注解

程序员看到类注解之后,就能直接了解当前类的用途
• @Controller:控制层, 接收请求, 对请求进行处理, 并进行响应.
• @Servie:业务逻辑层, 处理具体的业务逻辑.
• @Repository:数据访问层,也称为持久层. 负责数据访问操作
• @Configuration:配置层. 处理项目中的一些配置信息.

查看@Controller / @Service / @Repository / @Configuration 等注解的源码发现:
其实这些注解里面都有一个注解 @Component ,说明它们本身就是属于 @Component 的"子类"
@Component 是一个元注解,也就是说可以注解其他类注解, @Controller , @Service 和 @Repository 用于更具体的用例(分别在控制层, 业务逻辑层, 持久化层), 在开发过程中, 如果你要在业务逻辑层使用 @Component 或@Service,显然@Service是更好的选择

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

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

3.2 方法注解@Bean

这种场景, 我们就需要使用方法注解 @Bean
在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中

@ComponentpublicclassBeanConfig{@BeanpublicPersonperson(){Personperson=newPerson();person.setAge(20);returnperson;}}
publicstaticvoidmain(String[]args){ApplicationContextcontext=SpringApplication.run(TestApplication.class,args);Personperson=context.getBean(Person.class);System.out.println(person.getAge());}

3.2.1 定义多个对象

对于同一个类, 如何定义多个对象呢?

@ComponentpublicclassBeanConfig{@BeanpublicPersonperson1(){Personperson=newPerson();person.setAge(20);returnperson;}@BeanpublicPersonperson2(){Personperson=newPerson();person.setAge(21);returnperson;}}

定义了同一个类的对象后, 如果再根据Bean的类型获取对象, 就会发生报错, 期望只有一个匹配, 结果发现了两个.
@Bean 注解的bean, bean的名称就是它的方法名, 接下来我们根据名称来获取bean对象

publicstaticvoidmain(String[]args){ApplicationContextcontext=SpringApplication.run(TestApplication.class,args);Personperson1=(Person)context.getBean("person1");Personperson2=(Person)context.getBean("person2");System.out.println(person1+","+person2);}

3.2.2 重命名Bean

可以通过设置 name 属性给 Bean 对象进行重命名操作

@Bean(name={"p1","person1"})publicPersonperson1(){Personperson=newPerson();person.setAge(20);returnperson;}
publicstaticvoidmain(String[]args){ApplicationContextcontext=SpringApplication.run(TestApplication.class,args);Personperson1=(Person)context.getBean("p1");Personperson2=(Person)context.getBean("person1");System.out.println(person1+","+person2);}

name=可以省略, 写成@Bean({“p1”, “person1”})
只有一个名称时候, {}也可以省略, @Bean(“p1”)

3.3 扫描路径

使用前面学习的四个注解声明的bean,一定会生效吗?
不一定(原因:bean想要生效,还需要被Spring扫描)
使用五大注解声明的bean,要想生效, 还需要配置扫描路径, 让Spring扫描到这些注解也就是通过 @ComponentScan 来配置扫描路径

@SpringBootApplication@ComponentScan({"com.example.test.demos"})

那为什么前面没有配置 @ComponentScan注解也可以呢?

@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication 中了?
默认扫描的范围是SpringBoot启动类所在包及其子包, 在配置类上添加 @ComponentScan 注解, 该注解默认会扫描该类所在的包下所有的配置类, 把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到

修改META-INF 自动配置文件也能做到加载Bean的效果, 一般开发公共组件会用上, 可自行简单了解

4. DI详解

依赖注入是一个过程,是指IoC容器在创建Bean时, 去提供运行时所依赖的资源,而资源指的就是对象.
DI简单来说, 就是把对象取出来放到某个类的属性中.
关于依赖注入, Spring也给我们提供了三种方式

4.1.1 属性注入

属性注入是使用 @Autowired

@ControllerpublicclassUserController{// 注入方法1: 属性注入@AutowiredprivateUserServiceuserService;publicvoidsayHi(){userService.sayHi();}}

4.1.2 构造方法注入

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

@ControllerpublicclassUserController{//注入方法2: 构造方法privateUserServiceuserService;@AutowiredpublicUserController(UserServiceuserService){this.userService=userService;}publicvoidsayHi(){userService.sayHi();}}

4.1.3 Setter注入

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

@ControllerpublicclassUserController{// 注入方法3: Setter方法注入privateUserServiceuserService;@AutowiredpublicvoidsetUserService(UserServiceuserService){this.userService=userService;}publicvoidsayHi(){userService.sayHi();}}

4.2 三种注入优缺点分析

属性注入:
优点:

  1. 简洁,使用方便

缺点:

  1. 只能用于 IoC 容器, 并且只有在使用的时候才会出现 NPE(空指针异常);
  2. 不能注入一个Final修饰的属性

构造函数注入
优点:

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

缺点:

  1. 注入多个对象时, 代码会比较繁琐

Setter注入
优点:

  1. 方便在类实例之后, 重新对该对象进行配置或者注入

缺点:

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

4.3 @Autowired存在问题(同类多Bean情况)

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

@Bean(name="p1")publicPersonperson1(){Personperson=newPerson();person.setAge(20);returnperson;}@BeanpublicPersonperson2(){Personperson=newPerson();person.setAge(21);returnperson;}


Spring提供了以下几种解决方案:
@Primary
@Qualifier
@Resource

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

@Primary@Bean(name="p1")publicPersonperson1(){Personperson=newPerson();person.setAge(20);returnperson;}@BeanpublicPersonperson2(){Personperson=newPerson();person.setAge(21);returnperson;}

使用@Qualifier注解:指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称; @Qualifier注解不能单独使用,必须配合@Autowired使用

@Autowired@Qualifier("person2")privatePersonperson;

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

@Resource(name="p1")privatePersonperson;

@Autowird 与 @Resource的区别
@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
@Autowired 默认是按照类型注入,而@Resource是按照名称注入. 相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean

5. Spring, SpringMVC, SpringBoot

学到这里对Spring, Spring Boot 和Spring MVC应该有了一定的了解, 简单总结下三者关系以及区别
Spring: 简单来说, Spring 是一个开发应用框架, 其目的是用于简化企业级应用程序开发
Spring的主要功能: 管理对象,以及对象之间的依赖关系, 面向切面编程, 数据库事务管理, 数据访问, web框架支持等
但是Spring具备高度可开放性, 并不强制依赖Spring, 开发者可以自由选择Spring的部分或者全部, Spring可以无缝继承第三方框架

Spring MVC: Spring MVC: Spring MVC是Spring的一个子框架, Spring诞生之后, 大家觉得很好用, 于是按照MVC模式设计了一个 MVC框架, 主要用于开发WEB应用和网络接口,所以,Spring MVC 是一个Web框架
Spring MVC基于Spring进行开发的, 天生的与Spring框架集成. 可以让我们更简洁的进行Web层开发, 支持灵活的 URL 到页面控制器的映射, 提供了强大的约定大于配置的契约式编程支持

Spring Boot: Spring Boot是对Spring的一个封装, 为了简化Spring应用的开发而出现的,中小型企业,没有成本研究自己的框架, 使用Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发人员更加专注于Spring应用的开发,而无需过多关注XML的配置和一些底层的实现。
Spring Boot是个脚手架, 插拔式搭建项目, 可以快速的集成其他框架进来.
比如想使用SpringBoot开发Web项目, 只需要引入Spring MVC框架即可, Web开发的工作是SpringMVC完成的, 而不是SpringBoot, 想完成数据访问, 只需要引入Mybatis框架即可

最后一句话总结: Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的一个MVC 框架,而Spring Boot 是基于Spring的一套快速开发整合包

http://www.cnnetsun.cn/news/3945134.html

相关文章:

  • Translumo终极指南:如何免费实现游戏与视频的实时屏幕翻译
  • B站视频下载工具:解锁大会员4K和充电专属内容的秘密武器
  • 从零构建角色化终端:以安全审计为例的Otaku实战指南
  • PostgreSQL数据库监控:15个核心指标与实施策略
  • c语言链表与结构体
  • 深入探讨辽阳网站建设58的行业现状与未来趋势,揭秘辽阳网站优化58的核心竞争力及辽阳建站公司58的服务流程解析
  • FlowChartCharter:基于多智能体协作与YAML流程配置实现高精度知识库问答
  • PTA基础编程题目集 7-23币值转换(C++语言实现)
  • 中介者模式:解耦复杂交互的设计模式实践
  • 网盘直链下载助手完整指南:九大网盘高速下载免费解决方案
  • Adobe-GenP 3.0:5分钟完成Adobe全系列软件激活的终极指南
  • 关于cesium初始化配置参数说明
  • Noto Emoji字体终极指南:告别乱码,轻松实现跨平台统一表情显示
  • 金融数据分类分级实战系列三:生成全量数据清单
  • WorkshopDL高效指南:一站式免费获取Steam创意工坊模组的智能解决方案
  • VMware虚拟机安装Windows XP Media Centre Edition完整教程与优化指南
  • 终极指南:5个简单步骤让旧Mac免费升级最新macOS系统
  • 多端商城怎么做?一套代码 vs 各端各写,4 个开源项目的实现路线对比
  • 2.宏碁掠夺者擎控制台无法识别电源状态?一次驱动层排查与修复实录
  • ArrayList与LinkedList核心差异及性能对比
  • HTTP请求死循环:原理、检测与防御实践
  • 终极文档下载神器:如何免费下载百度文库、原创力文档等30+平台内容
  • 告别繁琐手动操作:百度网盘批量转存神器5分钟上手指南
  • 如何实现跨平台游戏模组下载:WorkshopDL终极完整指南
  • 从零构建游戏服务器:基于Netty与Java的DNF私服技术解析
  • Palantir 给中国企业上了一课:AI 落地缺的不是模型,是“操作系统“
  • HTTP解析器核心原理与实战:从状态机到高性能网络编程
  • Unity物理系统跨平台适配鸿蒙:从核心原理到实战优化
  • 百度网盘批量转存工具深度解析:从技术原理到高效实战
  • 原神帧率解锁终极指南:3步轻松突破60FPS限制的完整教程