任务二:Spring IoC高级应用与源码剖析

一、Ioc基础特性

1.1 BeanFactory和ApplicationContext的区别

BeanFactory是Spring Ioc容器的顶级接口,它定义了一些基础的功能与规范。ApplicationContexts是BeanFactory的子接口,它包含了BeanFactory全部的功能,并且还提供了一些额外的功能,比如国际化支持和资源访问(加载xml、java配置)等。
在这里插入图片描述

1.2 启动Ioc容器的方式

  • Java模式下
  • ClassPathXmlApplicationContext : 从classpath下加载配置文件
  • FileSystemXmlApplicationContext : 从文件系统下加载配置文件(全路径名)
  • AnnotationConfigApplicationContext : 纯注解模式下启动
  • Web模式下
    通过在web.xml中配置监听器ContextLoaderListener来启动,该类在spring-web模块下
    • xml方式
<?xml version="1.0" encoding="UTF-8"?>
<web-app
        version="2.4"
        xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    
    <!-- 配置全局参数,配置xml所在的位置-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- 配置启动监听-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>
  • 配置类方式
<?xml version="1.0" encoding="UTF-8"?>
<web-app
        version="2.4"
        xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- 全部配置参数,配置要使用注解方式启动Ico-->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    <!-- 配置全局参数,配置类全路径-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.lagou.config.SpringConfig</param-value>
    </context-param>
    <!-- 配置启动监听-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

1.3 纯xml方式

  • 文件头
    xmlns:xx : xml nameSpace 命名空间,xx为前缀,因为beans 是最基本的标签,所以没有指定前缀。
    xsd :指定命名空间应该包含哪些标签,允许使用哪些标签。
<?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">
</beans>
  • Bean实例化的方式
  1. 无参构造方法
    spring通过反射的方法去实例化bean
    <bean id="accountDao" class="com.lagou.dao.Impl.AccountDaoImpl"/>

我们可以自己创建某个类,并把它放入Ioc容器进行管理,根据获取这个类的方式是否是static的分为一下两种

  1. 使用静态方法创建
    <bean id="accountDao" class="com.lagou.factory.AccountDaoFactory" factory-method="getAccountDaoStatic"/>
  1. 使用实例化方法构建
	<bean id="factory"  class="com.lagou.factory.AccountDaoFactory">
	<bean id="accountDao" factory-bean="factory" factory-method="getAccountDao"/>
  • Bean的作用域
    spring 默认的作用域为singleton,可以通过scope 来配置生命周期
    <bean id="accountDao" class="com.lagou.dao.Impl.AccountDaoImpl" scop="xxx"/>
scop解释
singleton整个Ioc容器中只有一个改类的实例
prototype每次获取都创建一个新的实例
requestweb应用中,每次请求都创建一个新的实例
sessionweb应用中,每次回话都创建一个新的实例
  • Bean的生命周期
  1. singleton:生命周期与容器生命周期一致
    创建:容器创建时,对象就被创建
    存在:只要容器在,对象一直存在
    销毁:当容器销毁时,对象也一起销毁
  2. prototype:容器只负责创建,不负责销毁
    创建:获取对象时,才创建对象
    存在:对象一直在使用
    销毁:对象不再使用会被jvm回收
  • Bean的属性
属性解释
id类的唯一标识,不可重复
class创建Bean对象的全路径
name给Bean指定名称,多个名称可以用空格分隔,一般不使用
factory-bean⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。
factory-method⽤于指定创建当前bean对象的⼯⼚⽅法,与class配合使用时,该方法必须是static的。与factory-bean配合使用时,方法无需是static的。
scope作用域
init-methodBean初始化方法,在类创建时会去调用,必须是无参的
destory-methodBean销毁前会执行的方法,只有在scop=singleton时生效(其他作用域容器不负责对象的销毁)
  • 依赖注入的方式
  1. 构造器注入
    Bean需要提供有参的构造方法,使用constructor-arg标签,该标签属性如下
属性解释
name指定要赋值的参数名称
index指定要赋值的参数的索引坐标,从0开始
value赋予参数的值,用于基本数据类型和string类型
ref赋予参数的值,用于引用类型,内容为bean的id
  1. set方法注入
    Bean需要提供注入属性的set方法,使用property标签,该标签属性如下
属性解释
name指定要赋值的参数名称
value赋予参数的值,用于基本数据类型和string类型
ref赋予参数的值,用于引用类型,内容为bean的id

对于复杂类型的注入,分为两类,一类是集合,一类是键值对

    <bean id="transferService" class = "com.lagou.service.Impl.TransferServiceImpl">
        <property name="accountDao" ref ="accountDao"/>
        <property name="setParam">
            <set>
                <value>value1</value>
                <value>value2</value>
            </set>
        </property>
        <property name="listParam">
            <list>
                <value>list1</value>
                <value>list2</value>
            </list>
        </property>
        <property name="mapParam">
            <map>
                <entry key="key1" value="val1"/>
                <entry key="key2" value="val2"/>
            </map>
        </property>
        <property name="propertyParam">
            <props>
                <prop key="key1">val1</prop>
                <prop key="key2">val2</prop>
            </props>
        </property>
    </bean>

在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,除了可以value 标签内部直接写值,也可以使⽤ bean标签配置⼀个对象,或者⽤ ref 标签引⽤⼀个已经配合的bean的唯⼀标识。
在Map结构的集合数据注⼊时, map 标签使⽤ entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时 entry 标签中也可以使⽤ ref 标签,但是不能使⽤ bean 标签。⽽ prop 标签中不能使⽤ ref 或者 bean 标签引⽤对象

1.4 xml+注解方式

实际项目开发中,一般都是采用xml+注解的方式进行配置。自己开发的类一般使用注解,对于第三方的类采用xml配置。使用注解不需要引入额外的jar包,在配置启动的时候仍然以加载xml开始。

  • 在xml中开启对注解的扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    
    <!-- 开启注解扫描,并指定要扫描的包-->
    <context:component-scan base-package="com.lagou"/>
  • Ioc中xml与注解的对应
xml属性对应注解
bean@Component(""),添加在类上,可以在括号内指定id,如果不指定默认为首字母小写的类名,为了方便区分层级,还提供了@Controller,@Service,@Repository 三个注解,他们功能和@Componnet完全一致
scope@Scope(“prototype”),添加在类上
init-method@PostContruct,添加在类上
destory-method@PreDestory,添加在类上
  • Di中xml与注解的对应
  1. @Autowired:Spring提供的注解,可以填在属性或set方法上,它是根据类型进行注入,如果一个类型有多个子类时,还需要@Qualifier配合,找到唯一的bean。
  2. @Qualifier :选择注解,可以指定条件来选择bean,与其他标签是与的关系
  3. @Resource:j2ee提供的注入注解,默认是按照名字(id)进行注入,也可以指定类型注入(type=“xxx”)或同时指定(name=“xx”,type=“xx”),在jdk11以后移除了

1.5 纯注解方式

纯注解的方式就是把xml文件中的内容转换成java类配置,并删除xml文件,系统的启动入口变为java配置类

  1. @Configuration : 添加在类上,标识这是一个spring的配置类
  2. @ComponentScan : 添加在类上,指定要扫描的类
  3. @PropertySource : 添加在类上,指定要加载的配置文件
  4. @Import :添加在类上,指定要引入的其他配置文件
  5. @Bean : 添加在配置类的方法上,标识这个类要添加到容器中
  6. @Value : 添加在配置类的属性上,对属性赋值

二、Ioc高级特性

2.1 lazy-init 延迟加载

默认单例的bean,会在spring容器启动时,进行实例化,可以通过 lazy-init属性来设置,一个类的延迟加载,该属性默认值为false

  • 该属性只对scop=singleton的类有效
  • 为true时,只有在调用getBean方法时,才会实例化这个对象
<bean id="test" class="com.lagou.Test" lazy-init="true"/>
  • 当用在bean上时,只有这个类延迟加载。如果用在beans上时,全部的类都延迟加载,但是bean上的延迟加载设置优先级更高
<beans defalut-lazy-init="true">
	......
</beans>
  • 对应的注解是@Lazy,用在@Bean处或具体类上
	@Bean
	@Lazy
	public Test getTest(){
		return new Test();
	}
  • 延迟加载是为了提高容器的启动速度,避免某些很少使用的类从一开始就占用资源

注意:虽然一个类A设置了延迟加载,但是如果另一个类B引用了A,而B没有延迟加载,那么在实例化B的时候,仍然回去实例化A,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。

2.2 FactoryBean

BeanFactory 是容器的顶级接口,它定义了以一些基础的功能,负责创建和管理bean。
FactoryBean 是自定义bean创建过程的接口,可以生产某一个类型的Bean,通过它我们可以自定义bean的创建过程。他多用于spring的一些组件或者整合第三发框架时。

public interface FactoryBean<T> {
	@Nullable
	T getObject() throws Exception;
	@Nullable
	Class<?> getObjectType();
	default boolean isSingleton() {
		return true;
	}
}
  • 对于factoryBean,通过id拿到的bean是它所产生的具体对象,而不是factoryBean本身。只有通过&+id 拿到的才是factoryBean本身

2.3 后置处理器

spring提供了两种后置处理器,BeanPostProcessor和BeanFactoryPostProcessor。
工厂初始化 -> bean 初始化

  • BeanPostProcessor
    在bean对象初始化(并不是bean整个生命周期完成)后进行一些操作。他可以针对某个bean进行处理。
    在这里插入图片描述
    该接口具体的实现位置如下图
    在这里插入图片描述
    注意:
  • init-method对应的注解postConstruct所执行位置在 6和7之间,并不是init-method的8。
  • destory-method对应的注解PreDestorys所执行的位置在9之后,虚线框之前。
  • BeanFactoryPostProcessor
    BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,在工厂初始化后,上图第一步之前执行
public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

参数ConfigurableListableBeanFactory类中可以获取到所有的BeanDefinition,这样我们就可以对所有的bean定义进行处理,该接口一个典型的实现类是PropertyPlaceholderConfigurer,
他在解析完所有BeanDefinition后,把${xxx}的具体值进行替换。

三、源码剖析

#Gradle 的根目录
export GRADLE_HOME=/Users/soft/gradle-6.8.2
#Gradle 仓库地址
export GRADLE_USER_HOME=/Users/xxx/repository
export PATH=$PATH:$GRADLE_HOME/bin:$GRADLE_USER_HOME
  • 导入项目并按照(core-oxm-context-beans-aspect-aop)的顺序进行构建。
    工程->task->other->compileTestJava

1.Spring IoC容器初始化主体流程

1.1 Spring IoC的容器体系

Ioc容器是spring的核心模块,是抽象了对象管理、依赖关系管理的框架解决方案。Spring提供了很多的容器,其中BeanFactory是顶层容器,定义了所有Ico容器必须遵从的一套原则。具体的容器实现可以增加额外的功能。这个样细粒度的拆分,可以在需要哪些层次就继承哪些层次,避免想要实现一个功能而引入了一大堆不需要的功能。
BeanFactory提供的方法如下

BeanFactory继承体系
在这里插入图片描述

1.2 Bean生命周期关键点

  • 创建MyTestBean、MyBeanPostProcessor、MyBeanFactoryProcessor三个类,并在构造方法、实现方法上打上断点
package com.lagou.pojo;

import org.springframework.beans.factory.InitializingBean;

public class MyTestBean implements InitializingBean {
	public MyTestBean() {
		System.out.println("MyTestBean构造器执行");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("MyTestBean类初始化方法执行");
	}
}

package com.lagou.process;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	public MyBeanFactoryPostProcessor() {
		System.out.println("MyBeanFactoryPostProcessor构造器执行");
	}
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("工厂类后置处理器执行");
	}
}

package com.lagou.process;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
	public MyBeanPostProcessor() {
		System.out.println("MyBeanPostProcessor构造器执行");
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("Bean后置拦截器before方法执行");
		return null;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("Bean后置拦截器after方法执行");
		return null;
	}
}
  • 在xml中配置三个类
	<bean id="myTest" class="com.lagou.pojo.MyTestBean"/>
	<bean id="myBeanFactoryPostProcessor" class="com.lagou.process.MyBeanFactoryPostProcessor"/>
	<bean id="myBeanPostProcessor" class="com.lagou.process.MyBeanPostProcessor"/>
  • 通过debug,观察调用栈,得出如下结论
关键点名称触发时机
Bean 构造器、初始化方法、before、afterrefresh # finishBeanFactoryInitialization
BeanPostProcessor 构造器refresh # registerBeanPostProcessors
BeanFactoryPostProcessor 构造器、post方法refresh # invokeBeanFactoryPostProcessors

1.3 Ioc容器初始化主体流程

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		//加锁处理,防止处理时进行destory操作
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			//容器启动前的准备工作
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			//创建beanFactory,并解析配置文件,加载beanDefinition 注册到 BeanDefinitionRegister(map)中
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			//对BeanFactory进行一些设置
			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接口的类,并执行后置方法
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 注册bean后置处理器,只是注册,并没有执行after、before,因为此时bean还没初始化
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 实例化国际化功能的组件
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				//注册监听器,实现了ApplicationListener接口的类
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 初始化非懒加载的单例bean,
				// 填充属性、
				// 执行初始化方法(如init-method、afterPropertiesSet)
				// 执行bean后置处理器方法(after、before)
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 完成刷新,并发布事件
				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();
			}
		}
	}

2.BeanFactory的获取流程

2.1 获取beanFactory子流程

在这里插入图片描述

2.2 BeanDefinition加载解析及注册流程

3.Bean对象创建流程

4. lazy-init延迟加载原理

  • 分析
    普通bean的初始化,是在容器启动初期阶段执行的,而被lazy-init=true修饰的bean则是在从容器中第一次进行getBean时触发的。spring启动的时候会把所有的bean信息加载成beanDefinition存储在map中,然后对每个beanDefinition进行处理,如果发现是懒加载则跳过,否则进行初始化和依赖注入。
	@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.
		//所有的beanid 存入一个集合
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			//非抽象的、单例的、非懒加载的bean才进行创建
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						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 {
					getBean(beanName);
				}
			}
		}
	}
  • 总结
    • 对于被lazy-init=true修饰的bean Spring容器初始化的时候不会进行初始化,当第一次进行getBean时才进行初始化
    • 对于非懒加载的bean,getBean是首先会去从缓存中获取,因为容器初始化时已经实例化并存储起来了

5. 循环依赖问题

5.1 什么是循环依赖问题

循环依赖其实就是循环引用,也就是两个或两个以上的bean相互引用,最终形成一个闭环。
在这里插入图片描述
注意:循环引用是指对象的相互依赖,并不是指方法的循环调用
循环引用的场景有两个:

  • 构造器的循环依赖
  • 属性的循环依赖
    其中,构造器循环依赖spring无法处理,只能抛出BeanCurrentlyInCreationException 异常。属性循环依赖,spring采用提前暴露来解决

5.2循环依赖的处理机制

  • bean单例的构造器循环依赖(无法解决)
    spring的循环引用的理论基于java的引用传递,当获得对象引用后,属性的设置可以延后进行,但是构造器的执行在获取引用之前。
  • 原型Bean的循环依赖(无法解决)
    无论是构造器还是属性spring都会直接报错处理
//AbstractBeanFactory.doGetBean()方法
if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
}
  • 单例属性或@Autowrite循环依赖
    spring通过提前暴露一个ObjectFactory对象来实现,在创建完ClassA之后,在调用setClassB之前,就将ClassA实例化的对象通过ObjectFactory提前暴露到spring容器中。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值