Spring进阶(二)IOC高级应用及源码深度剖析

一. 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

Gradle 安装配置详解_舞鹤白沙编码日志-CSDN博客

3. 导⼊(耗费⼀定时间)

Spring源码的导入_舞鹤白沙编码日志-CSDN博客

4. 编译⼯程

顺序:core-oxm-context-beans-aspects-aop

⼯程—>tasks—>compileTestJava

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

enterpc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值