一. Spring IOC基础知识
Spring核心之IOC--快速入门_舞鹤白沙编码日志-CSDN博客
Spring核心之IOC--相关API_舞鹤白沙编码日志-CSDN博客
Spring核心之IOC--配置文件开发_舞鹤白沙编码日志-CSDN博客
Spring核心之IOC--注解开发_舞鹤白沙编码日志-CSDN博客
(一)BeanFactory与ApplicationContext区别
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐
BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等。
(二)启动IOC容器的方式
1. Java环境下启动IoC容器
(1)xml方式(两种,推荐第一种)
ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐)
FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件(不推荐)
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 不推荐使用
//ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件系统的绝对路径");
// 第一次getBean该对象
Object accountPojo = applicationContext.getBean("accountPojo");
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
Spring之applicationContext.xml代码示例_舞鹤白沙编码日志-CSDN博客
(2)纯注解方式
AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);
Spring之配置类代码示例_舞鹤白沙编码日志-CSDN博客
2. Web环境下启动IoC容器
(1)从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring ioc容器的配置⽂件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>
(2)从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli
cationContext</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>
(三)纯XML模式、半XML半注解模式及纯注解模式代码示例
这里将用银行转账案例来演示纯XML模式、半XML半注解模式及纯注解模式
1. 纯XML模式(不常用)
银行转账案例-Spring纯XML模式代码示例_舞鹤白沙编码日志-CSDN博客
2. 半XML半注解模式
外部引用的包采用XML方式,自定义类采用注解方式
银行转账案例-Spring半XML半注解模式代码示例_舞鹤白沙编码日志-CSDN博客
3. 纯注解模式
银行转账案例-Spring纯注解模式代码示例_舞鹤白沙编码日志-CSDN博客
二. Spring IOC高级特性
(一)lazy-Init 延迟加载
Spring高级特性之lazy-Init 延迟加载_舞鹤白沙编码日志-CSDN博客
(二)FactoryBean 和 BeanFactory
FactoryBean 和 BeanFactory_舞鹤白沙编码日志-CSDN博客
(三)后置处理器
Spring高级特效之后置处理器_舞鹤白沙编码日志-CSDN博客
三. Spring IOC源码深度剖析
(一)Spring IOC容器体系介绍
IoC容器是Spring的核⼼模块,是抽象了对象管理、依赖关系管理的框架解决⽅案。Spring 提供了很多的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IoC 容器 必须遵从的⼀套原则,具体的容器实现可以增加额外的功能,⽐如我们常⽤到的ApplicationContext,其下更具体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等⼀系列的内容,AnnotationConfigApplicationContext 则是包含了注解解析等⼀系列的内容。Spring IoC 容器继承体系⾮常聪明,需要使⽤哪个层次⽤哪个层次即可,不必使⽤功能⼤⽽全的。
BeanFactory 顶级接⼝⽅法栈如下:
BeanFactory 容器继承体系:
通过其接⼝设计,我们可以看到我们⼀贯使⽤的 ApplicationContext 除了继承BeanFactory的⼦接⼝,还继承了ResourceLoader、MessageSource等接⼝,因此其提供的功能也就更丰富了。
下⾯我们以 ClasspathXmlApplicationContext 为例,深⼊源码分析,以探明 IoC 容器的初始化流程。
Spring之IoC 容器源码分析_舞鹤白沙编码日志-CSDN博客
(二)Spring IoC容器初始化主流程
由上分析可知,Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() ⽅法中,我们查看 refresh ⽅法来俯瞰容器创建的主体流程,主体流程下的具体⼦流程我们后⾯再来讨论。
/**
* Return the list of statically specified ApplicationListeners.
*/
public Collection<ApplicationListener<?>> getApplicationListeners() {
return this.applicationListeners;
}
@Override
public void refresh() throws BeansException, IllegalStateException {
// 对象锁加锁
synchronized (this.startupShutdownMonitor) {
/*
Prepare this context for refreshing.
第一步:刷新前的预处理
表示在真正做refresh操作之前需要准备做的事情:
设置Spring容器的启动时间,
开启活跃状态,撤销关闭状态
验证环境信息里一些必须存在的属性等
*/
prepareRefresh();
/*
Tell the subclass to refresh the internal bean factory.
第二步:获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/*
Prepare the bean factory for use in this context.
第三步:BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加载器等)
*/
prepareBeanFactory(beanFactory);
try {
/*
Allows post-processing of the bean factory in context subclasses.
第四步: BeanFactory准备工作完成后进行的后置处理工作
*/
postProcessBeanFactory(beanFactory);
/*
Invoke factory processors registered as beans in the context.
第五步:实例化实现了BeanFactoryPostProcessor接口的Bean,并调用接口方法
*/
invokeBeanFactoryPostProcessors(beanFactory);
/*
Register bean processors that intercept bean creation.
第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
*/
registerBeanPostProcessors(beanFactory);
/*
Initialize message source for this context.
第七步: 初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
*/
initMessageSource();
/*
Initialize event multicaster for this context.
第八步: 初始化事件派发器
*/
initApplicationEventMulticaster();
/*
Initialize other special beans in specific context subclasses.
第九步: 子类重写这个方法,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器
*/
onRefresh();
/*
Check for listener beans and register them.
第十步:注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean
*/
registerListeners();
/*
Instantiate all remaining (non-lazy-init) singletons.
第十一步: 初始化所有剩下的非懒加载的单例bean
初始化创建非懒加载方式的单例Bean实例(未设置属性)
填充属性
初始化方法调用(比如调用afterPropertiesSet方法、init-method方法)
调用BeanPostProcessor(后置处理器)对实例bean进行后置处理
*/
finishBeanFactoryInitialization(beanFactory);
/*
Last step: publish corresponding event.
第十二步: 完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)
*/
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
(三)Spring IoC容器初始化主流程中部分子流程剖析
接上部分代码中refresh()方法的执行主流程,下面就其中部分重要的子流程进行深入剖析:
1. 第二步:获取BeanFactory;默认实现是DefaultListableBeanFactory,加载BeanDefition 并注册到 BeanDefitionRegistry。
Spring IOC容器初始化之获取BeanFactory、BeanDefinition加载解析及注册子流程剖析_舞鹤白沙编码日志-CSDN博客
2. 第十一步: 初始化所有剩下的非懒加载的单例bean,初始化创建非懒加载方式的单例Bean实例(未设置属性)填充属性,初始化方法调用(比如调用afterPropertiesSet方法、init-method方法)
调用BeanPostProcessor(后置处理器)对实例bean进行后置处理
(四) lazy-init 延迟加载机制原理
普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥第⼀次进⾏context.getBean() 时进⾏触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进⾏初始化并依赖注⼊。
以下是: DefaultListableBeanFactory类的preInstantiateSingletons方法
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
// 所有bean的名字
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
// 触发所有非延迟加载单例bean的初始化,主要步骤为getBean
for (String beanName : beanNames) {
// 合并父BeanDefinition对象
// map.get(beanName)
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
// 如果是FactoryBean则加&
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
// 实例化当前bean
getBean(beanName);
}
}
}
对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次
进⾏getBean时候才进⾏初始化并依赖注⼊。
对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经
初始化完成并缓存了起来。
(五)循环依赖问题
Spring IoC循环依赖问题_舞鹤白沙编码日志-CSDN博客
四. 扩展知识点-源码的阅读
(一)源码阅读
好处:提⾼培养代码架构思维、深⼊理解框架
原则:
定焦原则:抓主线
宏观原则:站在上帝视⻆,关注源码结构和业务流程(淡化具体某⾏代码的编写细节)
读源码的⽅法和技巧:
断点(观察调⽤栈)
反调(Find Usages)在idea中读到某个方法时,鼠标右键选FindUsages去找哪里调用了该方法
经验(spring框架中doXXX,做具体处理的地⽅)
(二)Spring源码构建
1. 下载源码(github)
spring-5.1.x_ch(含文档): 8888
2. 安装gradle 5.6.3(类似于maven) Idea 2019.1 Jdk 11.0.5
3. 导⼊(耗费⼀定时间)
4. 编译⼯程
顺序:core-oxm-context-beans-aspects-aop
⼯程—>tasks—>compileTestJava