IOC
# IOC
# 1.BeanFactory
# 功能实现
区分两个对象:BeanDefinition和Object,其中BeanDefinition成员对象中保存的是class类,与Object实现解耦分离。
从Bean工厂获取Bean最终用户获取到的一定是Object对象,整个流程如下:
默认整个容器注册了BeanDefinition的class信息,不注册肯定注入失败抛异常。
首先向上查找单例池Map获取Object对象,如果有直接返回。(类似于一层缓存)
如果没有,则向下查找Bean定义池子,获取BeanDefinition对象
调用newInstance方法初始化一个新的Object对象,并放入单例池做缓存。并最后返回给用户。
# 架构设计
AbstractBeanFactory:模板模式来控制实现getBean流程,形成一个上下缓存之间传输数据的桥梁。
AbstractAutowireCapableBeanFactory:createBean方法把BeanFactory对象转化为Object对象,实现上下两层数据类型的转换。
基本上Bean对象从Map中存入与取出都是通过实现接口或者抽象类的方法实现。
编程架构:可以用于多级缓存设计。

# 2.Cglib
# 如何实例化含有构造函数的对象
①Cglib:
- enhancer.setSuperclass()设置要继承代理的父类
- enhancer.setCallbacks设置回调(生成的代理类的方法被调用时,会执行的逻辑)
- enhancer.create(Class[] 构造器参数类型数组,object[] 参数值)创建代理对象。
②jdk代理:
- clazz.getDeclaredConstructor(Class... 构造器参数类型数组)拿到构造器
- 构造器调用newInstance(Object[] 参数值)创建对象
# 如何根据Bean对象的构造函数入参信息传递到getBean实例化操作中
需要根据用户getBean时传入的参数args和Class对象,获取调用代理方式创建对象需要的所有对象。
- 拿到BeanDefinition后,获取Class对象。
- clazz.getDeclaredConstructors()获取代理类的所有构造器对象
- constructor.getParametersType()获取该构造器形参的类型数组
- 通过clazz.getDeclaredField(name)可以拿到字段对应的Field
- 通过field.get(object)可以获取该object对象的name字段值
# 3.bean对象注入Property
# 功能实现
问题:Bean属性为 Bean 对象时进行对象注入。
目前给Bean的成员变量初始化有两种方式:①通过构造函数赋值②通过PropertyValues设置。此处默认全部成员变量都是通过②方式赋值。因为通过构造函数赋值方式是在getBean创建对象期间同时进行的,所以PropertyValues会刷新覆盖掉构造器初始化的变量。
# 架构设计
创建并注入完毕的Object需要保存放在单例池中,因此整个流程在AbstractAutowireCapableBeanFactory中的createBean进行。具体流程如下:
createBeanInstance创建Object对象——>applyPropertyValues给Object对象的成员变量注入对象——>addSingleton把新的Object存入单例池中。
需要维护一个变量列表PropertyValues,变量对象存储字段名+属性值,把变量列表放入BeanDefinition作为其中一个属性,beanObject创建好后可以从BeanDefinition中取出变量列表进行注入。

其中PropertyValue的value设置为Object类型,分为两种:
八大基本类型,直接通过反射注入。
自定义类型,需要创建一个新的对象BeanReference,其中存放自定义类的beanName,可以通过getBean(BeanReference.getBeanName)循环从工厂获取引用对象Object。
for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
String name = propertyValue.getName();
Object value = propertyValue.getValue();
/** 如果已经通过构造函数注入,则该字段不需要重新覆盖*/
Object fieldValue = BeanUtil.getFieldValue(bean, name);
if (fieldValue != null) {
continue;
}
if (value instanceof BeanReference) {
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getBeanName());
}
BeanUtil.setFieldValue(bean,name,value);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.加载XML资源配置
# 解析XML
容器中注入Bean定义信息通过XML文件来实现。通过解析XML配置信息,把每个Bean以及所有类型的成员属性property都封装成BeanDefinition对象,然后注册添加到beanDefinitionMap当中。具体流程如下:
- 获取InputStream
拿到类加载器classLoader——>拿到资源地址path——>classLoader.getResourceAsStream(path)获取输入流。
类加载器作用是当需要使用一个类时,加载该类的".class"文件并创建对应的class对象,将class文件加载到虚拟机的内存。
根据输入流获取文件对象以及文件的根元素Element,也就是最外层的标签<beans>
遍历根元素下的所有子节点Element(bean标签),对于每个<bean>子节点也要遍历其子节点(property标签)读取bean的成员对象
Element.getChildNodes():获取当前节点的子节点集合对象,并通过NodeList.item(index)根据索引下标获取子节点Node对象(Element的父类)。
NodeList.item(i).getNodeName():返回当前标签的标签名称
每个Node对象在XMl解析中,会存在两种,一种是Element对象,也就是需要继续进行解析的对象。二是TEXT对象。
- 每一个Bean对象根据className和属性值集合,最终封装成一个BeanDefinition,并注入到容器中。
# 架构设计
Resource资源类设计:整个解析的过程关键是拿到InputStream输入流,可以直接根据URL路径、本地项目资源文件路径、从File对象获取一共三种方式,因此采用策略模式设计,根据不同方式实现getInputStream()行为。
资源加载器设计:具体采用哪种方式获取输入流,需要根据外部传入的路径判断。因此需要一个方法传入的形参为路径,返回类型是Resource统一资源接口,而方法内部会根据路径最终返回不同资源实现类型。于是定义了一个资源加载器接口。(类似于DDD抽奖系统中config类的Map思想,只不过此处是通过if-else方式不够优雅)
XML解析器设计:解析过程包括调用获取输入流资源服务——>解析——>调用Bean定义信息服务。因此XmlBeanDefinitionReader需要使用两个服务接口,有两种方法:
①XmlBeanDefinitionReader继承服务对象DefaultListableBeanFactory,而DefaultListableBeanFactory又实现资源加载器功能方法。这样设计的问题在于整个继承链延长,同时整个XMl解析器会继承到更多无关的方法。
②通过外部分别传入加载资源服务对象与Bean注册服务对象。这里完全可以直接通过XmlBeanDefinitionReader的构造器传入两个服务对象,之所以没这么做而是创建一个中间的抽象类AbstractBeanDefinitionReader来引入服务接口,因为考虑到后续可能需要拓展。
启发:A的服务依赖于B服务,其中B服务接口又采用策略模式具有多个实现类。那么A接口中可以设计一个返回类型为B接口的方法,在方法实现中根据方法入参来选择返回对应的实现类。

# 5.应用上下文
# 功能需求一:在Bean实例化过程中实现增强(不考虑应用上下文)
BeanFactoryPostProcessor接口(Bean定义增强):在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,提供修改 BeanDefinition 属性的方法。
BeanPostProcessor接口(Bean初始化增强):提供Bean对象在实例化完后执行类的初始化方法之前/之后实现增强的方法。
得力于前面优良的架构设计,整个XML注册Bean(XmlBeanDefinitionReader)以及Bean实例化(DefaultListableBeanFactory)这两个部分具有比较好的解耦性。因此对于Bean定义增强方法调用时,不需要单独在两个服务内部引入接口类型并在内部控制调用,可以直接传入BeanFactory对象进行外部调用即可,通过工厂对象拿到BeanDefinitionMap后进行增强修改。
对于Bean初始化增强,因为它是在Bean实例化后实现增强,需要把BeanPostProcessor接口对象织入到整个Bean实例化服务当中。具体做法在AbstractBeanFactory抽象类中维护了一个BeanPostProcessor列表,同时实现了addBeanPostProcessor方法,并通过子类实现的createBean和initializeBean方法控制增强方法的执行位置和流程。
整体过程如下:

整体架构:

# 功能需求二:ApplicationContext上下文操作类整合
ApplicaitonContext整合XML 加载 注册、实例化以及新增的修改和扩展。用户只需要加载并初始化Bean容器即可完成上面所有操作。
- Spring容器管理增强接口
增强接口方法不能通过外部实现类来调用,这意味着用户实现的两种增强接口需要交给Spring容器进行管理,需要在spring资源文件中添加增强类bean配置。注册完后需要从工厂Map中取出所有的增强类对象并执行增强方法,这里DefaultListableBeanFactory实现了getBeansOfType方法,根据class类型(接口类型)返回工厂中所有该类型的对象。
getBeansOfType:首先遍历BeanDefinitionMap,找到符合要求的Class类型对应的beanName,最后调用getBean获取该Bean对象。注意该方法内部会调用getBean实现Bean的预加载,使增强类先于其它Bean进行实例化并放入单例池缓存。
refresh对所有流程进行整合:
①refreshBeanFactory:创造一个新的DefaultListableBeanFactory工厂对象,并完成XML资源注册。
②invokeBeanFactoryPostProcessors:Bean定义增强类进行实例化,然后遍历调用所有增强方法,对BeanDefinition进行修改。
③registerBeanPostProcessors:Bean初始化增强类进行实例化,然后存入工厂对象的List集合中(类似于二级缓存)。此处还不能执行初始化方法,需要在Bean实例化后再通过List容器拿到增强对象执行。
④preInstantiateSingletons:预先对工厂所有Bean执行一次实例化,并加入缓存。其中增强方法在每个getBean方法中执行。
- 结构设计
①XML加载注册功能是通过AbstractXmlApplicationContext抽象类,其中直接new一个XML解析对象并调用资源加载方法。
②Spring工厂的引入是通过抽象类AbstractRefreshableApplicationContext,其中实现了createBeanFactory方法,方法会直接new一个工厂类对象DefaultListableBeanFactory。所有需要用到工厂类的地方都通过getBeanFactory方法获取。
@Override
protected ConfigurableListableBeanFactory getBeanFactory() {
return beanFactory;
}
2
3
4
getBeanFactory()巧妙的构思设计在于,充分利用函数调用会递归向父类上找,虚函数递归向下子类实现的特性。因为是返回的是接口引用对象ConfigurableListableBeanFactory,因此只能通过该对象调用ConfigurableListableBeanFactory接口以及父类继承的所有声明了的接口方法。工厂类需要提供声明方法,就把使用到的工厂方法声明在接口ConfigurableListableBeanFactory中。
调用链:接口方法——>找到最底层实现类DefaultListableBeanFactory——>当前类方法有没有,如果没有继续向上找父类找到实现了的方法。

③AbstractApplicationContext类继承DefaultResourceLoader,可以向外部提供获取资源路径的接口,读取XML文件。
整体结构设计如下:

改进优化:
1、Refresh方法中两种增强器的提前初始化顺序不能变,因为但凡是调用了getBean都会执行初始化增强方法,因此如果是后初始化BeanFactoryPostProcessor的话,会影响程序执行效率(用户定义非常多个BeanPostProcessor情况下)。先初始化因为此时List为空,因此性能更优。
2、每一个Bean调用getBean会执行所有BeanPostProcessor对象的方法,效率低。按个人理解利用BeanPostProcessor进行更新修改,针对的是具体某个类型的对象,而不是所有Bean。因此可以加一层判断,类型不匹配的Bean不需要执行增强方法。
# 6.Bean初始化和销毁
# Bean初始化
执行时机:Refresh中getBean预加载执行前置增强方法之后进行。
实现Bean初始化的本质就是要解决以什么方式告诉Spring当前这个Bean里面有init方法,从而让Spring能够区分哪些是需要初始化的Bean。而怎么执行这个问题本身很简单。
- 方式一XML配置方法名property
通过在XML配置中给当前bean添加初始化方法的property,因此BeanDefinition中除了要有bean的属性集合,还需要添加方法名(实际上你可以添加Bean内定义的所有方法)。执行时如果判断BeanDefinition中还有初始化方法,那么直接反射调用即可。
- 方式二通过接口引入服务
Bean实现InitializingBean接口afterPropertiesSet方法。通过接口引入服务本质在于利用了创建的对象不仅属于声明所Class类型,同时也属于所实现的接口类型(类似于感知器)。执行时需要判断是否是InitializingBean类型,直接强转成接口引用调用方法即可。
# Bean销毁方法
执行时机:在jvm关闭时的钩子函数添加关闭销毁资源方法,程序执行完毕前进行销毁。
Runtime.getRuntime().addShutdownHook(new Thread(this::XX)):jvm关闭前会起一个新的线程执行用户指定的run方法,执行完内存清理、对象销毁、资源关闭等操作后jvm才关闭。(kill-9关闭进程不走钩子函数)。
- 执行所有销毁方法
如果不做什么处理直接执行销毁方法,那么需要遍历一次单例池,然后判断每个Bean对象是否定义了销毁方法,显然如果Bean对象比较多的情况下执行效率比较一般。这里可以类比加载BeanPostProcessor的做法,给所有声明销毁方法的Bean对象设计一个List容器收集起来作为二级缓存。refresh的预加载过程实际上就是一次遍历一级缓存SingleTonMap的过程,可以在Refresh中收集各种定制化的Bean服务。
- 细节
ConfigurableBeanFactory接口声明了destroySingletons方法,遍历销毁集合执行每个Bean销毁方法,并释放整个List占用的内存空间。destroySingletons方法隔离分层设计,当前类把当前接口方法的实现交给父类来进行。
AbstractAutowireCapableBeanFactory类的createBean方法中,添加registerDisposableBeanIfNecessary方法用于搜集所有的销毁方法。
# 销毁方法适配器
针对销毁方法的多种实现方式(反射调用和接口直接调用),通过适配器暴露给其它模块一个统一的类型对象,对执行方法进行统一处理。
对于同一个Bean对象定义了多种销毁方式的情况,添加过滤条件来控制只执行一次销毁方式(比如同一个对象释放两次会报错)。
# 7.容器感知Aware
功能需求:用户需要获取到框架资源对象比如BeanFactory,ApplicationContext,采用向外部暴露感知器接口的方式实现。这种做法与前面BeanPostProcessor、InitializingBean本质上都是一样的,都是通过对接口类型采用instanceof判断来感知并引入定制化服务。区别在于,容器感知需要传递资源参数并由框架调用服务方法,而前面的使用方式基本只需要框架调用服务方法。
- 工厂容器感知
考虑到容器感知实现的是把框架的资源对象注入到Bean的属性,相当于Bean实例化后的增强操作,因此放入到initializeBean中实现。而实例化Bean又是在BeanFactory中进行的,因此可以把this对象传给用户拿到工厂。
- 上下文容器感知
因为ApplicationContext上下文对象和工厂对象是调用的依赖关系,createBean方法获取不到上下文操作对象,因此需要进一步扩大容器感知范围,也就是在Refresh中先实现对上下文操作对象的容器感知。
实现细节是在refresh中使用BeanPostProcessor类对上下文操作对象(this)进行封装,在增强方法中判断bean的接口类型并把上下文操作对象传给bean的上下文属性。

# 8.对象作用域和FactoryBean
解决单例模式与原型模式:需要在BeanDefinition中添加scope字段指明采用哪种模式。需要修改的地方①对象添加进单例池前需要判断②原型模式不执行销毁方法,不需要注册销毁实例。
FactoryBean:用户自定义Bean工厂接口,提供更复杂bean对象生成方式,从而扩充对象功能。通过原生Spring创建出对象后,如果该对象实现了FactoryBean接口,那么最终getBean返回的对象需要由用户自定义工厂的getObject创建。
getObjectFromFactoryBean:提供了FactoryBean的缓存机制,以及缓存读写方法。
整个创建Bean对象的流程中,需要考虑是否为单例,FactoryBean对象,以及缓存是否存在。核心逻辑①原型对象获取和创建时不需要走缓存②单例对象获取前需要读缓存,创建后需要写缓存③FactoryBean对象,首先应该先进行单例判断。此外如果用户自定义的getObject返回空,那么缓存中需要new一个NULL_OBJECT对象来填坑(或者直接往容器中放NULL)。流程如下:

# 9.容器事件和事件监听器
功能:使用观察者模式定义事件类、监听类、发布类。事件用于传递source而监听类则是进行处理,发布者相当于一个中间调用的桥梁,事件推送时会找到监听该事件的监听者,并调用监听者的处理方法。实现的核心在于声明时把事件类型作为监听者类型的类型参数(泛型参数)。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener{
void onApplicationEvent(E event);
}
2
3
- 监听者和事件相互匹配
①获取监听者对象的类型参数,并通过getTypeName拿到类型名称。核心Api如下:
Class.getGenericInterfaces:返回调用Class所实现接口信息的Type数组,并包含泛型信息。
ParameterizedType.getActualTypeArguments:返回调用类型的类型参数(泛型参数)的实际Class类型数组。
②通过类型名反射拿到所监听事件的Class对象
③通过isAssignableFrom来判断当前监听者所监听的事件Class对象是否和当前事件Class对象相同(或者继承关系),如果符合则表明当前这个事件需要由该监听者来处理。
④监听者调用listener.onApplicationEvent(event)方法执行处理。意味着当前监听者可以拿到事件对象的触发时所有的属性(类似于容器感知,事件对象将上下文信息传给事件处理调用者)。
- 架构设计
事件和监听者是一对多的关系,一个事件可以被多个监听者监听并处理。用户定制化服务时需要自定义监听者和事件(或者使用Spring内部定义的事件),并把监听者交给Spring管理。发布者需要在refresh阶段创建一个对象交给Spring容器管理,对用户暴露publishEvent事件推送接口。
注意:监听者匹配事件时,如果监听者是通过Cglib代理创建Bean对象的话,不能直接通过反射获取Listener的泛型类,需要拿到代理对象的getSuperclass,这才是我们声明定义的监听者Class。
AbstractApplicationEventMulticaster抽象类:同时维护了监听者缓存容器,并实现了发布者事件推送的核心功能。
ApplicationContext接口:继承了更多接口,为上下文操作类赋能。
Refresh:主要添加如下过程:创建事件发布器并添加到单例池缓存——>注册Listener监听者,并加入缓存容器。
