IoC全称Inversion of Control即控制反转,它还有一个别名依赖注入。spring利用Ioc容器帮我们自动构建对象及注入依赖对象,减少了对象构建与业务代码的耦合,使得我们能够更加高效愉快的写bug🐞了( ̄▽ ̄)"。接下来我们详细介绍下这个spring Ioc吧。
依赖注入原理
1.三种依赖注入方式
spring中有三种常见的依赖注入方式即:构造方法注入、setter方法注入、接口注入。其中前两种注入方式是我们现在仍然用比较多的,而最后一种由于其需要侵入代码,所以已经很少用了,这里就不介绍了。
(1) 构造方法注入:
这是我们经常能看到的注入方式,即通过对象构造器参数注入依赖对象。这种方式比较直观,同时构造完成后对象即进入就绪状态可以使用了。
(2)setter方法注入
在我们java bean对象中,经常会通过getter和setter方法获取和设置对象的属性,这些方法统称为setter方法,通过为依赖对象添加setter方法,容器就会帮我们实现依赖对象的注入。其中我们比较常用的@Autowired就属于这种方式。不过这种方式的注入,不能保证对象构造完成后就立马进入就绪状态。
在idea里当我们使用@Autowired时经常能看到"Filed injection is not recommended"的提示,告诉我们不推荐使用@Autowired进行注入。其主要原因是@Autowired的注入仅仅适用于Ioc容器,而当我们在程序中直接使用new去构造对象时,对象中的@Autowired依赖是无法自动注入的,就可能存在npe的风险。
2.BeanFactory和ApplicationContext
在讲两个主角之前,我们首先讲下IoC Service Provider。上面介绍了三种依赖的注入方式,但我们需要的是相应的角色或服务来帮我们实际的实现对象的构建和与依赖的注入,而IoC Service Provider就是这个角色。它是一个抽象的概念,可能是一段代码也可能是一组类,它主要负责业务对象的构建管理、业务对象间的依赖绑定。在spring中承担这个角色的主要就是BeanFactory和ApplicationContext,当然他们也承担着容器类的角色,我们这里只着重讲解他们作为IoC Service Provider的功能,他们作为容器的功能会在下面讲解。
这两个类中BeanFactory比较古老了,它默认采用延迟加载策略,即只有当需要访问容器中的受管对象时,才会对受管对象进行初始化及依赖注入操作,所以项目启动比较快。而ApplicationContext是目前项目中比较常用的,它继承了BeanFactory并增加了其他很多高级特性。ApplicationContext所管理的对象,默认在容器启动之后全部进行初始化和绑定操作,所以其启动速度会相对慢些,不过随着spring和java的不断优化和技术升级,这个启动时间一般都可以接受,而其提供的很多特性非常大的方便了我们的开发,所以目前我们大部分的项目都是使用的ApplicationContext。
3.依赖注入过程
上面已经介绍了我们常用的注入方式及帮我实现注入的角色,接下来我们就可以介绍下依赖注入的过程了🤡🤠。
(1)首先对容器来说,要实现依赖注入它最需要的是对象的信息及对象间的依赖关系,spring通常会通过XML或注解等方式记录这些信息。我们在一些比较古来的项目中还能看到这种XML配置文件,如下所示:
<bean id="djNewsProvider" class="..FXNewsProvider"> <property name="newsListener"><ref bean="djNewsListener"/></property><property name="newPersistener"><ref bean="djNewsPersister"/></property></bean>
<bean id="newsListener" class="..FXNewsProvider">
</bean>
<bean id="newPersistener" class="..FXNewsProvider">
</bean>
上面是我们在些老项目中常见的配置方式,在java5支持注解后,我们现在使用的更多是通过注解来代替这些XML配置。例如现在我们通过@Component、@Service等来标注对象信息,用@Autowired、@Resource来标注当前对象的依赖对象信息。ApplicationContext在项目启动时会通过我们配置的scanning-path自动的去寻找这些对象并解析保存其信息及依赖关系。
(2)spring收集到对象信息和依赖关系后会将这些信息封装到BeanDefinition中,每个容器中受管对象都会有一个BeanDefinition,它记录了对象的所有必信息。包括对应的Class类型、是否为抽象类、构造方法参数及依赖关系等。最后这些数据会被注册到BeanDefiniteRegistry中。
(3)当某个请求通过BeanFactory或ApplicationContext获取对象时(getBean()),就会开始对象的实例化了。对象在实例化的过程中会先获取对象的BeanDefinition,然后采用"策略模式",通过反射或者cglib动态代理来初始化对象(注意这里只是初始化,依赖对象还没注入)。
(4)在完成初始化后容器会通过BeanWrapper包裹住实例,然后通过BeanWrapper来实现对象属性值的设置和依赖的对象的注入。BeanWrapper根据实例的依赖对象到BeanFactory或ApplicationContext中获取相关对象,然后set进对象,这样就完成了实例的依赖注入。
我们可以再来看下(3)(4)的伪代码:
// 通过反射构建对象
Object provider = Class.forName("...Provider").newInstance();
Object listener = Class.forName("...Listener").newInstance();BeanWrapper newProvider = new BeanWrapperImpl(provider);
// 注入依赖对象
newProvider.setPropertyValue("listener", listener);
整个(1)(2)(3)(4)就是容器中依赖注入的详细流程了,是不是比想象中的要简单呢ヘ|・∀・|ノ*~●
Spring容器揭秘
IoC容器是Spring框架的重要组成部分。它通过加载配置数据并利用这些信息构造绑定容器内的所有对象,最终组装成一个可用的基于轻量级容器的应用系统。spring容器功能的实现主要分为两个过程:容器启动阶段和Bean实例化阶段。
1.容器启动阶段
上面也大致介绍过了,实际上容器的启动阶段主要就是通过某些特定的工具类来收集配置信息,并将解析后的信息封装为BeanDefinition,最后注册到相应的BeanDefinitionRegistry中,总的来说就是进行对象管理信息的收集。
在这个阶段spring为我们提供了一种叫BeanFactoryPostProcess的扩展机制来插手容器的启动,它可以对容器中的BeanDefinition进行修改,比如修改Bean定义的属性、为Bean增加其他信息等。
其中有一个使用的非常普遍的功能就是系统属性值的替换:我们经常可以在项目文件中看到properties文件,这里面经常会存放数据库密码账号等经常发生变化的配置数据,这些配置数据本来应该是在XML中配置的,但是我们通过$
{jdbc.url}这种方式将实际的数据配置在properties文件中。这个功能就是BeanFactoryPostProcess来帮我们实现,它修改了BeanDefinition的数据,用properties文件中数据替换了BeanDefinition中的$
{}内的数据。是不是很好玩🤣。
2.Bean实例化阶段
容器启动后并不会马上实例化Bean,而是需要等到客户端调用BeanFactory或ApplicationContext自动调用getBean方法获取对象时才会真正的实例化bean。我们可以先看下Bean实例化的过程。
当客户端调用了getBean方法后,就是开始了Bean的整体的实例化流程。
- 首先第一步进行的是对象的初始化,这个阶段就是上面介绍过的通过cglib或者反射来初始化对象,这里只是初始化,依赖对象还没注入。
- 这阶段进行的是依赖对象的注入,依赖对象的注入我们在上面介绍过,主要是通过BeanWrapper来实现的。
- 在之后会检查Aware相关接口并设置相关依赖,比如Bean名称的设置,以及我们比较常用的ApplicationContextAware,这个Aware接口可以将容器类ApplicationContext的引用注入对象中,这样我们就可以方便的通过ApplicationContext.getBean()获取我们需要的对象了。
- 接下来是重要的BeanPostProcessor了,它分为Pre和Post两种,在图中可以看到,这两个processor分别处在Bean实例化(调用构造方法)前和实例化(调用构造方法)后,它类似容器启动阶段的BeanFactoryPostProcessor可以用来动态修改扩展Bean的数据信息。其中一个最重要的应用就是Spring Aop的动态代理,Aop的动态代理就是通过BeanPostProcessor来实现的,它利用反射或cglib通过BeanPostProcessor生成了实例的代理对象并直接返回给了调用方完成了动态代理。
- 接下来在两个BeanPostProcessor间,对象实例化(调用构造方法)前,spring会检查对像是否实现了InitializingBean接口,如果是就会调用afterPropertiesSet()方法进一步调整对象状态,除此之外我们常用的 @Bean(initMethod = “func”)中initMethod方法的执行也是在这里实现的。
- 最后阶段就是在容器关闭Bean生命周期结束时,检查Bean实现的destory-method,其中 @Bean(destroyMethod = “func”)中的destroyMethod方法也是在这里执行的。
好了,至此我们们就把容器的启动及Bean的实例化讲完了,舒服了🍑🍓🍎🍐。下面在简单讲下Bean的生命周期。
3.Bean的生命周期
容器除了会帮我们进行依赖注入外还会帮我们管理对象的生命周期,Bean的生命周期被称为Scope,可以理解为对象的存活范围或存活时间。bean的scope最常见的主要有两种:singleton和prototype。
(1)singleton:
这种类型是我们最常见的。被标记为singleton scope的对象在容器中只存在一个实例,所有对该对象的引用都共享这个实例,它会一直存活到容器退出,几乎和容器的生命周期一样长。
(1)prototype:
被标记为prototype scope的对象,在容器每次收到该对象的请求时,都会生产一个新的对象返回给请求方,并且放回给请求方后,容器就不会再拥有当前对象的引用,请求方需要自己负责该对象的生命周期。这种类型很像我们自己在程序中使用new生成的对象,这中对象在我们不在使用后,会被GC线程在何时的时机回收掉,结束掉他的一生。
除此之外还有些不太常见的类型,原理都是一样的,这里就不在赘述了。终于写完了,嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻,打完收工,吃饭去🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗🤗