Spring 核心组件

实体Bean的创建

基于Class构建

1
<bean class="com.tuling.spring.HelloSpring"></bean>

这是最常规的方法,其原理是在spring底层会基于class属性通过反射进行构建。

构造方法构建

1
2
3
4
<bean class="com.tuling.spring.HelloSpring">
<constructor-arg name="name" type="java.lang.String" value="luban"/>
<constructor-arg index="1" type="java.lang.String" value="sex" />
</bean>

如果需要基于参数进行构建,就采用构造方法构建,其对应属性如下:
name: 构造方法参数变量名称
type: 参数类型
index: 参数索引,从0开始
value: 参数值,spring 会自动转换成参数实际类型值
ref: 引用容器的其它对象

静态工厂方法创建

1
2
3
<bean class="com.tuling.spring.HelloSpring" factory-method="build">
<constructor-arg name="type" type="java.lang.String" value="B"/>
</bean>

如果你正在对一个对象进行A/B测试 ,就可以采用静态工厂方法的方式创建,其于策略创建不同的对像或填充不同的属性。
该模式下必须创建一个静态工厂方法,并且方法返回该实例,spring 会调用该静态方法创建对象。

1
2
3
4
5
6
7
8
9
public static HelloSpring build(String type) {
if (type.equals("A")) {
return new HelloSpring("luban", "man");
} else if (type.equals("B")) {
return new HelloSpring("diaocan", "woman");
} else {
throw new IllegalArgumentException("type must A or B");
}
}

FactoryBean创建

1
2
<!-- 返回的并不是LubanFactoryBean实例,而是被LubanFactoryBean包装的实例 -->
<bean class="com.tuling.spring.LubanFactoryBean" id="lubanFactoryBean"></bean>

指定一个Bean工厂来创建对象,对象构建初始化完全交给该工厂来实现。配置Bean时指定该工厂类的类名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// LubanFactoryBean只是起到一层包装代理作用 
public class LubanFactoryBean implements FactoryBean {

// 真正创建的bean实例
@Override
public Object getObject() throws Exception {
return new HelloSpring();
}
@Override
public Class<?> getObjectType() {
return HelloSpring.class;
}
@Override
public boolean isSingleton() {
return false;
}
}

Bean的基本特性

作用范围

很多时候Bean对象是无状态的 ,而有些又是有状态的, 无状态的对象我们采用单例即可,而有状态则必须是多例的模式,通过scope即可创建

scope=“prototype”
scope=“singleton”

1
2
<bean class="com.tuling.spring.HelloSpring" scope="prototype">
</bean>

如果一个Bean设置成prototype我们可以 通过BeanFactoryAware获取 BeanFactory对象即可每次获取的都是新对像。

生命周期

Bean对象的创建初始化销毁即是Bean的生命周期。通过 init-methoddestroy-method属性可以分别指定期构建方法与初始方法。

1
<bean class="com.tuling.spring.HelloSpring" init-method="init" destroy-method="destroy"></bean>

如果觉得麻烦,可以让Bean去实现 InitializingBean.afterPropertiesSet()DisposableBean.destroy()方法。分别对应初始和销毁方法。

加载机制

指示Bean在何时进行加载。设置lazy-init即可,其值如下:

true: 懒加载,即延迟加载
false: 非懒加载,容器启动时即创建对象
default: 默认,采用default-lazy-init中指定值,如果default-lazy-init 没指定就是false

1
2
3
4
<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 http://www.springframework.org/schema/beans/spring-beans.xsd"
default-lazy-init="true">

什么时候使用懒加载?
懒加载会容器启动的更快,而非懒加载可以容器启动时更快的发现程序当中的错误 ,选择哪一个就看追求的是启动速度,还是希望更早的发现错误,一般我们会选择后者。

Bean的构建过程

spring.xml文件中保存了我们对Bean的描述配置,BeanFactory会读取这些配置然后生成对应的Bean。这是我们对ioc原理的一般理解。但在深入一些我们会有更多的问题:

  1. 配置信息最后是谁JAVA中哪个对象承载的?
  2. 这些承载对象是谁业读取XML文件并装载的?
  3. 这些承载对象又是保存在哪里?

BeanDefinition(Bean定义)

ioc实现中我们在xml中描述的Bean信息最后都将保存至BeanDefinition (定义)对象中,其中xml beanBeanDefinition 是一对一的关系。

由此可见,xml bean中设置的属性最后都会体现在BeanDefinition中。如:

XML-bean BeanDefinition
class beanClassName
scope scope
lazy-init lazyInit
constructor-arg ConstructorArgument
property MutablePropertyValues
factory-method factoryMethodName
destroy-method AbstractBeanDefinition.destroyMethodName
init-method AbstractBeanDefinition.initMethodName
autowire AbstractBeanDefinition.autowireMode
id
name

BeanDefinition属性结构

BeanDefinitionRegistry(Bean注册器)

在上表中我们并没有看到 xml bean 中的 idname属性, 没有体现在定义中,原因是ID其作为当前Bean的存储key注册到了BeanDefinitionRegistry 注册器中。name 作为别名key 注册到了 AliasRegistry 注册中心。其最后都是指向其对应的BeanDefinition

BeanDefinitionReader(Bean定义读取)

至此我们学习了 BeanDefinition 中存储了Xml Bean信息,而BeanDefinitionRegister 基于IDname 保存了Bean的定义。接下要学习的是从xml BeanBeanDefinition然后在注册至BeanDefinitionRegister 整个过程。

上图中可以看出Bean的定义是由BeanDefinitionReader 从xml 中读取配置并构建出 BeanDefinitionReader, 然后在基于别名注册到BeanDefinitionRegister中.

BeanDefinitionReader结构

方法说明:

  • loadBeanDefinitions(Resource resource)

    • 基于资源装载Bean定义并注册至注册器
  • int loadBeanDefinitions(String location)

    • 基于资源路径装载Bean定义并注册至注册器
  • BeanDefinitionRegistry getRegistry()

    • 获取注册器
  • ResourceLoader getResourceLoader()

    • 获取资源装载器

BeanDefinitionReader装载过程

1
2
3
4
5
6
7
8
9
10
11
12
//创建一个简单注册器
BeanDefinitionRegistry register = new SimpleBeanDefinitionRegistry();
//创建bean定义读取器
BeanDefinitionReader reader = new XmlBeanDefinitionReader(register);
// 创建资源读取器
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
// 获取资源
Resource xmlResource = resourceLoader.getResource("spring.xml");
// 装载Bean的定义
reader.loadBeanDefinitions(xmlResource);
// 打印构建的Bean 名称
System.out.println(Arrays.toString(register.getBeanDefinitionNames());

Beanfactory(bean 工厂)

有了Bean的定义就相当于有了产品的配方,接下来就是要把这个配方送到工厂进行生产了。在ioc当中Bean的构建是由 BeanFactory 负责的。其结构如下:

方法说明:

  • getBean(String)

    • 基于ID或name 获取一个Bean
  • ** T getBean(Class requiredType) **

    • 基于Bean的类别获取一个Bean(如果出现多个该类的实例,将会报错。但可以指定 primary=“true” 调整优先级来解决该错误 )
  • Object getBean(String name, Object… args)

    • 基于名称获取一个Bean,并覆盖默认的构造参数
  • boolean isTypeMatch(String name, Class<?> typeToMatch)

    • 指定Bean与指定Class 是否匹配

以上方法中重点要关注getBean,当用户调用getBean的时候就会触发 Bean的创建动作:

基本BeanFactory获取一个Bean, 以下是运行时的栈信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 其反射实例化Bean
java.lang.reflect.Constructor.newInstance(Unknown Source:-1)
BeanUtils.instantiateClass()
//基于实例化策略 实例化Bean
SimpleInstantiationStrategy.instantiate()
AbstractAutowireCapableBeanFactory.instantiateBean()
// 执行Bean的实例化方法
AbstractAutowireCapableBeanFactory.createBeanInstance()
AbstractAutowireCapableBeanFactory.doCreateBean()
// 执行Bean的创建
AbstractAutowireCapableBeanFactory.createBean()
// 缓存中没有,调用指定Bean工厂创建Bean
AbstractBeanFactory$1.getObject()
// 从单例注册中心获取Bean缓存
DefaultSingletonBeanRegistry.getSingleton()
AbstractBeanFactory.doGetBean()
// 获取Bean
AbstractBeanFactory.getBean()
// 调用的客户类
com.tuling.spring.BeanFactoryExample.main()

Bean创建时序图

从调用过程可以总结出以下几点:

  1. 调用 BeanFactory.getBean() 会触发Bean的实例化
  2. DefaultSingletonBeanRegistry 中缓存了单例Bean
  3. Bean的创建与初始化是由AbstractAutowireCapableBeanFactory完成的

BeanFactory 与 ApplicationContext区别

BeanFactory 可以去做IOC当中的大部分事情,为什么还要去定义一个ApplicationContext 呢?

ApplicationContext 结构图

从图中可以看到 ApplicationContext 它由 BeanFactory 接口派生而来,因而提供了BeanFactory 所有的功能。除此之外context包还提供了以下的功能:

  1. MessageSource, 提供国际化的消息访问
  2. 资源访问,如URL和文件
  3. 事件传播,实现了ApplicationEventPublisher接口的bean
  4. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

Spring advice执行顺序

advice执行顺序,就是拦截器链的执行顺序

  • 1、单个切面的场景,around开始 –> before –> aound结束–> after –> AfterReturning

  • 2、对于多个切面的场景,可以通过 @order(序号),来调整执行顺序。

demo参考: Spring多个AOP执行先后顺序

源码分析:
前置通知拦截器: MethodBeforeAdviceInterceptor#invoke
拦截器链:ReflectiveMethodInvocation#proceed
proceed 根据 currentInterceptorIndex 来确定当前应执行哪个拦截器,并在调用拦截器的 invoke 方法时,将自己作为参数传给该方法

参考资料
Spring AOP 源码分析 - 拦截器链的执行过程
Spring AOP 源码分析(生成代理对象)

IOC 容器只存放单例bean吗

结论:IOC 容器只存放单例bean

IOC容器初始化的时候,会将所有bean初始化在 singletonObjects 这个CurrentHashMap 中, bean是单例的。

在获取bean的时候,首先会从singletonObjects去取值,通过debug,发现如果scope是单例,则可以获取到bean,如果scope是多例,则获取不到bean,需要 从一个叫 mergedBeanDefinitionsCurrentHashMap中去获取bean的定义,然后再根据bean的scope去决定如何创建bean,如果scope=prototype,则每次都会创建一个新的实例。

猜想:IOC在初始化时,只会将 scope = singleton(单例)的对象进行实例化,而不会去实例化scope=prototype的对象(多例);

证实:找到singletonObjects.put方法,debug看一下singletonObjects.put的前提条件是什么

源码分析AbstractBeanFactory#doGetBean

单例的场景,直接从 singletonObjects 这个Map中获取bean

多例的场景,发现从 singletonObjects 中拿不到值

接下来,发现它是从一个叫 mergedBeanDefinitionsHashMap中获取了RootBeanDefinition,里面包含了bean的一些基础信息。

最后根据 bean的scope属性,来做处理,如果作用域是单例,则直接从容器中获取,如果作用域是多例,则创建一个实例,当然,作用域还有其它,自己可以一一去验证

Scope为request的bean是否会放入IOC容器

这个应该和IOC没有关系,request对象只是一个参数。
场景分析:SpringMVC在接受一个http请求后,会根据URl去匹配具体的bean(可以这样理解:bean=Map.get(url),这个map是在容器初始化的时候创建的),然后通过反射获取class实例,最终执行method.invoke方法的时候,会将request对象的值封装到args数组中,当然args可能还包含response对象、注解参数、非注解参数的值