马士兵

源码方法论

  1. 不要忽略源码中的注释
  2. 先梳理脉络,再深入细节
  3. 大胆猜测、小心求证
  4. 见名知意
  5. hold on
  6. 对源码有兴趣的都是变态
  7. 为了钱!

Spring

IoC

Spring容器帮助管理对象,不需要程序员去new。

<!-- applicationContext.xml -->
<bean id=? class=?>
<property name=? value=?/>
<property name=? ref=?/>
<bean/> <bean id=? class=?>
<constructor-arg name=? value=?/>
<constructor-arg name=? ref=?/>
<bean/>
  • 加载配置文件
  • 解析属性值
  • 创建实例对象
  • 使用
  • 销毁

Spring容器中使用 concurrentHashMap数据结构存放 bean。k-v结构存储。

Spring容器设计

常规创建对象的方式:

  1. new
  2. 反射
  3. 设计模式

Spring中 bean的作用域默认是单例的,其他还有 request的,session的。

// 常规反射创建对象
Class clazz = Class.forName();
Class clazz = 类名.class;
Class clazz = 对象名.getClass();
Constructor con = clazz.getDeclareConstructor();
Object obj = con.newInstance();

1)针对不同的配置方式 xml, 注释, json等有一个抽象定义规范接口 BeanDefinitonReader 读取Spring中 BeanDefinition接口中bean的定义信息。

2)

ApplicationContext ac = new ClassPathXmlApplicationContext();
Object o = ac.getBean("xxx");

实例化:在堆中开辟一块空间,属性都是默认值

初始化:给属性赋值、填充属性、执行初始化方法

<bean id="" class="" init-mmethod="" destroy-method="" />

完整对象

DefaultListableBeanFactory

从 BeanDefinition到 BeanFactory有多个 BeanFactoryPostProcessor实现过程增强(修改 bean的定义信息)。如:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}" />
</bean>

其中,在 <property/>标签中,将 value值从 db.properties中提取出来并赋值给username的过程便是一个过程增强的过程。具体调用的是 PlaceholderConfigurerSupport,它是 BeanFactoryPostProcessor的子类。

可以自定义类实现 BeanFactoryPostProcessor接口,获取 BeanDefinition,并对其进行修改。

应用场景:针对在 xml配置文件中定义了诸多的类并且发现部分同名属性都定义错了,就可以在容器读取配置文件后,通过 BeanFactoryPostProcessor进行截断修改。

针对注解开发的 @ComponentScan同样也需要有相应的增强器,ConfigurationClassPostProcessor,用于解析被注解的类。

bean的生命周期

1)实例化

2)填充属性(populateBean)

3)执行诸多 aware接口需要实现对方法

aware接口存在的意义是:方便通过 spring中的 bean对象来获取对应容器中的相关属性。

4)BeanPostProcessor(before, init-method, after)

CglibAopProxy

JdkDynamicAopProxy

5)完整的对象

6)销毁流程


观察者模式

在 Spring生命周期的不同阶段做不同的处理工作。

  1. 监听器
  2. 监听事件
  3. 多播器

Debug

1)ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

2)Refresh()

3)BeanFactoryPostProcessor

4)BeanPostProcessor

5)多播器

6)监听器

7)实例化非懒加载的对象

doGetBean();

createBean();

doCreateBean();

createBeanInstance();

instabtiateBean();

getInstantiationStrategy().instantiate();

getDeclaredConstruction();

BeanUtils.instantiateClass();

newInstance();

populateBean();

invokeAwareMethods();

ignoreDependencyInterface(EnvironmentAware.class);

循环依赖

A类中定义了 b属性

B类中定义了 a属性

A调用B,B调用A

三级缓存

DefaultSingletonBeanRegistry

set注入:可以解决

构造方法注入:不能解决

原因:实例化和初始化分开处理(提前暴露对象)

实例化流程

1)先实例化 A对象,只是完成了堆空间开辟,并没有设置属性值,只是生成了半成品

2)初始化 A对象,此时要给 b属性赋值,而 B对象是一个完全独立的对象,所以此时要去 Spring容器中查找 B对象

如果此时已经有了 B对象,就直接赋值

如果此时没有 B对象,就先完成 B对象的实例化

3)实例化 B对象 。。。

4)初始化 B对象,给 B对象中的 a属性赋值 。。。

如果有了 A对象,就赋值

如果没有,就实例化 A对象

解决策略

  • 使用半成品
  • 放到容器内的一个 map中取用

ObjectFactory是一个函数式接口,仅有一个方法,可以传入 lambda表达式或者匿名内部类,可以通过调用 getObject()方法来执行具体的逻辑

1)创建 a:

​ getBean -> doGetBean -> getSingleton -> createBean -> doCreateBean

​ getObject()实际上调用的是 createBean();

​ instanceWrapper = nreateBeanInstance(beanName, mbd, args);

​ 得到对象 A@1755

2)填充 b:

​ getEarlyBeanReference(); 判断一级缓存内是否已经包含目标实例

​ 存入三级缓存:k-a, v-() ->getEarlyBeanReference(beanName, mbd, bean);

​ populateBean()

​ applyPropertyValus();开始填充属性值,填充 b

​ getName(); b

​ getValue(); RuntimeBeanReference@1983

​ resolveValueIfNecessary();

​ 判断 value类型是否为 RuntimeBeanReference -> resolveReference()

3)创建 b:B@2152

4)填充 b:存入三级缓存:k-b, v-() ->getEarlyBeanReference(beanName, mbd, bean);

populateBean(),给 b对象内的 a属性赋值 RuntimeBeanReference@2375

5)给 b中的 a赋值:在三级缓存中获取,获取 a所对应的 lambda表达式

此时 getObject() -> getEarlyBeanReference() -> 获取 A@1755

存入 二级缓存,删除 三级缓存中的 a对象,此时 a仍旧是半成品

此时 b的实例化和初始化已经完成

6)继续初始化 a:在一级缓存中存入k-b, v-B@2152,清除二级缓存和三级缓存中的 b

resolveValueIfNecessory()

initializeBean(); 之后 a就成为了完全体

在一级缓存中存入 k-a, v-A@1755,清除二级缓存和三级缓存中的 a

7)之后再需要获取时,直接在一级缓存中获取即可

查找顺序:

  1. 一级缓存:存完全体
  2. 二级缓存:存半成品
  3. 三级缓存:存 lambda,来完成代理对象的覆盖过程

总结

三级缓存解决循环依赖问题的关键是什么?

为什么通过提前暴露对象能解决?

因为对象的 实例化 和 初始化 分离

在中间过程中给其他对象赋值的时候,并不是一个完整的对象,而是把半成品对象赋值给了其他对象。

如果只使用一级缓存能不能解决问题?

不能!在整个调用过程中,一、二、三级缓存中存放的是半成品和成品对象,如果只有一级缓存,则成品和半成品都会放到一级缓存中。则有可能获取到半成品对象,而它是无法正常使用的。因此需要把半成品和成品对象的存放空间分割开来。

只使用二级缓存行不行?为什么需要三级缓存?

三级缓存相比二级缓存,多了个 getEarlyBeanReference();

如果,我们能保证所有的 bean对象都不需要此方法,那么只用二级缓存也可以。

getEarlyBeanReference():获取早期暴露的对象引用,解决循环依赖的关键!

AbstractAutoProxy();

createProxy();

使用三级缓存的本质在于解决AOP代理的问题!!

在创建代理对象时,如果某个 bean需要代理对象,那么会不会创建普通的 bean对象?

肯定会!

当调用 getEarlyBeanReference()时,会先把 bean赋值给 exposedBean,如果在之后的一系列判断中为 false,则直接返回该对象。否则,进行处理再返回。此时的 exposedObject

为什么使用了三级缓存就可以解决代理的问题?

当一个对象需要被代理的时候,在整个创建流程中包含两个对象(代理前的普通对象 和 代理后的牛逼对象)

而 bean默认是单例的,那么在整个生命周期的处理中,一个 beanname也只能对应一个对象。

解决措施:保证我在使用的时候,判断是否需要进行代理的处理。

怎么确定什么时候使用 AOP代理?

无法确定,因此需要使用匿名内部类或者 lambda,在使用的时候直接对普通对象进行覆盖。

保证全局唯一!

Spring bean装配流程和三级缓存的更多相关文章

  1. Spring Bean装配详解(五)

    装配 Bean 的概述 前面已经介绍了 Spring IoC 的理念和设计,这一篇文章将介绍的是如何将自己开发的 Bean 装配到 Spring IoC 容器中. 大部分场景下,我们都会使用 Appl ...

  2. Spring Bean装配(下)——注解

    @Repository,@Service,@Controller这三个注解是基于component定义的注解 component-scan:组件扫描 base-package:扫描这个下的所有类 &l ...

  3. Spring Bean装配(上)

    Bean:在spring的IOC里面,把配置到IOC容器里面的实体或者是对象都称为Bean Bean配置项 Bean的作用域 Bean的生命周期 Bean的自动装配 Resources&Res ...

  4. Spring入门篇——第4章 Spring Bean装配(下)

    第4章 Spring Bean装配(下) 介绍Bean的注解实现,Autowired注解说明,基于java的容器注解说明,以及Spring对JSR支持的说明 4-1 Spring Bean装配之Bea ...

  5. Spring入门篇——第3章 Spring Bean装配(上)

    第3章 Spring Bean装配(上) 介绍Bean的作用域.生命周期.Aware接口.自动装配和Resource等内容. 3-1 Spring Bean装配之Bean的配置项及作用域 从上至下依次 ...

  6. Spring Bean装配笔记

    Spring Bean装配笔记 Spring中的Bean是一个很重要的概念.Spring作为一个Bean容器,它可以管理对象和对象之间的依赖关系,我们不需要自己建立对象,把这部分工作全部转交给容器完成 ...

  7. Spring Bean装配学习

    解释:所谓装配就是把一个类需要的组件给它设置进去,英文就是wire,wiring:注解Autowire也叫自动装配. 目前Spring提供了三种配置方案: 在XML中进行显式的配置 在Java中进行显 ...

  8. spring Bean装配的几种方式简单介绍

    Spring容器负责创建应用程序中的bean同时通过ID来协调这些对象之间的关系.作为开发人员,我们需要告诉Spring要创建哪些bean并且如何将其装配到一起. spring中bean装配有两种方式 ...

  9. Spring Bean装配方式

    Spring装配机制 在xml中进行显示配置 在Java中进行显示配置 隐式bean发现机制和自动装配 自动化装配bean 组件扫描(component scanning),Spring会自动发现应用 ...

随机推荐

  1. 众妙之门玄之又玄,游戏系统中的伪随机(Pseudo-Randomization)和真随机(True-Randomization)算法实现Python3

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_212 有人说,如果一个人相信运气,那么他一定参透了人生.想象一下,如果你在某款moba游戏中,在装备平平,队友天坑的情况下,却刀刀 ...

  2. Java SE 8 新增特性

    Java SE 8 新增特性 作者:Grey 原文地址: Java SE 8 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new_ ...

  3. 深入分析FragmentPagerAdapter和FragmentStatePagerAdapter

    最近遇到比较奇怪的bug,TableLayout+ViewPager实现点击顶部tab切换viewpager视图.但是在Viewpager设置dapter时,最开始设置的是FragmentPagerA ...

  4. java-数组排序之冒泡排序(经典排序)

    public class BubbleSort { public static void main(String[] args) { /*冒泡排序不一定是用时最短的 * 1)声明整型数组arr,包含1 ...

  5. bbs项目解读

    1.注册功能 具体的效果图如下: 注册功能涉及到的逻辑步骤: 1.搭建前端html页面 2.向后端提交用户输入数据 3.对用户输入的数据格式进行校验 4.页面输入数据格式错误,及时向用户进行提示/正确 ...

  6. Apache HttpClient 5 使用详细教程

    点赞再看,动力无限. 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. 超文本传输协议(HTTP)可能是当今 ...

  7. 【SWIFT】从零开始的SWIFT语言学习笔记-2:简单值、数组与字典

    1.0.3 简单值.数组与字典 知识点: 使用var创建变量 var myVariable = 65 myVariable = myVariable + 1 使用let创建常量 let myConst ...

  8. QtCreator使用AStyle配置VS默认编辑代码风格

    基础配置和下载 基础配置和下载,随便找一个教程就行 下面贴出我的配置 --style=allman indent=spaces=4 indent-switches indent-preproc-blo ...

  9. 【读书笔记】C#高级编程 第二章 核心C#

    (一)第一个C#程序 创建一个控制台应用程序,然后输入代码,输入完毕后点击F5 Console.WriteLine();这条语句的意思:把括号内的内容输出到界面上: Console.ReadKey() ...

  10. 编译安装Erlang+RabbitMQ

    楔子 由于国内信创越来越火,客户现场也开始使用国产操作系统替换CentOS之类的开源操作系统,最近做实施的同事找到我,说现场是ARM架构的操作系统编译安装RabbitMQ一直提示无法启动也没有日志文件 ...