IOC(Inversion Of Control)
1.概念:
- 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
- IoC是一种设计思想。
2.一句话阐述控制反转:
-
将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
-
控制指的是创建对象的控制权。
-
反转指的是将对象控制权完全交由IoC容器,并由IoC来管理对象间的相互依赖关系。
-
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
-
3.简单的生活例子(工厂模式):
假如你现在很想喝可乐,然后你试着去制作一瓶可乐:
那么你需要做些什么呢?
- 购买原料:可口可乐的糖浆(可口可乐的灵魂),碳酸水,糖,焦糖,磷酸,咖啡因。
- 按一定比例进行混合,搅拌。
- 特殊工艺对可乐进行成品制作。
- 装瓶,压盖。
- 最后得到一瓶图中的可乐。
但是你的目的仅仅是想喝一瓶可乐,所以对你而言,最简单的办法就是直接买一瓶,从哪来,工厂。
于是,所有这些复杂的操作都交给了工厂,你只需要向工厂要,工厂就给你一瓶成品。
在上述过程中,发生了这样的改变:
- 由你自己生产一瓶可乐,到工厂为你生产一瓶可乐,你直接拿来使用就可以了。
这个过程其实就是设计模式中的工厂模式:
- 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
为什么要了解工厂模式,因为它就是IOC的核心思想。
4.IOC的由来:
假设你是一名Java开发者,你参与了一个非常巨大的项目,比如,某某大型交易网站,在你负责的代码中,有这样一个模块:
很复杂是吧,其实还可以更加复杂。
这里面包含了许多调用,控制,继承的关系,那么,这样的程序有什么样的缺点呢?
- 可维护性极差。
为什么这样说呢,举个例子,如果在哪一天,由于业务需求,你需要更改其中某一段代码,那可真是一件有趣的事情,你需要更改所有与这个类有关的代码,一句话描述就是:牵一发而动全身。
- 用标准语言来解释的话,这叫:耦合性高,造成可维护性差。
对于企业来说,最好的依赖关系是什么呢,在企业中,项目一般由许多人合作完成,如果A程序员在写B类时,需要C程序员负责的D类,而C一直没有开始写D类,怎么办,A程序员也不写嘛,这肯定是不行的,对于企业而说,最好的依赖关系是:
- 编译时不依赖,运行时才依赖
那么这个问题怎么解决呢,这个时候,你会想起工厂模式,把他们都交给工厂,由工厂来处理它们之间的依赖关系,需要对象的时候,向工厂索要就可以了。这也是IOC最初的由来。
- 为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中,很多的J2EE项目均采用了IOC框架产品Spring。
5.IOC容器:
IOC的核心在IOC容器:
通过IOC容器,成功的将上述的高耦合的代码解耦,对象间的依赖关系,全部由IOC容器来处理。这个容器,也可以看做是是一个工厂。
Spring-IOC容器:
- 在Spring中,核心IOC容器是一个Map结构。
key
是bean的id。value
是Object
类型的,是bean对象。
6.Spring IOC的简单配置:
通过xml文件配置的方式,将对象交给Spring容器来管理:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 将对象交给Spring容器来管理 -->
<bean id="ATService" class="service.impl.ATserviceImpl"></bean>
<bean id="ATDao" class="dao.impl.ATDaoImpl"></bean>
</beans>
-
<bean>
标签:用于让Spring创建对象,并存入IOC容器中。id
:对象唯一标识符。class
:对象的全限定类名。
从ioc容器中获取对象的例子:
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取Bean
ATService obj1 = (ATService)ac.getBean("ATService");
ATDao obj2 = (ATDao)ac.getBean("ATDao");
7.Spring IOC有关的结构:
接口结构图:
-
BeanFactory
是 Spring容器中的顶层接口。 -
ApplicationContext
是BeanFactory
的子接口。 -
二者区别:
ApplicationContext
:只要一读取配置文件,默认情况下就会创建对象(即时加载)。单例对象适用(一个应用只有一个对象的实例)。BeanFactory
:什么使用什么时候创建对象(延迟加载)。多例对象适用(每次访问对象时,都会重新创建新的对象实例)
-
ApplicationContext
常用实现类:ClassPathXmlApplicationContext
:从类的根路径下加载配置文件。每次访问对象时,都会重新创建新的对象实例。AnnotationConfigApplicationContext
:在有访问权限的条件下可以加载磁盘任意路径下的配置文件。AnnotationConfigApplicationContext
:用于读取注解创建容器。
8.Spring对Bean的管理:
实例Bean的方式:
- 方式一:Spring默认会根据无参构造函数来创建类对象。如果 bean中没有默认无参构造函数,将会创建失败。
- 方式二:创建一个实例工厂,让Spring来管理,调用里面的方法来创建对象。
工厂类:
public class Factory{
public ATService createATService{
return new ATServiceImpl();
}
}
配置bean:
<bean id="Factory" class="factory.Factory"></bean>
<bean id="ATService" factory-bean="Factory" factory-method="createATService"></bean>
-
factory-bean
属性:用于指定实例工厂 bean的 id。 -
factory-method
属性:用于指定实例工厂中创建对象的方法。 -
方式三:创建一个静态工厂,让Spring来管理这个工厂,调用这个工厂的方法来创建对象。
工厂类:
public class Factory{
public static ATService createATService{
return new ATServiceImpl();
}
}
配置bean:
<bean id="ATService" class="factory.Factory" factory-method="createATService"></bean>
class
属性:指定静态工厂的全限定类名。factory-method
属性:指定生产对象的静态方法。
管理Bean的作用范围:
<bean>
标签的scope
标签用于指定对象的作用范围。
singleton
:该属性的默认值,是单例的 。prototype
:是多例的。request
,session
:WEB项目中,将创建的对象存入指定的中。global session
:WEB项目中,应用于 Portlet环境,没有Portlet环境,就应用于session
。
管理Bean的生命周期:
<bean>
标签的init-method
属性:指定类中的初始化方法名称。<bean>
标签的destroy-method
属性:指定类中销毁方法名称。
对于单例对象:
- 一个应用只有一个对象的实例。它的作用范围是整个引用。
- 创建容器时就创建了,销毁容器时才销毁。
- 单例对象的生命周期等同于容器。
对于多例对象:
- 每次访问对象时,都会重新创建对象实例。
- 当使用时才创建新的对象实例。
- 当对象长时间不使用时,被JVM垃圾回收。
9.Spring-IOC依赖注入(DI):
- DI—Dependency Injection,即 依赖注入。
- DI是IOC的另一个说法,主要由反射实现。
- Spring通过DI来实现对对象间各种依赖关系的管理。
构造函数注入:
适用于不常变化的数据。
使用构造函数注入,需要在<bean>
标签内使用<constructor-arg>
。
属性:
type
:指定注入的数据类型index
:指定要注入数据在构造函数参数列表的索引位置。name
:参数在构造函数中的名称。value
:对指定参数赋值。(仅可以是基本数据类型和String型)ref
:赋值。(在该配置文件中已配置的其它bean类型)
实例:
<bean id="ATService" class="service.impl.ATserviceImpl">
<constructor-arg name="name" value="ATFWUS"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="date"></constructor-arg>
</bean>
<bean id="date" class="java.util.Date"></bean>
优劣势分析:
- 优势:对象在构造完后就进入了准备的状态,可以立即使用。
- 劣势:当依赖对象过多时,构造方法的参数会比较的长,而通过反射构造对象的时候,对相同类型的参数处理会比较困难。
set方法注入:
需要原类提供set方法。
使用此种注入方式,需要使用<property>
标签。
属性:
name
:set方法名称后面部分,如setName
,这里是name
。value
:给属性赋值。(只可以是基本数据类型和String)ref
:赋值,其它bean类型。
对于注入集合,哈希表等数据类型,可以使用子标签:
<set><value>ATFWUS</value></set>
set集合数据的注入。<array><value>ATFWUS</value></array>
数组数据注入。<list><value>ATFWUS</value></list>
list集合数据注入。- property数据注入:
<property name='mypro'>
<pros>
<prop key='A'>a</prop>
<prop key='B'>b</prop>
</prop>
</property>
- map数据注入:
<property name='mymap'>
<map>
<entry key='A' value='a'></entry>
<entry key='B'>
<value>b</value>
</entry>
</map>
</property>
优劣势分析:
- 优势:创建对象没有明确限制,可以使用默认构造函数。
- 劣势:如果有成员必须有值,那么获取的对象set方法可能没有执行。
10.Spring基于注解的IOC:
创建对象注解–@Component
:
先需要在xml文件中告知Spring创建容器时需要扫描的包:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知Spring创建容器时需要扫描的包 -->
<context:component-scan base-package="com" ></context:component-scan>
</beans>
- 作用:把当前类对象存入Spring容器。
- 相当于XML中的
<bean id="" class="">
。 - 属性
value
:指定bean的id,不指定则默认时当前类名(首字母改小写)。
创建对象注解–@Controller
,@Service
,@Respository
:
- 这三个注解是
@Component
的衍生注解,不过赋予了语义化。 @Controller
:一般用于表现层的注解。@Service
:一般用于业务层的注解。@Repository
:一般用于持久层的注解。
注入数据注解–@Autowired
:
- 自动按照类型注入。只要容器中有唯一bean对象与之匹配,就可以注入成功。
- set方法可以省略。
- 它只能注入其他 bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean的 id,在 spring容器查找,找到了也可以注入成功。找不到就报错。
- 属性
value
:指定 bean的 id。
注入数据注解–@Qualifier
,@Resource
,@Value
:
@Qualifier
在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和@Autowire
一起使用;但是给方法参数注入时,可以独立使用。属性value
:指定 bean的 id。@Resource
:直接按照 Bean的 id注入。只能注入其他 bean类型。属性name
:指定bean的id。@Value
:注入基本数据类型和 String类型数据,属性value
:指定注入的值。
改变作用范围注解–@Scope
- 用于指定 bean的作用范围。
- 相当于:
<bean id="" class="" scope="">
。 - 属性value:指定范围的值。
生命周期注解–@PostConstruct
,@PreDestroy
:
@PostConstruct
指定初始化方法。@PreDestroy
指定销毁方法。
配置注解–@Configuration
,@ComponentScan
:
- 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。
- 使用
@Configuration
在获取容器时需要使用AnnotationApplicationContext
(有@Configuration
注解的类.class)。 @ComponentScan
用于指定 spring在初始化容器时要扫描的包。作用和在 spring的 xml配置文件中的:<context:component-scan base-package="com"/>
是一样的。@ComponentScan
属性basePackages
:用于指定要扫描的包。和该注解中的value
属性作用一样。
获取容器:
ApplicationContext ac =
new AnnotationConfigApplicationContext(SpringConfiguration.class);
配置注解–@Import
:
- 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。
- 属性
value[]
:用于指定其他配置类的字节码。
创建对象注解–@Bean
:
- 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring容器。
- 属性
name
:给当前@Bean注解方法创建的对象指定一个名称(与 bean的 id一样)。 - 默认是当前方法名称。
加载配置文件注解–@PropertySource
:
- 此注解用于加载
.properties
文件中的配置。 - 属性
value[]
:指定 properties文件位置。如果是在类路径下,需要写上类路径classpath
。
11.XML配置与注解的权衡分析
-
XML文件配置的优势:不涉及任何源码,修改后,无需重新编译与部署。
-
注解配置的优势:配置方便简单,维护方便,直接找到类就可以改变相应的配置。
-
使用场景分析:
- 如果项目所需的Bean大都来源于第三方,基于XML配置更好。
- 如果项目所需的Bean大都用户自我开发,基于注释配置更好。
12.IOC优劣势分析
- IOC的优势大大降低了程序间的耦合性,将对象交给Spring容器,简化了开发,增强了可维护性。
- IOC的一些不足:IOC的核心是反射,反射创建对象一定程度上,还是对效率有一定的影响,但目前影响已经不大,大概1-2倍的差距。
本文暂不涉及Spring IOC实现的源码分析,仅解释了Spring IOC,和怎样去使用Spring IOC,后期会对Spring的IOC相关源码进行分析。
参考书籍:
- 《Spring实战》第四版
- 《SSM项目实战》
感谢耐心的你看到了这,再次感谢!
ATFWUS --Writing By 2020–04-20