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