Spring基础 - Spring核心之控制反转(IOC)

Spring基础 - Spring核心之控制反转(IOC)

  概要

  我们知道,Spring提供的容器又称为IoC容器。其实,IoC(Inversion of control )不是 Spring 提出来的,它们在 Spring 之前其实已经存在了,只不过当时更加偏向于理论。Spring 在技术层次将这个思想进行了很好的实现。

  一、IoC(Inversion of Control )

  1. 什么是IoC?

  IoC 即控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。

  例如:现有类 A 依赖于类 B

  传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来使用

  IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面去取即可。

  从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)

  2、为什么叫控制反转?

  控制 :指的是对象创建(实例化、管理)的权力

  反转 :控制权交给外部环境(IoC 容器)

 

  3. IoC 解决了什么问题?

  IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?

  1)对象之间的耦合度或者说依赖程度降低;

  2)资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。

  例如:现有一个针对 User 的操作,利用 Service 和 Dao 两层结构进行开发在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在UserServiceImpl 中手动 new 出 IUserDao 的具体实现类 UserDaoImpl(不能直接 new 接口类)。如下图:

 

  很完美,这种方式也是可以实现的,但是我们想象一下如下场景:开发过程中突然接到一个新的需求,针对IUserDao 接口开发出另一个具体实现类。因为 Server 层依赖了IUserDao的具体实现,所以我们需要修改UserServiceImpl中 new 的对象。如果只有一个类引用了IUserDao的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了IUserDao的具体实现的话,一旦需要更换IUserDao 的实现方式,那修改起来将会非常的头疼。如下图:

 

   使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接从 IoC 容器中获取就可以了。

   对于我们常用的 Spring 框架来说, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。不过,IoC 在其他语言中也有应用,并非 Spring 特有。

   二、DI(Dependency Injection)

   依赖注入是 IOC 的一种典型实现方式。简单的说,依赖注入就是把对象A所依赖的其他对象,以属性或者构造函数的方式传递到对象A的内部,而不是直接在对象A内实例化。

   其目的就是为了让对象A和其依赖的其他对象解耦,减少二者的依赖。即通过“注入”的方式去解决依赖问题

   依赖注入有三种方式:构造函数注入、setter方法注入、接口注入(已淘汰)。

   举个例子,商家将添加剂注入到橙汁中。下面通过这几种依赖注入的方式来讲解对IOC容器实例的应用。

   首先我们先分别创建橙汁OrangeJuice类和添加剂Additive类。

   创建OrangeJuice类,代码如下:

1 /**
2  * @author xxx
3  * @desc 橙汁类
4  */
5 public class OrangeJuice {
6     public void needOrangeJuice(){
7         System.out.println("消费者点了一杯橙汁(无添加剂)...");
8     }
9 }

   创建添加剂Additive类,代码如下:

1 /**
2  * @author tanghaorong
3  * @desc 添加剂类
4  */
5 public class Additive  {
6     public void addAdditive(){
7         System.out.println("奸商在橙汁中添加了添加剂...");
8     }
9 }

   原始方式:

   最原始的方式就是没有IOC容器的情况下,我们要在主体对象中使用new的方式来获取被依赖对象。我们看一下在主体类中的写法,添加剂类一直不变:

1 public class OrangeJuice {
2     public void needOrangeJuice(){
3         //创建添加剂对象
4         Additive additive = new Additive();
5         //调用加入添加剂方法
6         additive.addAdditive();
7         System.out.println("消费者点了一杯橙汁(有添加剂)...");
8     }
9 }

   创建测试类:

1 public class Test {
2     public static void main(String[] args) {
3         OrangeJuice orangeJuice = new OrangeJuice();
4         orangeJuice.needOrangeJuice();
5     }
6 }

   1. 构造函数注入

   构造器注入,顾名思义就是通过构造函数完成依赖关系的注入。首先我们看一下spring的配置文件:   

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="
 4         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- bean definitions here -->
 5 
 6     <!--将指定类都配置给Spring,让Spring创建其对象的实例,一个bean对应一个对象-->
 7     <bean id="additive" class="com.thr.Additive"></bean>
 8 
 9     <bean id="orangeJuice" class="com.thr.OrangeJuice">
10         <!--通过构造函数注入,ref属性表示注入另一个对象-->
11         <constructor-arg ref="additive"></constructor-arg>
12     </bean>
13 </beans>

   使用构造函数方式注入的前提必须要在主体类中创建构造函数,所以我们再来看一下,构造器表示依赖关系的写法,代码如下所示:

 1 public class OrangeJuice {
 2     //引入添加剂参数
 3     private Additive additive;
 4     //创建有参构造函数
 5     public OrangeJuice(Additive additive) {
 6         this.additive = additive;
 7     }
 8 
 9     public void needOrangeJuice(){
10         //调用加入添加剂方法
11         additive.addAdditive();
12         System.out.println("消费者点了一杯橙汁(有添加剂)...");
13     }
14 }

   创建测试类: 

 1 public class Test {
 2     public static void main(String[] args) {
 3         //1.初始化Spring容器,加载配置文件
 4         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
 5         //2.通过容器获取实例对象,getBean()方法中的参数是bean标签中的id
 6         OrangeJuice orangeJuice = (OrangeJuice) applicationContext.getBean("orangeJuice");
 7         //3.调用实例中的方法
 8         orangeJuice.needOrangeJuice();
 9     }
10 }

   2. setter方法注入

   setter注入在实际开发中使用的非常广泛,因为它可以在对象构造完成后再注入,这样就更加直观,也更加自然。我们来看一下spring的配置文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="
 4         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- bean definitions here -->
 5 
 6     <!--将指定类都配置给Spring,让Spring创建其对象的实例,一个bean对应一个对象-->
 7     <bean id="additive" class="com.thr.Additive"></bean>
 8 
 9     <bean id="orangeJuice" class="com.thr.OrangeJuice">
10         <!--通过setter注入,ref属性表示注入另一个对象-->
11         <property name="additive" ref="additive"></property>
12     </bean>
13 </beans>

   接着我们再来看一下,setter表示依赖关系的写法:

 1 public class OrangeJuice {
 2     //引入添加剂参数
 3     private Additive additive;
 4     //创建setter方法
 5     public void setAdditive(Additive additive) {
 6         this.additive = additive;
 7     }
 8 
 9     public void needOrangeJuice(){
10         //调用加入添加剂方法
11         additive.addAdditive();
12         System.out.println("消费者点了一杯橙汁(有添加剂)...");
13     }
14 }

   测试类和运行的结果和构造器注入的方式是一样的,所以这里就不展示了。

   3. 接口注入

   接口注入,就是主体类必须实现我们创建的一个注入接口,该接口会传入被依赖类的对象,从而完成注入。

   由于Spring的配置文件只支持构造器注入和setter注入,所有这里不能使用配置文件,此时仅仅起到帮我们创建对象的作用。spring的配置文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="
 4         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- bean definitions here -->
 5 
 6     <!--将指定类都配置给Spring,让Spring创建其对象的实例,一个bean对应一个对象-->
 7     <bean id="additive" class="com.thr.Additive"></bean>
 8 
 9     <bean id="orangeJuice" class="com.thr.OrangeJuice"></bean>
10 </beans>

   创建一个接口如下:

 1 //创建注入接口
 2 public interface InterfaceInject {
 3     void injectAdditive(Additive additive);
 4 }
 5        主体类实现接口并且初始化添加剂参数:
 6 
 7 //实现InterfaceInject
 8 public class OrangeJuice implements InterfaceInject {
 9     //引入添加剂参数
10     private Additive additive;
11     //实现接口方法,并且初始化参数
12     @Override
13     public void injectAdditive(Additive additive) {
14         this.additive = additive;
15     }
16 
17     public void needOrangeJuice(){
18         //调用加入添加剂方法
19         additive.addAdditive();
20         System.out.println("消费者点了一杯橙汁(有添加剂)...");
21     }
22 }

   创建测试类:

 1 public class Test {
 2     public static void main(String[] args) {
 3         //1.初始化Spring容器,加载配置文件
 4         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
 5         //2.通过容器获取实例对象,getBean()方法中的参数是bean标签中的id
 6         OrangeJuice orangeJuice = (OrangeJuice) applicationContext.getBean("orangeJuice");
 7         Additive additive = (Additive) applicationContext.getBean("additive");
 8         //通过接口注入,调用注入方法并且将Additive对象注入
 9         orangeJuice.injectAdditive(additive);
10         //3.调用实例中的方法
11         orangeJuice.needOrangeJuice();
12     }
13 }

   由于接口注入方式它强制被注入对象实现了不必要的接口,具有很强的侵入性,所以这种方式已经被淘汰了。

   

   参考链接:

   https://javaguide.cn/system-design/framework/spring/ioc-and-aop.html

   https://www.cnblogs.com/tanghaorong/p/13364634.html

posted @ 2024-06-08 17:56  欢乐豆123  阅读(17)  评论(0编辑  收藏  举报