文末福利:扫描文末二维码,回复关键字"SpringIoC"获取总结的完整思维导图。
IoC(Inverse of Control 控制反转):接口实现类的选择控制权,从调用类中移除,转交给第三方决定,即由Spring容器借由Bean配置来进行控制。
IoC的概念不太直观,后来有人提出DI的概念用来代替IoC。
DI(Dependency Injection 依赖注入):调用类对某一接口的实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
IoC的类型有:
Spring支持构造函数注入和属性注入。
Java语言允许通过程序化的方式间接对Class进行操作。Class文件由类装载器装载后,在JVM中将形成描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息。为使用程序化的方式操作Class对象开辟了途径。Java反射涉及到的主要类如下:
类装载器:负责寻找类的字节码文件并构造出类在JVM内部表示对象的组件
类装载步骤:
JVM运行时产生3个ClassLoader:根装载器、ExtClassLoader、AppClassLoader
根装载器不是ClassLoader的子类,它是用C++编写,负责装载JRE核心类库:rt.jar、charsets.jar等
ExtClassLoader、AppClassLoader都是ClassLoader的子类。Ext负责装载JRE扩展目录ext中的JAR类包,AppClassLoader负责装载Classpath路径下的类包。
三个装载器存在父子层级关系,默认情况下使用AppClassLoader装载应用程序中的类。JVM装载类时的全盘负责委托机制(/双亲委派机制):
主要方法:
Class文件、类装载器、Class类对象之间的引用关系如图:
Spring通过访问配置文件,或者读取配置类进行容器的初始化、启动、刷新等操作,资源访问作为Spring框架的底层基础实施,具体是怎样实现的呢?提供了哪些扩展的方式?下面具体来看……
Spring设计了Resource接口,为应用提供了更强的底层资源访问能力。通过Spring接口,可以将Spring的配置信息放置到任何地方,比如数据库、LDAP等。
Resource在Spring框架中起着不可或缺的作用,Spring使用Resource装载各种资源,包括配置文件资源、国际化属性文件资源等。具体实现类如下图:
WritableResource:字接口类定义了可写资源相关方法,其实现类有FileSystemResource、PathResource。
ClassPathResource:类路径下的资源,资源以类路径的方式表示。
UrlResource:封装了java.net.URL,能够访问通过URL表示的资源,如文件系统资源、HTTP资源等。
ServletContextResource:为访问Web容器上下文中资源定义的类。以Web应用根路径的方式加载资源。
ByteArrayResource:二进制数组表示的资源。
InputStreamResource:以输入流返回表示。
FileSystemResource:文件系统资源。
PathResource:表示任何通过URL、Path、系统文件路径表示的资源。
有了针对资源的表达方式,加载资源上需要由资源的路径地址以及资源加载器实现,可以将资源文件看作代加工的物料,加载器看作加工处理器,Spring的体系中随处可见这样的设计思路。
资源表达式
不需要显示指定具体Resource实现类,通过地址自动识别,支持的以下地址及对应的实现类:
file:/xxx、http://xxx、ftp://xxx 使用UrlResource装载资源;classpath:/xxx使用ClassPathResource装载;没有前缀则使用ApplicationContext具体实现类对应类型的Resource。
“classpath:” 还有一种"classpath*:"的前缀,表示在多个包名中进行匹配,否则仅匹配第一个出现的包名,对于分模块打包需要加载多个配置文件的情况下适用。
Ant风格支持的匹配符:
ResourceLoader接口仅支持带资源类型前缀的表达式,ResourcePatternResolver扩展ResourceLoader接口,支持Ant风格资源路径表达式。PathMatchingResourcePatternResolver是Spring提供的标准实现。
我们平常所说的Spring容器具体在Spring框架中,指代的是BeanFactory、ApplicationContext以及在Web环境中使用的WebApplicationContext三个容器级别的接口,在启动Spring程序时,也是通过其中某一个接口进行容器的启动工作。那这三个接口具体是什么含义呢,他们之间又有什么相同点和区别呢,他们具体又有哪些实现类供我们实际使用呢?
BeanFactory可看作是Spring内部的基础设施,是对容器的抽象,定义了Bean工厂的基本方法,供Spring内部使用;ApplicationContext在BeanFactory的基础上,提供了更丰富的功能支持,比如国际化支持和框架事件体系等。
对于Spring的使用者,我们一般使用ApplicationContext,而非底层的BeanFactory。为了更好的理解Spring框架,我们还是有必要理解BeanFactory的设计思路的,BeanFactory中定义个主要方法为getBean(beanName),通过bean名称获得bean,通过其他接口扩展BeanFactory的功能,具体如下:
BeanFactory常用实现类为DefaultListableBeanFactory,配合使用XmlBeanDefinitionReader读取配置信息,启动容器:
public class BeanFactoryTest {
public static void main(String[] args) {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resource = resolver.getResource("classpath:com/smart/beanfactory/beans.xml");
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
reader.loadBeanDefinitions(resource);
Car car = bf.getBean("car", Car.class);
car.introduce();
}
}
ApplicationContext扩展自BeanFactory的子接口,同时继承了容器事件、国际化消息访问相关的接口,功能更加丰富,具体继承体系如下图:
ApplicationContext主要的实现类有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext、GenericGroovyApplicationContext。
AnnotationConfigApplicationContext为基于类注解的配置方式提供专门的实现类;GenericGroovyApplicationContext是为Groovy DSL配置方式提供的专门的实现类
专门为Web应用准备,除了prototype、singleton,另外为Bean新增了三个作用域:request、session、global session。类继承体系为:
Spring的Web应用上下文与Web容器可以实现互相访问:
WebApplicationContext的初始化方式区别于BeanFactory和ApplicationContext,需要配置在Web容器启动的基础上进行启动,具体是配置ContextLoaderServlet或ContextLoaderListener完成容器的初始化工作。
在了解了Spring容器核心接口的情况下,继续讨论被Spring容器管理的Bean,具体会经过哪些阶段完成初始化,以及销毁的过程又是怎样?
Spring初始化Bean经过了若干步骤,具体流程如下图所示:
① 若容器注册了InstantiationAwareBeanPostProcessor接口,则调用其postProcessBeforeInstantiation方法。
② 根据配置调用类的构造函数或工厂方法实例化Bean。
③ 与步骤①对应,调用其xxxAfterInstantiation方法。
④ 与步骤①对应,设置属性值前,调用其xxxPropertyValues方法。
⑤ 调用Bean的Setter方法,设置Bean属性值。
⑥ 若Bean实现了BeanNameAware接口,调用其setBeanName方法,设置BeanName到Bean中。
⑦ 与步骤⑥类似,BeanFactoryAware接口,设置BeanFactory。
⑧ 若BeanFactory装配了BeanPostProcessor实现类,则调用postProcessBeforeInitialization方法。
⑨ 若Bean实现了InitializingBean接口,则调用其afterPropertiesSet方法。
⑩ 执行节点指定的init-method或注解了@PostConstruct的方法。
⑪ 与步骤⑧对应,执行其xxxAfterxxx方法。
⑫ 将bean返回给调用者,对于单实例bean进行缓存,prototype作用域的Bean容器不再管理其生命周期。
⑬ 容器关闭时,对于实现了DisposableBean接口的单实例Bean,调用其destroy方法。
⑭ 调用单实例节点指定的destroy-method或注解了@PreDestroy的方法。
图中带☆标记的方法称为后处理器,附属于容器级别的Bean,影响是全局性的,同时也可以注册多个Bean,分步骤处理,需实现Ordered接口
按流程划分
可以看到Bean的声明周期过程阶段很多,不容易理解,可将整个过程划分为几类阶段:
一般,为了实现与Spring框架的解耦,我们不需要关注Bean级生命周期接口及相关方法,使用init-method和destroy-method指定初始化、销毁前的清理动作能达到和实现接口同样的效果。但BeanPostProcesor不同,它以插件的形式为容器提供了对Bean进行后续加工处理的切入点,同时不需要Bean实现接口,在此基础上,能够统一扩展Spring的功能。
ApplicationContext管理的Bean生命周期与BeanFactory类似,不同之处在于增加了装配容器上下文的ApplicationContextAware接口执行,以及在启动容器时执行的一些后工厂处理器方法,通过实现BeanFactoryPostProcessor接口实现,Spring框架提供的后工厂处理器有CustomEditorConfigurer、PropertyPlaceholderConfigurer等。
xmls对文档所引用的命名空间进行声明
命名:id需要满足XML命名规范,name不限制特殊字符;id和name都可以指定多个名字;id在配置中唯一,name可以有相同的,getBean返回后面声明的Bean;尽量使用id。
注入方式选择?
构造方法
属性注入
简化配置方式
@Configuration
@Bean
在讨论Bean生命周期的时候,涉及到了Spring容器的很多接口及组件,这些是何时实例化的?Spring容器启动时到底经过了哪些步骤?Bean的初始化过程和容器启动过程是怎样衔接的?这些问题从AbstractApplicationContext的refresh方法中窥探一二。
上图整理了Spring容器启动时经历的各个阶段,从配置文件一个个节点到容器中完备的Bean实例,可以分为以下几个大的阶段:
《精通Spring4.x》书中对于BeanDefinition、属性编辑器、工厂后处理器、InstantiationStrategy、BeanWapper等组件有详细的介绍,在图中进行了简短的梳理,有兴趣的可以找书来读一读。
本文根据《精通Spring4.x 企业应用开发实战》,总结了SpringIoC的方方面面,从IoC的基本概念出发,介绍SpringIoC的底层Java基础技术,即反射,介绍了配置Bean的方式以及Spring容器的工作机制及Bean的生命周期阶段,希望对读者了解Spring容器、Bean相关配置、内部工作机制有所帮助。