21284 字
58 分钟
Spring核心:注解原理与手写Spring框架

spring注解加原理#

spring源码及其收获(查看源码讲解xmd)#

1、Spring容器中的Bean是何时创建的?为什么我们能从容器中获取到?#

结论

  • 非懒加载的单实例bean会在容器创建的时候创建。容器内部会创建一个beanFactory,使用beanFactory的doGetBean方法来进行创建,并且在创建后会把bean放入一个单例bean的map集合(singletonObjects)中存储。key就是我们配置的bean的名称。
  • 所以我们调用容器的getBean方法来获取对象的时候,其实他也是调用了doGetBean方法。就会从对应的集合中获取到之前创建的对象。

2、BeanDefinition是什么?是什么时候创建的?#

结论:

  • 在容器创建时会先去创建一个beanFactory,然后使用XmlBeanDefinitionReader去读取xml配置文件,把里面的标签进行解析,然后创建BeanDefinition对象来存放bean标签中的各个属性的值。所以BeanDefinition相当于就是保存了bean的定义信息的对象。
  • BeanDefinition被创建后会被存入beanFactoty的beanDefinitionMap集合和beanDefinitionNames集合中

3、探究SpringBoot项目中的Spring容器是在什么时候创建和刷新的?#

在启动类中调用SpringApplication的run方法时会根据容器的类型创建不同的容器对象,并且调用容器的refresh方法

createApplicationContext 方法创建的是系统自带的类

refresh方法 会将用户的类创建,并存入容器

4、@Import注解#

作用:

如果我们把所有的配置都写在一个配置类中不便于我们进行管理。所以Spring也支持我们编写多个配置类,只要使用@Impory注解引入其他配置类即可。作用相当于标签,可以跨包使用

探究@Import注解为什么能引入配置类:

ConfigurationClassParser的doProcessConfigurationClass方法在处理启动类的时候会先递归扫描其上所有的@Import注解,获取其中的的value属性值添加到集合中。然后获取遍历这个集合判断他们是ImportSelector的子类还是ImportBeanDefinitionRegistrar的子类还是配置类。如果是前面两种情况都不满足就按照配置类来出来。会继续调用processConfigurationClass(该类用于解析@Configuration注解的)方法来解析这个导入进来的配置类。

5、ImportSelector#

作用:

可以根据字符串数组(数组元素为类的全类名)来批量的加载指定的bean。

探究@Import注解引入ImportSelect是如何生效的:

  • ConfigurationClassParser的doProcessConfigurationClass方法在处理启动类的时候会先递归扫描其上所有的@Import注解,获取其中的的value属性值添加到集合中。然后获取遍历这个集合判断他们是ImportSelector的子类还是ImportBeanDefinitionRegistrar的子类还是配置类。如果ImportSelector则会去通过反射创建其对象,调用其方法获取字符串数组,封装成SourceClass对象。然后递归调用这些处理import的方法,对这些期望导入的类做同样的处理。
  • 如果导入的不是ImportSelector或者ImportBeanDefinitionRegistrar,也会被当做是配置类调用processConfigurationClass进行处理。这个方法中会把它们添加到一个配置类的集合中(configurationClasses)。后面会把配置类集合中的对象加载成BeanDefinition对象。
  • 可以使用ImportSelect导入一些配置类。因为在处理过程中会把他们当作配置类类进行解析处理。----作用之一

6、ImportBeanDefinitionRegistrar#

应用场景:

如要要实现动态Bean的装载可以使用ImportBeanDefinitionRegistrar。尤其是如果想装载动态代理对象的时候。例如Mybatis的启动器就是使用了它实现了Mapper接口的代理对象装载的。

7、探究SpringBoot项目中我们没有设置组件扫描的包为什么它会默认扫描启动类所在的包#

结论:

  • 在容器刷新时会调用BeanFactoryPostProcessor(Bean工厂后置处理器)进行处理。其中就有一个ConfigurationClassPostProcessor(配置类处理器)。在这个处理器中使用ConfigurationClassParser(配置类解析器)的parse方法去解析处理我们的配置类,其中就有对ComponentScan注解的解析处理。会去使用ComponentScanAnnotationParser的parse方法进行解析。解析时如果发现没有配置basePackage,它会去获取我们加了注解的这个类所在的包,作为我们的basepackage进行组件扫描。
  • ConfigurationClassParser中除了对组件扫描会进行处理,还对@PropertySource,@Import,@ImportResource,@Bean等注解进行处理

《spring注解及其原理》#

准备工作

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_annotation</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
<!-- 用于单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
</dependencies>
</project>

容器组件#

给容器中添加组件

  • 1)、包扫描+组件标注注解(@controller/@service/@repository/@component)[自己写的]

  • 2)、@Bean【导入的第三方包里面的组件】

  • 3)、@Import[快速的给容器中导入一个组件]

  • ​ @Import(要导入容器的组件):容器就会自动注册这个组件,id默认是全类名
  • ​ ImportSelector 返回需要注册到容器中的组件的全类名数组
  • ​ ImportBeanDefinitionRegistrar:手动注册bean到容器中
  • 4)、使用spring提供的FactoryBean(工厂bean)

@Configuration#

告诉spring这是一个配置类

//配置类==配置文件
@Configuration
public class Mainconfig {
//给容器注册一个bean;类型为返回值类型;ID是默认方法名作为ID
@Bean
public Person person01(){
return new Person("lisi",20);
}
}

@Bean#

给容器注册一个bean;类型为返回值类型;ID是默认方法名作为ID

不用被扫描到,自己就会注入到ioc容器中去

默认是 单实例的

@Bean(“填入为bean指定名称”)

AnnotationConfigApplicationContext#

注解配置应用上下文

image-20210320105655270

@ComponentScan#

扫描指定包中注解那些可以注入的bean

value 指定包名

excludeFilters

includeFilters

useDefaultFilters

FilterType.ANNOTATION :按照注解

FilterType.ASSIGNABLE_TYPE :按照指定类型 FilterType.REGEX :按照指定正则表达式 FilterType.CUSTOM :使用自定义规则

@ComponentScan(value = "com.eim",
//excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
useDefaultFilters = false
)
//@ComponentScan value:指定要扫描的包
//@ComponentScan注解中有@Repeatable(ComponentScans.class),表明可以重复使用
//excludeFilters = Filter[]: 指定扫描时按什么规则排除那些组件
//includeFilters = Filter[]: 指定扫描时按什么规则包含那些组件,生效需要:useDefaultFilters = false
//FilterType.ANNOTATION :按照注解
//FilterType.ASSIGNABLE_TYPE :按照指定类型
//FilterType.REGEX :按照指定正则表达式
//FilterType.CUSTOM :使用自定义规则

image-20210320113055798

测试自定义规则#

image-20210320121250924

package com.eim.filter;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
//自定义过滤规则,实现TypeFilter接口,
public class MyTypeFilter implements TypeFilter {
/**
* 实现match方法,返回true匹配成功,返回false匹配失败
* metadataReader: 读取到当前正在扫描的信息
* metadataReaderFactory: 可以获取到其他任何类信息的
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前类注解信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描类的信息(类型,是否实现接口)
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源信息(类的路径)
Resource resource = metadataReader.getResource();
String className = classMetadata.getClassName();
System.out.println("------------->"+className);
if(className.contains("er")){
return true;
}
return false;
}
}
@ComponentScan(value = "com.eim",
//excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
includeFilters = {
//@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
//@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BaseService.class})
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
},
useDefaultFilters = false
)
public class IOCTest {
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Mainconfig.class);
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}

@ComponentScans#

//可放入多个@ComponentScan
@ComponentScans(value = {
@ComponentScan(value = "com.eim",
//excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
useDefaultFilters = false
)
})

@Scope#

调整bean创建作用域

可用值:

prototype: 多实例 singleton: 单实例(默认)

singleton :默认单实例情况下,启动ioc容器是调用方法创建对象到ioc容器中,以后每次获取从容器(map.get)中拿

@Bean("person2")
public Person person(){
System.out.println("------给容器添加person2-----");
return new Person("zhangsan",25);
}

prototype :ioc启动并不会去调用方法创建对象,每次获取时调用方法创建对象;

@Scope("prototype")
@Bean("person2")
public Person person(){
System.out.println("------给容器添加person2-----");
return new Person("zhangsan",25);
}

@Lazy 懒加载#

单实例: 默认容器启动是创建对象;

懒加载:容器启动不创建对象。第一次使用(获取)bean创建对象,并初始化。(只会被创建一次)

@Lazy
@Bean("person2")
public Person person(){
System.out.println("------给容器添加person2-----");
return new Person("zhangsan",25);
}

@Conditional#

按照一定条件进行判断,满足条件给容器中注册bean,可以作用在类和方法上

@Conditional({实现Conditional接口的类})

1、配置文件类#

//配置类==配置文件
@Configuration
@ComponentScan(value = "com.eim")
public class MainConditionalconfig {
/**
*
* 如果是Windows bill
* 如果是linux linux
*/
@Conditional({WindowsConditional.class})
@Bean("bill")
public Person person1(){
return new Person("bill",25);
}
@Conditional({LinuxConditional.class})
@Bean("linux")
public Person person2(){
return new Person("linux",30);
}
}

2、测试类#

public class IOCTest {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MainConditionalconfig.class);
@Test
public void test03(){
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//动态获取环境变量的值
String property = environment.getProperty("os.name");
System.out.println("os.name:"+property);
//名字
String[] names = applicationContext.getBeanNamesForType(Person.class);
for (String name : names) {
System.out.println("name="+name);
}
//person对象的map集合
Map<String, Person> personMap = applicationContext.getBeansOfType(Person.class);
System.out.println(personMap);
}
}

3、自定义conditional接口#

LinuxConditional 和一下代码一致,除了判断条件

public class WindowsConditional implements Condition {
/**
* ConditionContext 判断能使用的上下文(环境)
* AnnotatedTypeMetadata 注释信息
*
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//是否是windons系统
//ioc容器的bean工厂
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//获取到运行时的信息,环境变量,虚拟机环境变量
Environment environment = context.getEnvironment();
//获取类加载器
ClassLoader classLoader = context.getClassLoader();
//获取bean定义的注册类:所有的bean注册在这类中,可以查询和注册bean
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
}
}

4、修改虚拟机参数#

image-20210321174521370

@Import#

@Import <导入组件>,id默认时组件的类全名

ImportSelector接口<返回需要导入组件的全类名数组>

1、自定义 ImportSelector接口#

//自定义需要导入的组件
public class MyImportSelector implements ImportSelector {
//返回值,就是导入到容器中的组件全类名
//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.eim.bean.Blue"};
}
}

2、配置#

@Import({Color.class, MyImportSelector.class})

image-20210321180721925

3、测试#

image-20210321180805313

factoryBean#

1、工厂获取的是调用getObject创建的对象

2、获取工厂bean本身,需要给id前加上&

1、自定义

//自定义factoryBean
public class ColorFactoryBean implements FactoryBean {
//返回的对象会被注册到ioc容器中
@Override
public Object getObject() throws Exception {
System.out.println("创建color");
return new Color("黄色");
}
@Override
public Class<?> getObjectType() {
return null;
}
/**
* true 单例
* false 每次获取时,调用getObject方法生成对象
*/
@Override
public boolean isSingleton() {
return false;
}
}

2、配置生效

配置类中加入配置

@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}

3、测试

@Test
public void test04(){
getAllBean(applicationContext);
//工厂bean获取的是调用getObject创建的对象
Object bean = applicationContext.getBean("colorFactoryBean");
System.out.println("bean的类型:"+bean.getClass());
//获取工厂类本身name前加上&
Object bean2 = applicationContext.getBean("&colorFactoryBean");
System.out.println("bean2的类型:"+bean2.getClass());
}

生命周期#

》 bean的创建 》初始化》销毁过程

容器管理bean的生命周期

我们可以自定义初始化和销毁方法:容器在bean进行到当前生命周期时调用我们自定义的方法

1)、指定初始化和销毁方法

Spring解决循环依赖,你真的懂了吗?#

什么是循环依赖#

简单的说就是A依赖B,B依赖C,C依赖A这样就构成了循环依赖。

img

循环依赖分为构造器依赖属性依赖,众所周知的是Spring能够解决属性的循环依赖(set注入)。下文将从源码角度分析Spring是如何解决属性的循环依赖。思路

如何解决循环依赖,Spring主要的思路就是依据三级缓存,在实例化A时调用doGetBean,发现A依赖的B的实例,此时调用doGetBean去实例B,实例化的B的时候发现又依赖A,如果不解决这个循环依赖的话此时的doGetBean将会无限循环下去,导致内存溢出,程序奔溃。spring引用了一个早期对象,并且把这个”早期引用”并将其注入到容器中,让B先完成实例化,此时A就获取B的引用,完成实例化。三级缓存

Spring能够轻松的解决属性的循环依赖正式用到了三级缓存,在AbstractBeanFactory中有详细的注释。

img

一级缓存:singletonObjects,存放完全实例化属性赋值完成的Bean,直接可以使用。

二级缓存:earlySingletonObjects,存放早期Bean的引用,尚未属性装配的Bean

三级缓存:singletonFactories,三级缓存,存放实例化完成的Bean****工厂

什么是循环依赖

简单的说就是A依赖B,B依赖C,C依赖A这样就构成了循环依赖。

开撸#

先上一张流程图看看Spring是如何解决循环依赖的

img

上图标记蓝色的部分都是涉及到三级缓存的操作,下面我们一个一个方法解析【1】 getSingleton(beanName):源码如下:

img

从源码可以得知,doGetBean最初是查询缓存,一二三级缓存全部查询,如果三级缓存存在则将Bean早期引用存放在二级缓存中并移除三级缓存。(升级为二级缓存)【2】addSingletonFactory:源码如下

img

从源码得知,Bean在实例化完成之后会直接将未装配的Bean工厂存放在三级缓存中,并且移除二级缓存【3】addSingleton:源码如下:

img

总之一句话,Bean添加到一级缓存,移除二三级缓存。

扩展#

【1】为什么Spring不能解决构造器的循环依赖?#

从流程图应该不难看出来,在Bean调用构造器实例化之前,一二三级缓存并没有Bean的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。

【2】为什么多实例Bean不能解决循环依赖?#

多实例Bean是每次创建都会调用doGetBean方法,根本没有使用一二三级缓存,肯定不能解决循环依赖。

【3】构造器循环依赖#

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

总结#

根据以上的分析,大概清楚了Spring是如何解决循环依赖的。假设A依赖B,B依赖A(注意:这里是set属性依赖)分以下步骤执行:A依次执行doGetBean、查询缓存、createBean创建实例,实例化完成放入三级缓存singletonFactories中,接着执行populateBean方法装配属性,但是发现有一个属性是B的对象。因此再次调用doGetBean方法创建B的实例,依次执行doGetBean、查询缓存、createBean创建实例,实例化完成之后放入三级缓存singletonFactories中,执行populateBean装配属性,但是此时发现有一个属性是A对象。因此再次调用doGetBean创建A的实例,但是执行到getSingleton查询缓存的时候,从三级缓存中查询到了A的实例(早期引用,未完成属性装配),此时直接返回A,不用执行后续的流程创建A了,那么B就完成了属性装配,此时是一个完整的对象放入到一级缓存singletonObjects中。B创建完成了,则A自然完成了属性装配,也创建完成放入了一级缓存singletonObjects中。Spring三级缓存的应用完美的解决了循环依赖的问题,下面是循环依赖的解决流程图。

img


手写spring#

《Spring 手撸专栏》#

地址:第01章:开篇介绍,我要带你撸 Spring 啦! | 小傅哥 bugstack 虫洞栈

本文只记录难点,难以理解的点

第 2 章:小试牛刀,实现一个简单的Bean容器#

目标#

一个简单的 Spring Bean 容器实现,还需 Bean 的定义、注册、获取三个基本步骤,简化设计如下;

img

  • 定义:BeanDefinition,可能这是你在查阅 Spring 源码时经常看到的一个类,例如它会包括 singleton、prototype、BeanClassName 等。但目前我们初步实现会更加简单的处理,只定义一个 Object 类型用于存放对象。
  • 注册:这个过程就相当于我们把数据存放到 HashMap 中,只不过现在 HashMap 存放的是定义了的 Bean 的对象信息。
  • 获取:最后就是获取对象,Bean 的名字就是key,Spring 容器初始化好 Bean 以后,就可以直接获取了。

图 2-2

由于BeanDefinition中存放的就是对象,故HashMap 即为对象容器。(按照道理应该分为BeanDefinition集合和对象集合)

测试#

image-20231130191730046

疑问#

为什么需要BeanDefinition?

Spring Bean的总体创建过程如下,Spring会加载指定的java类将其变成BeanDefinition对象,然后根据BeanDefinition来创建Bean

img

第 3 章:初显身手,运用设计模式,实现 Bean 的定义、注册、获取#

目标#

  • 这一次我们把 Bean 的创建交给容器,而不是我们在调用时候传递一个实例化好的 Bean 对象,

  • 另外还需要考虑单例对象,在对象的二次获取时是可以从内存中获取对象的。

  • 此外不仅要实现功能还需要完善基础容器框架的类结构体,否则将来就很难扩容进去其他的功能了。

设计#

我们需要将 Spring Bean 容器完善起来,

  • 首先非常重要的一点是在 Bean 注册的时候只注册一个类信息,而不会直接把实例化信息注册到 Spring 容器中。那么就需要修改 BeanDefinition 中的属性 Object 为 Class,

  • 接下来在需要做的就是在获取 Bean 对象时需要处理 Bean 对象的实例化操作以及判断当前单例对象在容器中是否已经缓存起来了。整体设计如图 3-1

img

  • 首先我们需要定义 BeanFactory 这样一个 Bean 工厂,提供 Bean 的获取方法 getBean(String name),之后这个 Bean 工厂接口由抽象类 AbstractBeanFactory 实现。这样使用模板模式 (opens new window)的设计方式,可以统一收口通用核心方法的调用逻辑和标准定义,也就很好的控制了后续的实现者不用关心调用逻辑,按照统一方式执行。那么类的继承者只需要关心具体方法的逻辑实现即可。
  • 那么在继承抽象类 AbstractBeanFactory 后的 AbstractAutowireCapableBeanFactory 就可以实现相应的抽象方法了,因为 AbstractAutowireCapableBeanFactory 本身也是一个抽象类,所以它只会实现属于自己的抽象方法,其他抽象方法由继承 AbstractAutowireCapableBeanFactory 的类实现。这里就体现了类实现过程中的各司其职,你只需要关心属于你的内容,不是你的内容,不要参与。这一部分内容我们会在代码里有具体的体现
  • 另外这里还有块非常重要的知识点,就是关于单例 SingletonBeanRegistry 的接口定义实现,而 DefaultSingletonBeanRegistry 对接口实现后,会被抽象类 AbstractBeanFactory 继承。现在 AbstractBeanFactory 就是一个非常完整且强大的抽象类了,也能非常好的体现出它对模板模式的抽象定义。接下来我们就带着这些设计层面的思考,去看代码的具体实现结果

难点在于复杂度提高

small-spring-step-02
└── src
├── main
└── java
└── cn.bugstack.springframework.beans
├── factory
├── config
├── BeanDefinition.java
└── SingletonBeanRegistry.java
├── support
├── AbstractAutowireCapableBeanFactory.java
├── AbstractBeanFactory.java
├── BeanDefinitionRegistry.java
├── DefaultListableBeanFactory.java
└── DefaultSingletonBeanRegistry.java
└── BeanFactory.java
└── BeansException.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
└── UserService.java
└── ApiTest.java

类图

图 3-2

虽然这一章节关于 Spring Bean 容器的功能实现与 Spring 源码中还有不少的差距,但以目前实现结果的类关系图来看,其实已经具备了一定的设计复杂性,这些复杂的类关系设计在各个接口定义和实现以及在抽象类继承中都有所体现,例如:

  • BeanFactory 的定义由 AbstractBeanFactory 抽象类实现接口的 getBean 方法
  • 而 AbstractBeanFactory 又继承了实现了 SingletonBeanRegistry 的DefaultSingletonBeanRegistry 类。这样 AbstractBeanFactory 抽象类就具备了单例 Bean 的注册功能。
  • AbstractBeanFactory 中又定义了两个抽象方法:getBeanDefinition(String beanName)、createBean(String beanName, BeanDefinition beanDefinition) ,而这两个抽象方法分别由 DefaultListableBeanFactory、AbstractAutowireCapableBeanFactory 实现。
  • 最终 DefaultListableBeanFactory 还会继承抽象类 AbstractAutowireCapableBeanFactory 也就可以调用抽象类中的 createBean 方法了。

综上这一部分的类关系和实现过程还是会有一些复杂的,因为所有的实现都以职责划分、共性分离以及调用关系定义为标准搭建的类关系。这部分内容的学习,可能会丰富你在复杂业务系统开发中的设计思路。

image-20231120134908675

设计模式#

抽象类定义模板方法(AbstractBeanFactory)#

cn.bugstack.springframework.beans.factory.support.AbstractBeanFactory

public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {
@Override
public Object getBean(String name) throws BeansException {
Object bean = getSingleton(name);
if (bean != null) {
return bean;
}
BeanDefinition beanDefinition = getBeanDefinition(name);
return createBean(name, beanDefinition);
}
protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeansException;
protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeansException;
}
  • AbstractBeanFactory 首先继承了 DefaultSingletonBeanRegistry,也就具备了使用单例注册类方法。
  • 接下来很重要的一点是关于接口 BeanFactory 的实现,在方法 getBean 的实现过程中可以看到,主要是对单例 Bean 对象的获取以及在获取不到时需要拿到 Bean 的定义做相应 Bean 实例化操作。那么 getBean 并没有自身的去实现这些方法,而是只定义了调用过程以及提供了抽象方法,由实现此抽象类的其他类做相应实现。
  • 后续继承抽象类 AbstractBeanFactory 的类有两个,包括:AbstractAutowireCapableBeanFactory、DefaultListableBeanFactory,这两个类分别做了相应的实现处理,接着往下看。

集中方法#

重要:接口定义了注册,抽象类定义了获取,都集中在 DefaultListableBeanFactory 中的 beanDefinitionMap 里

测试#

image-20231130191830088

疑问#

为什么有两个容器(单例对象容器、对象定义容器)?

//对象定义容器
Map<String, BeanDefinition> beanDefinitionMap

此容器只存放对象定义,也就是传入的Class对象会被包装成BeanDefinition,然后放入DefaultListableBeanFactory 中的beanDefinitionMap集合中保存。

//单例对象容器
HashMap<String, Object> singletonObjects
  • 单例对象容器存放所有对象,不管是不是单例。

  • 只要调用获取对象时判断单例对象容器中没有该对象,则根据bean定义创建对象,并加入单例容器中。

解析#

2、类和接口的简析

①BeanDefinition:由存储bean对象,改为存储类信息,这是将实例化Bean交给Spring操作的第一步

②SingletonBeanRegistry:获取单例对象的接口类

③DefaultSingletonBeanRegistry:继承SingletonBeanRegistry,定义单例对象的存储容器以及存取方法

④BeanFactory:定义了获取bean对象的接口

⑤AbstractBeanFactory:继承DefaultSingletonBeanRegistry类,就可以存取(注册)单例对象;继承BeanFactory实现bean对象的获取方法,并定义了getBeanDefinition、createBean抽象方法,如果从单例对象容器中获取不到对象,就调用这俩抽象方法 ⑥AbstractAutowireCapableBeanFactory:继承AbstractBeanFactory,实现createBean

⑦BeanDefinitionRegistry:定义注册BeanDefinition的接口

⑧DefaultListableBeanFactory(核心类):继承AbstractAutowireCapableBeanFactory,就有了createBean的能力,并实现了BeanDefinitionRegistry#registerBeanDefinition,并实现了AbstractBeanFactory#getBeanDefinition,使得拥有了beanDefinitionMap容器的存取功能,并且该类是核心类,具有对BeanDefinition容器、单例Bean容器这两个核心容器的操作权限 ⑨UserService:自定义的类,测试时作为bean的来源

初步搭建容器

3、调用流程

①实例化DefaultListableBeanFactory

②将UserService类作为构造器参数,实例化BeanDefinition对象,

③第①步的工厂对象调用registerBeanDefinition方法,将第②步的beanDefinition对象注册到BeanDefinitionMap容器

④利用工厂获取UserService的bean对象,先从单例对象容器中获取,获取不到就从BeanDefinition容器中获取UserService类并实例化(newInstance),并将实例化对象存入单例对象容器中,方便下次调用

⑤第二次调用UserService的bean对象时,就可以直接从单例Bean对象容器中获取了

收获#

相对简单bean容器

  • BeanDefinition中修改了Object到Class

  • BeanDefinition使用Map集合进行了存储

  • 使用了设计模式中的 ——模板模式

  • 巧妙的使用了接口,抽象类做到单一职责

第 4 章:崭露头角,基于Cglib实现含构造函数的类实例化策略#

目标#

这一章节的目标主要是为了解决上一章节我们埋下的坑,那是什么坑呢?其实就是一个关于 Bean 对象在含有构造函数进行实例化的坑。

在上一章节我们扩充了 Bean 容器的功能,把实例化对象交给容器来统一处理,但在我们实例化对象的代码里并没有考虑对象类是否含构造函数,也就是说如果我们去实例化一个含有构造函数的对象那么就要抛异常了。

怎么验证?其实就是把 UserService 添加一个含入参信息的构造函数就可以,如下:

public class UserService {
private String name;
public UserService(String name) {
this.name = name;
}
// ...
}

报错如下:

java.lang.InstantiationException: cn.bugstack.springframework.test.bean.UserService
at java.lang.Class.newInstance(Class.java:427)
at cn.bugstack.springframework.test.ApiTest.test_newInstance(ApiTest.java:51)
...

发生这一现象的主要原因就是因为 beanDefinition.getBeanClass().newInstance(); 实例化方式并没有考虑构造函数的入参,所以就这个坑就在这等着你了!那么我们的目标就很明显了,来把这个坑填平!

三、设计#

填平这个坑的技术设计主要考虑两部分,一个是串流程从哪合理的把构造函数的入参信息传递到实例化操作里,另外一个是怎么去实例化含有构造函数的对象。

图 4-1

  • 参考 Spring Bean 容器源码的实现方式,在 BeanFactory 中添加 Object getBean(String name, Object... args) 接口,这样就可以在获取 Bean 时把构造函数的入参信息传递进去了。
  • 另外一个核心的内容是使用什么方式来创建含有构造函数的 Bean 对象呢?这里有两种方式可以选择,一个是基于 Java 本身自带的方法 DeclaredConstructor,另外一个是使用 Cglib 来动态创建 Bean 对象。Cglib 是基于字节码框架 ASM 实现,所以你也可以直接通过 ASM 操作指令码来创建对象

四、实现#

1. 工程结构#

small-spring-step-03
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework.beans
│ ├── factory
│ │ ├── config
│ │ │ ├── BeanDefinition.java
│ │ │ └── SingletonBeanRegistry.java
│ │ ├── support
│ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ ├── AbstractBeanFactory.java
│ │ │ ├── BeanDefinitionRegistry.java
│ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ ├── DefaultListableBeanFactory.java
│ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ ├── InstantiationStrategy.java
│ │ │ └── SimpleInstantiationStrategy.java
│ │ └── BeanFactory.java
│ └── BeansException.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ └── UserService.java
└── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring Bean 容器类关系,如图 4-2

图 4-2

本章节“填坑”主要是在现有工程中添加 InstantiationStrategy 实例化策略接口,以及补充相应的 getBean 入参信息,让外部调用时可以传递构造函数的入参并顺利实例化。

有参构造实例化对象

测试#

image-20231130192031252

知识点#

getDeclaredConstructor#

getDeclaredConstructor 是Java反射(Reflection)API中的方法之一,属于 Class 类的一部分。它用于获取类中声明的构造方法(包括私有构造方法),而不管构造方法的访问修饰符是什么。

public Constructor<?> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

其中,parameterTypes 参数是一个可变参数,用于指定要获取的构造方法的参数类型列表。

使用示例:

import java.lang.reflect.Constructor;
public class Example {
public static void main(String[] args) {
try {
Class<?> clazz = MyClass.class;
// 获取声明的构造方法,这里假设 MyClass 有一个参数为 int 的构造方法
Constructor<?> constructor = clazz.getDeclaredConstructor(int.class);
// 使用构造方法创建对象
Object obj = constructor.newInstance(42);
} catch (Exception e) {
e.printStackTrace();
}
}
static class MyClass {
private int value;
// 假设有一个参数为 int 的私有构造方法
private MyClass(int value) {
this.value = value;
}
}
}

在上述示例中,getDeclaredConstructor 方法被用于获取 MyClass 类中声明的私有构造方法,然后通过 newInstance 方法创建了一个对象。这种通过反射获取构造方法并创建对象的方式在某些情况下是非常有用的,比如在框架中动态创建对象实例。

CGLIB——Enhancer#

示例:

public class CglibSubclassingInstantiationStrategy implements InstantiationStrategy {
@Override
public Object instantiate(BeanDefinition beanDefinition, String beanName, Constructor constructor, Object[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(beanDefinition.getBeanClass());
enhancer.setCallback(new NoOp() {
@Override
public int hashCode() {
return super.hashCode();
}
});
if (null == constructor) return enhancer.create();
return enhancer.create(constructor.getParameterTypes(), args);
}
}

解答:

这段代码涉及到 CGLIB(Code Generation Library)库的使用,它是一个用于生成动态代理和代码增强的库。在 Spring 中,CGLIB 可以用于创建代理对象,特别是用于代理那些没有接口的类。

以下是代码中使用到的 CGLIB 方法的解释:

  1. Enhancer: Enhancer 是 CGLIB 中的一个类,用于生成代理类。通过 Enhancer 可以创建一个类的子类,该子类可以覆盖父类的方法,实现方法的拦截和增强。

    Enhancer enhancer = new Enhancer();
  2. setSuperclass: 通过 setSuperclass 方法设置需要生成子类的父类。在这里,beanDefinition.getBeanClass() 返回的是要被代理的类的 Class 对象。

    enhancer.setSuperclass(beanDefinition.getBeanClass());
  3. setCallback: 通过 setCallback 方法设置一个回调对象,这里使用了 NoOp 类,表示不进行任何操作,只是简单地继承父类的方法。

    enhancer.setCallback(new NoOp() {
    @Override
    public int hashCode() {
    return super.hashCode();
    }
    });
  4. create: 调用 create 方法生成代理类的实例。如果 constructornull,则调用无参构造方法创建对象;否则,调用带参数的构造方法创建对象。

    if (null == constructor) return enhancer.create();
    return enhancer.create(constructor.getParameterTypes(), args);

总体来说,这段代码的目的是使用 CGLIB 生成一个代理类的实例。代理类继承了要被代理的类,通过设置回调对象实现对父类方法的拦截和增强。在生成代理类实例时,可以选择使用无参构造方法或带参数的构造方法。这种方式允许在运行时对目标类的行为进行灵活的控制和修改。

第 5 章:一鸣惊人,为Bean对象注入属性和依赖Bean的功能实现#

二、目标#

首先我们回顾下这几章节都完成了什么,包括:实现一个容器 (opens new window)定义和注册Bean (opens new window)实例化Bean (opens new window)按照是否包含构造函数实现不同的实例化策略 (opens new window),那么在创建对象实例化这我们还缺少什么?其实还缺少一个关于类中是否有属性的问题,如果有类中包含属性那么在实例化的时候就需要把属性信息填充上,这样才是一个完整的对象创建。

对于属性的填充不只是 int、Long、String,还包括还没有实例化的对象属性,都需要在 Bean 创建时进行填充操作。不过这里我们暂时不会考虑 Bean 的循环依赖,否则会把整个功能实现撑大,这样新人学习时就把握不住了,待后续陆续先把核心功能实现后,再逐步完善

三、设计#

鉴于属性填充是在 Bean 使用 newInstance 或者 Cglib 创建后,开始补全属性信息,那么就可以在类 AbstractAutowireCapableBeanFactory 的 createBean 方法中添加补全属性方法。这部分大家在实习的过程中也可以对照Spring源码学习,这里的实现也是Spring的简化版,后续对照学习会更加易于理解

img

  • 属性填充要在类实例化创建之后,也就是需要在 AbstractAutowireCapableBeanFactory 的 createBean 方法中添加 applyPropertyValues 操作。
  • 由于我们需要在创建Bean时候填充属性操作,那么就需要在 bean 定义 BeanDefinition 类中,添加 PropertyValues 信息。
  • 另外是填充属性信息还包括了 Bean 的对象类型,也就是需要再定义一个 BeanReference,里面其实就是一个简单的 Bean 名称,在具体的实例化操作时进行递归创建和填充,与 Spring 源码实现一样。Spring 源码中 BeanReference 是一个接口

四、实现#

1. 工程结构#

small-spring-step-04
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework.beans
│ ├── factory
│ │ ├── config
│ │ │ ├── BeanDefinition.java
│ │ │ ├── BeanReference.java
│ │ │ └── SingletonBeanRegistry.java
│ │ ├── support
│ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ ├── AbstractBeanFactory.java
│ │ │ ├── BeanDefinitionRegistry.java
│ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ ├── DefaultListableBeanFactory.java
│ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ ├── InstantiationStrategy.java
│ │ │ └── SimpleInstantiationStrategy.java
│ │ └── BeanFactory.java
│ ├── BeansException.java
│ ├── PropertyValue.java
│ └── PropertyValues.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── UserDao.java
│ └── UserService.java
└── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring Bean 容器类关系,如图 5-2

图 5-2

  • 本章节中需要新增加3个类,BeanReference(类引用)、PropertyValue(属性值)、PropertyValues(属性集合),分别用于类和其他类型属性填充操作。
  • 另外改动的类主要是 AbstractAutowireCapableBeanFactory,在 createBean 中补全属性填充部分

step05注入属性

测试#

image-20231130192229884

核心关键#

Bean 属性填充#

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {
private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
Object bean = null;
try {
bean = createBeanInstance(beanDefinition, beanName, args);
// 给 Bean 填充属性
applyPropertyValues(beanName, bean, beanDefinition);
} catch (Exception e) {
throw new BeansException("Instantiation of bean failed", e);
}
addSingleton(beanName, bean);
return bean;
}
protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) {
Constructor constructorToUse = null;
Class<?> beanClass = beanDefinition.getBeanClass();
Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();
for (Constructor ctor : declaredConstructors) {
if (null != args && ctor.getParameterTypes().length == args.length) {
constructorToUse = ctor;
break;
}
}
return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args);
}
/**
* Bean 属性填充
*/
protected void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
try {
PropertyValues propertyValues = beanDefinition.getPropertyValues();
for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
String name = propertyValue.getName();
Object value = propertyValue.getValue();
if (value instanceof BeanReference) {
// A 依赖 B,获取 B 的实例化
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getBeanName());
}
// 属性填充
BeanUtil.setFieldValue(bean, name, value);
}
} catch (Exception e) {
throw new BeansException("Error setting property values:" + beanName);
}
}
public InstantiationStrategy getInstantiationStrategy() {
return instantiationStrategy;
}
public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
this.instantiationStrategy = instantiationStrategy;
}
}
  • 这个类的内容稍微有点长,主要包括三个方法:createBean、createBeanInstance、applyPropertyValues,这里我们主要关注 createBean 的方法中调用的 applyPropertyValues 方法。
  • 在 applyPropertyValues 中,通过获取 beanDefinition.getPropertyValues() 循环进行属性填充操作,如果遇到的是 BeanReference,那么就需要递归获取 Bean 实例,调用 getBean 方法。
  • 当把依赖的 Bean 对象创建完成后,会递归回现在属性填充中。这里需要注意我们并没有去处理循环依赖的问题,这部分内容较大,后续补充。BeanUtil.setFieldValue(bean, name, value) 是 hutool-all 工具类中的方法,你也可以自己实现

疑问#

如何理解spring中BeanReference?

在 Spring 框架中,BeanReference 通常用于表示一个对另一个 Bean 的引用。它是 Spring IoC 容器中处理依赖关系的一部分,用于描述一个 Bean 对另一个 Bean 的依赖。

具体来说,BeanReference 类一般包含了被引用 Bean 的名称(或者其他标识符),以及可能的一些其他属性。它的存在主要是为了在实际需要使用被引用 Bean 的地方,延迟实际的 Bean 实例化和获取,而只保留一个引用信息。这有助于解决循环依赖等问题。

以下是一个简单的示例来理解 BeanReference 的概念:

假设有两个类 ClassAClassB,其中 ClassA 需要引用 ClassB

public class ClassA {
private ClassB classB;
public void setClassB(ClassB classB) {
this.classB = classB;
}
// 其他业务逻辑...
}
public class ClassB {
// 一些属性和方法...
}

在 Spring 配置文件中,可以使用 BeanReference 来表示对 ClassB 的引用:

<bean id="classB" class="com.example.ClassB"/>
<bean id="classA" class="com.example.ClassA">
<property name="classB">
<ref bean="classB"/>
</property>
</bean>

在这个示例中,<ref bean="classB"/> 使用了 BeanReference,表示 ClassAClassB 的引用。当 Spring IoC 容器创建 ClassA 实例时,它会将对 ClassB 的引用注入到 ClassAclassB 属性中。

在 Spring 的内部实现中,BeanReference 对象存储了被引用 Bean 的名称,而不是直接存储对目标 Bean 的引用。这样可以延迟获取实际的 Bean 实例,从而解决循环依赖等问题。

总之,BeanReference 是 Spring 中用于描述 Bean 之间依赖关系的一种机制,它使得容器能够在需要的时候,按需获取被引用 Bean 的实例。

第 6 章:气吞山河,设计与实现资源加载器,从Spring.xml解析和注册Bean对象#

二、目标#

在完成 Spring 的框架雏形后,现在我们可以通过单元测试进行手动操作 Bean 对象的定义、注册和属性填充,以及最终获取对象调用方法。但这里会有一个问题,就是如果实际使用这个 Spring 框架,是不太可能让用户通过手动方式创建的,而是最好能通过配置文件的方式简化创建过程。需要完成如下操作:

img

  • 如图中我们需要把步骤:2、3、4整合到Spring框架中,通过 Spring 配置文件的方式将 Bean 对象实例化。
  • 接下来我们就需要在现有的 Spring 框架中,添加能解决 Spring 配置的读取、解析、注册Bean的操作。

三、设计#

依照本章节的需求背景,我们需要在现有的 Spring 框架雏形中添加一个资源解析器,也就是能读取classpath、本地文件和云文件的配置内容。这些配置内容就是像使用 Spring 时配置的 Spring.xml 一样,里面会包括 Bean 对象的描述和属性信息。 在读取配置文件信息后,接下来就是对配置文件中的 Bean 描述信息解析后进行注册操作,把 Bean 对象注册到 Spring 容器中。整体设计结构如下图:

img

  • 资源加载器属于相对独立的部分,它位于 Spring 框架核心包下的IO实现内容,主要用于处理Class、本地和云环境中的文件信息。
  • 当资源可以加载后,接下来就是解析和注册 Bean 到 Spring 中的操作,这部分实现需要和 DefaultListableBeanFactory 核心类结合起来,因为你所有的解析后的注册动作,都会把 Bean 定义信息放入到这个类中。
  • 那么在实现的时候就设计好接口的实现层级关系,包括我们需要定义出 Bean 定义的读取接口 BeanDefinitionReader 以及做好对应的实现类,在实现类中完成对 Bean 对象的解析和注册。

四、实现#

1. 工程结构#

small-spring-step-05
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── xml
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── UserDao.java
│ └── UserService.java
└── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring Bean 容器资源加载和使用类关系,如图 6-3

图 6-3

  • 本章节为了能把 Bean 的定义、注册和初始化交给 Spring.xml 配置化处理,那么就需要实现两大块内容,分别是:资源加载器、xml资源处理类,实现过程主要以对接口 ResourceResourceLoader 的实现,而另外 BeanDefinitionReader 接口则是对资源的具体使用,将配置信息注册到 Spring 容器中去。
  • 在 Resource 的资源加载器的实现中包括了,ClassPath、系统文件、云配置文件,这三部分与 Spring 源码中的设计和实现保持一致,最终在 DefaultResourceLoader 中做具体的调用。
  • 接口:BeanDefinitionReader、抽象类:AbstractBeanDefinitionReader、实现类:XmlBeanDefinitionReader,这三部分内容主要是合理清晰的处理了资源读取后的注册 Bean 容器操作。接口管定义,抽象类处理非接口功能外的注册Bean组件填充,最终实现类即可只关心具体的业务实现

另外本章节还参考 Spring 源码,做了相应接口的集成和实现的关系,虽然这些接口目前还并没有太大的作用,但随着框架的逐步完善,它们也会发挥作用。如图 6-4

图 6-4

  • BeanFactory,已经存在的 Bean 工厂接口用于获取 Bean 对象,这次新增加了按照类型获取 Bean 的方法:<T> T getBean(String name, Class<T> requiredType)
  • ListableBeanFactory,是一个扩展 Bean 工厂接口的接口,新增加了 getBeansOfTypegetBeanDefinitionNames() 方法,在 Spring 源码中还有其他扩展方法。
  • HierarchicalBeanFactory,在 Spring 源码中它提供了可以获取父类 BeanFactory 方法,属于是一种扩展工厂的层次子接口。Sub-interface implemented by bean factories that can be part of a hierarchy.
  • AutowireCapableBeanFactory,是一个自动化处理Bean工厂配置的接口,目前案例工程中还没有做相应的实现,后续逐步完善。
  • ConfigurableBeanFactory,可获取 BeanPostProcessor、BeanClassLoader等的一个配置化接口。
  • ConfigurableListableBeanFactory,提供分析和修改Bean以及预先实例化的操作接口,不过目前只有一个 getBeanDefinition 方法。

解析#

1、回顾与计划

① 之前在测试类中将UserService、uId、UserDao组装为PropertyValue、PropertyValues、BeanDefinition并注入BeanDefinition容器后,再进行后续getBean操作的

② 希望将上述步骤改为通过配置文件操作,与代码解耦,且便于维护

③ Spring框架是通过解析classpath.xml获取配置信息

2、项目更新 - 资源

① +Source:定义获取InputStream流的接口

② +ClassPathResource:ClassUtils.getDefaultClassLoader().getResourceAsStream(path)

③ +FileSystemResource:new FileInputStream(new File(path))

④ +UrlResource:url.openConnection().getInputStream()

3、项目更新 - 资源加载

① +ResourceLoader:定义获取资源的接口

② +DefaultResourceLoader:实现接口,按照location判断并执行获取Resource

4、项目更新 - 资源解析

① +BeanDefinitionReader:定义获取BeanDefinition注册器、资源加载器、加载BeanDefinition的接口

② +AbstractBeanDefinitionReader:通过构造函数的形式,让调用方传入BeanDefinition注册器、资源加载器(目前资源加载器是通过new的方式实例化的),并对外提供BeanDefinition注册器、资源加载器

③ +XmlBeanDefinitionReader:继承AbstractBeanDefinitionReader,调用资源加载器加载Resource,从Resource中获取InputStream,解析InputStream,将属性封装成PropertyValues,将Class封装成BeanDefinition并注入BeanDefinition容器

5、调用流程

① 自定义spring.xml配置UserService、uId、UserDao

② 实例化DefaultListableBeanFactory,得到了向BeanDefinition容器注册数据的功能

③ 实例化XmlBeanDefinitionReader,又得到了资源加载的功能

④ 使用资源解析器解析classpath.xml,得到Resource,再从Resource中获取InputStream

⑤ 解析InputStream,将属性封装成PropertyValues,存入将Class、PropertyValue存入BeanDefinition,将BeanDefinition注入BeanDefinition容器

⑥ 调用getBean方法,可以正常获取BeanDefinition并实例化Class

测试#

image-20231130192333780

image-20231130192315101

资源类#

设计#

Resource

public interface Resource {
InputStream getInputStream() throws IOException;
}
package org.example.core.io;
import cn.hutool.core.lang.Assert;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
public class UrlResource implements Resource{
private final URL url;
public UrlResource(URL url) {
Assert.notNull(url,"URL must not be null");
this.url = url;
}
@Override
public InputStream getInputStream() throws IOException {
URLConnection con = this.url.openConnection();
try {
return con.getInputStream();
}
catch (IOException ex){
if (con instanceof HttpURLConnection){
((HttpURLConnection) con).disconnect();
}
throw ex;
}
}
}
package org.example.core.io;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class FileSystemResource implements Resource {
private final File file;
private final String path;
public FileSystemResource(File file) {
this.file = file;
this.path = file.getPath();
}
public FileSystemResource(String path) {
this.file = new File(path);
this.path = path;
}
@Override
public InputStream getInputStream() throws IOException {
return Files.newInputStream(this.file.toPath());
}
public final String getPath() {
return this.path;
}
}
package org.example.core.io;
import cn.hutool.core.lang.Assert;
import org.example.util.ClassUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class ClassPathResource implements Resource {
private final String path;
private ClassLoader classLoader;
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
this.path = path;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
@Override
public InputStream getInputStream() throws IOException {
InputStream is = classLoader.getResourceAsStream(path);
if (is == null) {
throw new FileNotFoundException(
this.path + " cannot be opened because it does not exist");
}
return is;
}
}

测试#

package org.example.test;
import cn.hutool.core.io.IoUtil;
import org.example.core.io.DefaultResourceLoader;
import org.example.core.io.Resource;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class ResourceTest {
private DefaultResourceLoader resourceLoader;
@Before
public void init() {
resourceLoader = new DefaultResourceLoader();
}
@Test
public void test_classpath() throws IOException {
Resource resource = resourceLoader.getResource("classpath:important.properties");
InputStream inputStream = resource.getInputStream();
String content = IoUtil.readUtf8(inputStream);
System.out.println(content);
}
@Test
public void test_file() throws IOException {
Resource resource = resourceLoader.getResource("src/test/java/resources/important.properties");
InputStream inputStream = resource.getInputStream();
String content = IoUtil.readUtf8(inputStream);
System.out.println(content);
}
@Test
public void test_url() throws IOException {
// Resource resource = resourceLoader.getResource("https://github.com/fuzhengwei/small-spring/important.properties");
Resource resource = resourceLoader.getResource("https://github.com/fuzhengwei/small-spring/blob/main/important.properties");
InputStream inputStream = resource.getInputStream();
String content = IoUtil.readUtf8(inputStream);
System.out.println(content);
}
}

第 7 章:所向披靡,实现应用上下文,自动识别、资源加载、扩展机制#

正确理解应用上下文#

当我们谈到”应用上下文”时,你可以把它想象成一个大管家,管理着整个应用程序中的各种组件和资源。这个”管家”知道哪些类、对象、服务在应用中,它们之间的关系是什么,以及如何创建它们。

可以把应用上下文比作一个大的容器,里面装着你整个应用程序所需的各种东西。当你需要某个组件(比如一个服务、一个对象),你可以向这个容器(应用上下文)发出请求,然后它会为你提供需要的东西。

这个”管家”的作用就是帮你处理应用中各个部分的关系,让它们能够协同工作而不需要你亲自操心。这样一来,你的代码更加模块化、可维护,因为你不必亲自管理每个组件之间的关系和创建过程。

在Spring框架中,应用上下文是一个重要的概念,它实现了控制反转和依赖注入,使得整个应用程序的组件能够更加灵活、可扩展。因此,理解应用上下文就是理解整个应用是如何被组织和管理的,以及如何实现了一些重要的设计原则,如松耦合和模块化。

二、目标#

如果你在自己的实际工作中开发过基于 Spring 的技术组件,或者学习过关于 SpringBoot 中间件设计和开发 (opens new window)等内容。那么你一定会继承或者实现了 Spring 对外暴露的类或接口,在接口的实现中获取了 BeanFactory 以及 Bean 对象的获取等内容,并对这些内容做一些操作,例如:修改 Bean 的信息,添加日志打印、处理数据库路由对数据源的切换、给 RPC 服务连接注册中心等。

在对容器中 Bean 的实例化过程添加扩展机制的同时,还需要把目前关于 Spring.xml 初始化和加载策略进行优化,因为我们不太可能让面向 Spring 本身开发的 DefaultListableBeanFactory 服务,直接给予用户使用。修改点如下:

img

  • DefaultListableBeanFactory、XmlBeanDefinitionReader,是我们在目前 Spring 框架中对于服务功能测试的使用方式,它能很好的体现出 Spring 是如何对 xml 加载以及注册Bean对象的操作过程,但这种方式是面向 Spring 本身的,还不具备一定的扩展性。
  • 就像我们现在需要提供出一个可以在 Bean 初始化过程中,完成对 Bean 对象的扩展时,就很难做到自动化处理。所以我们要把 Bean 对象扩展机制功能和对 Spring 框架上下文的包装融合起来,对外提供完整的服务。

三、设计#

为了能满足于在 Bean 对象从注册到实例化的过程中执行用户的自定义操作,就需要在 Bean 的定义和初始化过程中插入接口类,这个接口再有外部去实现自己需要的服务。那么在结合对 Spring 框架上下文的处理能力,就可以满足我们的目标需求了。整体设计结构如下图:

img

  • 满足于对 Bean 对象扩展的两个接口,其实也是 Spring 框架中非常具有重量级的两个接口:BeanFactoryPostProcessorBeanPostProcessor,也几乎是大家在使用 Spring 框架额外新增开发自己组建需求的两个必备接口。
  • BeanFactoryPostProcessor(bean工场后置处理器),是由 Spring 框架组建提供的容器扩展机制,允许在 Bean 对象注册后但未实例化之前(记忆技巧:既然没有实例化那么就是属于工场的),对 Bean 的定义信息 BeanDefinition 执行修改操作。
  • BeanPostProcessor,也是 Spring 提供的扩展机制,不过 BeanPostProcessor 是在 Bean 对象实例化之后修改 Bean 对象(记忆技巧:实例化以后就是bean后置处理器),也可以替换 Bean 对象。这部分与后面要实现的 AOP 有着密切的关系。
  • 同时如果只是添加这两个接口,不做任何包装,那么对于使用者来说还是非常麻烦的。我们希望于开发 Spring 的上下文操作类,把相应的 XML 加载 、注册、实例化以及新增的修改和扩展都融合进去,让 Spring 可以自动扫描到我们的新增服务,便于用户使用。

四、实现#

1. 工程结构#

small-spring-step-06
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── UserDao.java
│ └── UserService.java
├── common
│ ├── MyBeanFactoryPostProcessor.java
│ └── MyBeanPostProcessor.java
└── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring 应用上下文和对Bean对象扩展机制的类关系,如图 7-3

图 7-3

  • 在整个类图中主要体现出来的是关于 Spring 应用上下文以及对 Bean 对象扩展机制的实现。
  • 以继承了 ListableBeanFactory 接口的 ApplicationContext 接口开始,扩展出一系列应用上下文的抽象实现类,并最终完成 ClassPathXmlApplicationContext 类的实现。而这个类就是最后交给用户使用的类。
  • 同时在实现应用上下文的过程中,通过定义接口:BeanFactoryPostProcessorBeanPostProcessor 两个接口,把关于对 Bean 的扩展机制串联进去了。

收获#

refresh方法#

@Override
public void refresh() throws BeansException {
// 1. 创建 BeanFactory,并加载 BeanDefinition
refreshBeanFactory();
// 2. 获取 BeanFactory
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 3. 在 Bean 实例化之前,执行 BeanFactoryPostProcessor (Invoke factory processors registered as beans in the context.)
invokeBeanFactoryPostProcessors(beanFactory);
// 4. BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作
//因为bean后置处理器是用于修改实例化对象后的操作,所以必须得提前于实例化对象前进行注册。
registerBeanPostProcessors(beanFactory);
// 5. 提前实例化单例Bean对象
beanFactory.preInstantiateSingletons();
}
  1. refreshBeanFactory()

    • 创建 BeanFactory
    • 加载 BeanDefinition(在Bean 对象注册后但未实例化),也就是说加载bean的定义到spring的BeanDefinition集合中
  2. beanFactory.preInstantiateSingletons();

    • 提前实例化单例Bean对象,回去调用doGetBean中的createBean方法并实例化对象

故前文的两个扩展点:

  • BeanFactoryPostProcessor,是由 Spring 框架组建提供的容器扩展机制,允许在 Bean 对象注册后但未实例化之前,对 Bean 的定义信息 BeanDefinition 执行修改操作。发生在refreshBeanFactory()方法后。

  • BeanPostProcessor,也是 Spring 提供的扩展机制,不过 BeanPostProcessor 是在 Bean 对象实例化之后修改 Bean 对象,也可以替换 Bean 对象。这部分与后面要实现的 AOP 有着密切的关系。发生在createBean中的实例化对象后。

维护了两个后置处理集合#

  • Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap

作用:在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,提供修改 BeanDefinition 属性的机制。

该集合由DefaultListableBeanFactory类,通过getBeansOfType组装而成。

@Override
public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
Map<String, T> result = new HashMap<>();
beanDefinitionMap.forEach((beanName, beanDefinition) -> {
Class beanClass = beanDefinition.getBeanClass();
if (type.isAssignableFrom(beanClass)) {
result.put(beanName, (T) getBean(beanName));
}
});
return result;
}

调用时机在

image-20231120181329982

private void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
//组装
Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);
//遍历
for (BeanFactoryPostProcessor beanFactoryPostProcessor : beanFactoryPostProcessorMap.values()) {
//执行
beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
}
}
  • beanPostProcessors

    执行时机initializeBean,也就是对象实例化后

    image-20231120181530646

    private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) {
    // 1. 执行 BeanPostProcessor Before 处理
    //遍历beanPostProcessors 然后执行postProcessBeforeInitialization方法,简称before方法
    Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
    // 执行 Bean 对象的初始化方法
    try {
    invokeInitMethods(beanName, wrappedBean, beanDefinition);
    } catch (Exception e) {
    throw new BeansException("Invocation of init method of bean[" + beanName + "] failed", e);
    }
    // 2. 执行 BeanPostProcessor After 处理
    //遍历beanPostProcessors 然后执行postProcessAfterInitialization,简称After方法
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    return wrappedBean;
    }

第 8 章:龙行有风,向虚拟机注册钩子,实现Bean对象的初始化和销毁方法#

二、目标#

当我们的类创建的 Bean 对象,交给 Spring 容器管理以后,这个类对象就可以被赋予更多的使用能力。就像我们在上一章节已经给类对象添加了修改注册Bean定义未实例化前的属性信息修改和初始化过程中的前置和后置处理,这些额外能力的实现,都可以让我们对现有工程中的类对象做相应的扩展处理。

那么除此之外我们还希望可以在 Bean 初始化过程,执行一些操作。比如帮我们做一些数据的加载执行,链接注册中心暴露RPC接口以及在Web程序关闭时执行链接断开,内存销毁等操作。如果说没有Spring我们也可以通过构造函数、静态方法以及手动调用的方式实现,但这样的处理方式终究不如把诸如此类的操作都交给 Spring 容器来管理更加合适。 因此你会看到到 spring.xml 中有如下操作:

img

  • 需要满足用户可以在 xml 中配置初始化和销毁的方法,也可以通过实现类的方式处理,比如我们在使用 Spring 时用到的 InitializingBean, DisposableBean 两个接口。 -其实还可以有一种是注解的方式处理初始化操作,不过目前还没有实现到注解的逻辑,后续再完善此类功能。

三、设计#

可能面对像 Spring 这样庞大的框架,对外暴露的接口定义使用或者xml配置,完成的一系列扩展性操作,都让 Spring 框架看上去很神秘。其实对于这样在 Bean 容器初始化过程中额外添加的处理操作,无非就是预先执行了一个定义好的接口方法或者是反射调用类中xml中配置的方法,最终你只要按照接口定义实现,就会有 Spring 容器在处理的过程中进行调用而已。整体设计结构如下图:

img

  • 在 spring.xml 配置中添加 init-method、destroy-method 两个注解,在配置文件加载的过程中,把注解配置一并定义到 BeanDefinition 的属性当中。这样在 initializeBean 初始化操作的工程中,就可以通过反射的方式来调用配置在 Bean 定义属性当中的方法信息了。另外如果是接口实现的方式,那么直接可以通过 Bean 对象调用对应接口定义的方法即可,((InitializingBean) bean).afterPropertiesSet(),两种方式达到的效果是一样的。
  • 除了在初始化做的操作外,destroy-methodDisposableBean 接口的定义,都会在 Bean 对象初始化完成阶段,执行注册销毁方法的信息到 DefaultSingletonBeanRegistry 类中的 disposableBeans 属性里,这是为了后续统一进行操作。这里还有一段适配器的使用,因为反射调用和接口直接调用,是两种方式。所以需要使用适配器进行包装,下文代码讲解中参考 DisposableBeanAdapter 的具体实现 -关于销毁方法需要在虚拟机执行关闭之前进行操作,所以这里需要用到一个注册钩子的操作,如:Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!"))); 这段代码你可以执行测试,另外你可以使用手动调用 ApplicationContext.close 方法关闭容器。

四、实现#

#1. 工程结构#

small-spring-step-07
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── UserDao.java
│ └── UserService.java
└── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring 应用上下文和对Bean对象扩展机制的类关系,如图 8-4

图 8-4

  • 以上整个类图结构描述出来的就是本次新增 Bean 实例化过程中的初始化方法和销毁方法。
  • 因为我们一共实现了两种方式的初始化和销毁方法,xml配置和定义接口,所以这里既有 InitializingBean、DisposableBean 也有需要 XmlBeanDefinitionReader 加载 spring.xml 配置信息到 BeanDefinition 中。
  • 另外接口 ConfigurableBeanFactory 定义了 destroySingletons 销毁方法,并由 AbstractBeanFactory 继承的父类 DefaultSingletonBeanRegistry 实现 ConfigurableBeanFactory 接口定义的 destroySingletons 方法。这种方式的设计可能数程序员是没有用过的,都是用的谁实现接口谁完成实现类,而不是把实现接口的操作又交给继承的父类处理。所以这块还是蛮有意思的,是一种不错的隔离分层服务的设计方式
  • 最后就是关于向虚拟机注册钩子,保证在虚拟机关闭之前,执行销毁操作。Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("close!")));

解析#

1、学习目标:

定义、执行初始化和销毁方法

2、回顾和计划

① 上一章实现了应用上下文,概括为:以容器基础功能为前提,扩展了容器的使用方式,使Spring容器能够对外提供完整服务

② 上一章为Class类、Bean对象提供了低耦合的更新操作,即Class类的属性更新、Bean对象初始化过程中的前置和后置处理

③ 本节还希望Bean对象初始化过程中能够完成一些例如 初始数据加载、链接注册中心暴露RPC接口、程序关闭时断开链接、销毁内存数据等操作

④ 因此可以在spring.xml中配置或者通过实现类,实现外围功能的嵌入

3、项目新增

① +InitializingBean:定义接口方法afterPropertiesSet,实现类可以在方法中实现参数的初始化,比如接口暴露、数据库数据读取、配置文件加载等,该方法在Bean属性填充后调用

② +DisposableBean:定义销毁方法

③ +DisposableBeanAdapter:实现了②接口中的销毁方法,该销毁方法提供了两种逻辑 Ⅰ 调用实现了DisposableBean的类的销毁方法 Ⅱ (在spring.xml中定义destroy-method属性,资源解析使将其属性值存入beanDefinition)从beanDefinition中取出destroy方法,利用反射机制method.invoke执行

4、项目更新

① BeanDefinition:定义initMethodName、destroyMethodName,解析spring.xml中的init-method、destroy-method存至这两个属性

② XmlBeanDefinitionReader:解析spring.xml,将init-method、destroy-method存入beanDefinition

③ AbstractAutowireCapableBeanFactory:获取实例化bean之后,执行 Bean 对象的初始化方法,初始化方法可以是InitializingBean实现类中的初始化方法,也可以是spring.xml中init-method注册的方法

④ ConfigurableBeanFactory:定义销毁单例容器disposableBeans的方法destroySingletons

⑤ DefaultSingletonBeanRegistry:实现destroySingletons,遍历移除disposableBeans容器中的元素DisposableBean,每次移除后调用DisposableBean的destroy方法,方法在下面有描述

⑥ ConfigurableApplicationContext:定义registerShutdownHook、close方法

⑦ AbstractApplicationContext:实现⑥定义的接口,即定义Runtime.getRuntime().addShutdownHook方法,该方法会开启新线程,在JVM关闭前调用自定义的close方法,close方法会调用⑤中的销毁方法

5、执行流程

① spring.xml的UserDao中定义init-method=“initDataMethod”、destroy-method=“destroyDataMethod”

② 让UserService实现InitializingBean,、DisposableBean接口,则需要实现了初始化和销毁方法,① ②相当于提供了两种初始化、销毁的方法

③ 在资源解析时将init-method、destroy-method注入beanDefinition

④ 实例化Class类后,初始化Bean对象时,调用①、②中定义的初始化方法

⑤ 定义ShutdownHook,在JVM销毁前执行自定义的线程,完成①、②中定义的销毁方法

6、一些想法

① DefaultSingletonBeanRegistry就专门负责底层容器的定义,以及对外提供存取、销毁方法,极好地体现了职责分明

② 只要实现了InitializingBean,、DisposableBean,就可自定义初始、销毁方法,使得方法的调用与定义解耦,极大提升了扩展性

③ 本章是在bean初始化时的的invokeInitMethods方法中,调用上面定义的初始化方法,上一章是在invokeInitMethods方法的前后通过注入的BeanPostProcessor实现类执行初始化方法,感觉目的是一样的,只是实现方式有差异

第 9 章:虎行有雨,定义标记类型Aware接口,实现感知容器对象#

二、目标#

目前已实现的 Spring 框架,在 Bean 操作上能提供出的能力,包括:Bean 对象的定义和注册,以及在操作 Bean 对象过程中执行的,BeanFactoryPostProcessor、BeanPostProcessor、InitializingBean、DisposableBean,以及在 XML 新增的一些配置处理,让我们可以 Bean 对象有更强的操作性。

那么,如果我们想获得 Spring 框架提供的 BeanFactory、ApplicationContext、BeanClassLoader等这些能力做一些扩展框架的使用时该怎么操作呢。所以我们本章节希望在 Spring 框架中提供一种能感知容器操作的接口,如果谁实现了这样的一个接口,就可以获取接口入参中的各类能力。

三、设计#

如果说我希望拿到 Spring 框架中一些提供的资源,那么首先需要考虑以一个什么方式去获取,之后你定义出来的获取方式,在 Spring 框架中该怎么去承接,实现了这两项内容,就可以扩展出你需要的一些属于 Spring 框架本身的能力了。

在关于 Bean 对象实例化阶段我们操作过一些额外定义、属性、初始化和销毁的操作,其实我们如果像获取 Spring 一些如 BeanFactory、ApplicationContext 时,也可以通过此类方式进行实现。那么我们需要定义一个标记性的接口,这个接口不需要有方法,它只起到标记作用就可以,而具体的功能由继承此接口的其他功能性接口定义具体方法,最终这个接口就可以通过 instanceof 进行判断和调用了。整体设计结构如下图:

img

  • 定义接口 Aware,在 Spring 框架中它是一种感知标记性接口,具体的子类定义和实现能感知容器中的相关对象。也就是通过这个桥梁,向具体的实现类中提供容器服务
  • 继承 Aware 的接口包括:BeanFactoryAware、BeanClassLoaderAware、BeanNameAware和ApplicationContextAware,当然在 Spring 源码中还有一些其他关于注解的,不过目前我们还是用不到。
  • 在具体的接口实现过程中你可以看到,一部分(BeanFactoryAware、BeanClassLoaderAware、BeanNameAware)在 factory 的 support 文件夹下,另外 ApplicationContextAware 是在 context 的 support 中,这是因为不同的内容获取需要在不同的包下提供。所以,在 AbstractApplicationContext 的具体实现中会用到向 beanFactory 添加 BeanPostProcessor 内容的 ApplicationContextAwareProcessor 操作,最后由 AbstractAutowireCapableBeanFactory 创建 createBean 时处理相应的调用操作。关于 applyBeanPostProcessorsBeforeInitialization 已经在前面章节中实现过,如果忘记可以往前翻翻

四、实现#

1. 工程结构#

small-spring-step-08
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── UserDao.java
│ └── UserService.java
└── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

Spring 感知接口的设计和实现类关系,如图 9-2

图 9-2

  • 以上整个类关系就是关于 Aware 感知的定义和对容器感知的实现。
  • Aware 有四个继承的接口,其他这些接口的继承都是为了继承一个标记,有了标记的存在更方便类的操作和具体判断实现。
  • 另外由于 ApplicationContext 并不是在 AbstractAutowireCapableBeanFactory 中 createBean 方法下的内容,所以需要像容器中注册 addBeanPostProcessor ,再由 createBean 统一调用 applyBeanPostProcessorsBeforeInitialization 时进行操作。

解析#

1、学习目标:Aware感知容器对象

2、回顾与计划

① 上一章通过两种方式,定义、执行初始化方法和初始化方法

② 目前Spring框架已经具备了Bean对象定义、实例化、初始化,通过spring.xml新增配置处理等操作,

③ 本章希望能够获取Spring容器的一些能力,例如BeanFactory、ApplicationContext、BeanClassLoader,这样就可以扩展框架的使用 ④ 所以本章引入Aware及其子接口,来感知、获取Spring内部对象

3、项目更新

① +Aware:标记类接口(类似标签),实现该接口可以被Spring容器感知

② +BeanClassLoaderAware:实现此接口,既能感知到所属的 ClassLoader

③ +BeanFactoryAware:实现此类,即能感知到所属的BeanFactory

④ +BeanNameAware:实现此接口,既能感知到所属的 BeanName

⑤ +ApplicationContextAware:实现此接口,既能感知到所属的 ApplicationContext

⑥ +ApplicationContextAwareProcessor:继承BeanPostProcessor,那么在Bean初始化时就会调用该类中定义的postProcessBeforeInitialization方法给Bean对象赋applicationContext

⑦ AbstractApplicationContext:在refresh中调用beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)),将⑥新增的类实例化至beanPostProcessors容器

⑧ AbstractAutowireCapableBeanFactory:在initializeBean方法中执行invokeAwareMethods,即判断本次实例化的Bean实现哪些Aware的子接口,调用setXXX将将Spring内部对象注入Bean中

⑨ AbstractBeanFactory:定义、实例化ClassLoader,并对外提供获取方法getBeanClassLoader

4、Aware感知原理

① Aware 接口的作用是在业务代码中获得 Spring 框架内部对象

② BeanClassLoaderAware、BeanFactoryAware、BeanNameAware的实现原理:Spring在Bean初始化过程中,执行invokeAwareMethods判断Bean实现的Aware子接口的类型,调用setXXX方法将Spring内部对象注入到Bean中

③ 除了上面的Aware子接口,还有一部分Aware子接口如ApplicationContextAware等的实现原理:涉及到第7章定义的BeanPostProcessor后置处理器,其作用是在Bean实例化以及属性填充之后,在执行完invokeAwareMethods之后,Spring会获取当前Bean容器中注册的所有BeanPostProcessor实现类的对象(是在spring.xml中定义,上下文refresh时注入beanPostProcessors容器的),利用invoke反射执行其中的逻辑

④ 负责Aware逻辑的BeanPostProcessor是ApplicationContextAwareProcessor(就可以感知上下文),该类是在上下文refresh时通过new ApplicationContextAwareProcessor(AbstractApplicationContext对象)的方式注入beanPostProcessors容器的

⑤ ApplicationContextAwareProcessor的postProcessBeforeInitialization方法中调用setXXX将applicationContext注入Bean对象

⑥ 第7章的BeanPostProcessor的实现类是定义在spring.xml的标签中,refresh时通过registerBeanPostProcessors注入beanPostProcessors容器的,

5、总结

① 虽然Aware子类生效方式和位置不同,但本质都是在Bean初始化过程中,通过instance判断Bean实现的Aware子接口的类型,调用setXXX方法将Spring内部对象注入Bean中

② 以下一些Aware子接口也是通过BeanPostProcessor感知容器对象:EnvironmentAware、EmbeddedValueResolverAware、ResourceLoaderAware、ApplicationEventPublisherAware、MessageSourceAware、ApplicationStartupAware

③ 这种扩展模式将业务代码和框架功能解耦,本质上就是自定义Bean后置处理器,在框架类代码中可以用来暴露自己的内部成员

疑问#

究竟如何感知#

执行点#

执行感知点:AbstractAutowireCapableBeanFactory类中

此处只有三个BeanFactoryAware、BeanClassLoaderAware、BeanNameAware感知

private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) {
// invokeAwareMethods
if (bean instanceof Aware) {
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(this);
}
if (bean instanceof BeanClassLoaderAware){
((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
}
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
}

ApplicationContextAware的执行点设置在BeanPostProcessor中,在createBean对象后遍历beanPostProcessors并执行before方法

由于该ApplicationContextAware比较特殊,故埋点的位置在AbstractApplicationContext类中refresh();

image-20231120183615493

public class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ApplicationContext applicationContext;
public ApplicationContextAwareProcessor(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ApplicationContextAware){
((ApplicationContextAware) bean).setApplicationContext(applicationContext);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
埋点#

UserService实现感知接口

public class UserService implements BeanNameAware, BeanClassLoaderAware, ApplicationContextAware, BeanFactoryAware {
private ApplicationContext applicationContext;
private BeanFactory beanFactory;
private String uId;
private String company;
private String location;
private UserDao userDao;
//1工厂
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
//2上下文
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
//3bean名称
@Override
public void setBeanName(String name) {
System.out.println("Bean Name is:" + name);
}
//4bean的类加载器
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("ClassLoader:" + classLoader);
}
public String queryUserInfo() {
return userDao.queryUserName(uId) + "," + company + "," + location;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public BeanFactory getBeanFactory() {
return beanFactory;
}
}

测试时调用

public class ApiTest {
@Test
public void test_xml() {
// 1.初始化 BeanFactory
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
applicationContext.registerShutdownHook();
// 2. 获取Bean对象调用方法
UserService userService = applicationContext.getBean("userService", UserService.class);
String result = userService.queryUserInfo();
System.out.println("测试结果:" + result);
System.out.println("ApplicationContextAware:"+userService.getApplicationContext());
System.out.println("BeanFactoryAware:"+userService.getBeanFactory());
}
}

第 10 章:横刀跃马,关于Bean对象作用域以及FactoryBean的实现和使用#

二、目标#

交给 Spring 管理的 Bean 对象,一定就是我们用类创建出来的 Bean 吗?创建出来的 Bean 就永远是单例的吗,没有可能是原型模式吗?

在集合 Spring 框架下,我们使用的 MyBatis 框架中,它的核心作用是可以满足用户不需要实现 Dao 接口类,就可以通过 xml 或者注解配置的方式完成对数据库执行 CRUD 操作,那么在实现这样的 ORM 框架中,是怎么把一个数据库操作的 Bean 对象交给 Spring 管理的呢。

因为我们在使用 Spring、MyBatis 框架的时候都可以知道,并没有手动的去创建任何操作数据库的 Bean 对象,有的仅仅是一个接口定义,而这个接口定义竟然可以被注入到其他需要使用 Dao 的属性中去了,那么这一过程最核心待解决的问题,就是需要完成把复杂且以代理方式动态变化的对象,注册到 Spring 容器中。而为了满足这样的一个扩展组件开发的需求,就需要我们在现有手写的 Spring 框架中,添加这一能力。

三、方案#

关于提供一个能让使用者定义复杂的 Bean 对象,功能点非常不错,意义也非常大,因为这样做了之后 Spring 的生态种子孵化箱就此提供了,谁家的框架都可以在此标准上完成自己服务的接入。

但这样的功能逻辑设计上并不复杂,因为整个 Spring 框架在开发的过程中就已经提供了各项扩展能力的接茬,你只需要在合适的位置提供一个接茬的处理接口调用和相应的功能逻辑实现即可,像这里的目标实现就是对外提供一个可以二次从 FactoryBean 的 getObject 方法中获取对象的功能即可,这样所有实现此接口的对象类,就可以扩充自己的对象功能了。MyBatis 就是实现了一个 MapperFactoryBean 类,在 getObject 方法中提供 SqlSession 对执行 CRUD 方法的操作 整体设计结构如下图:

img

  • 整个的实现过程包括了两部分,一个解决单例还是原型对象,另外一个处理 FactoryBean 类型对象创建过程中关于获取具体调用对象的 getObject 操作。
  • SCOPE_SINGLETONSCOPE_PROTOTYPE,对象类型的创建获取方式,主要区分在于 AbstractAutowireCapableBeanFactory#createBean 创建完成对象后是否放入到内存中,如果不放入则每次获取都会重新创建。
  • createBean 执行对象创建、属性填充、依赖加载、前置后置处理、初始化等操作后,就要开始做执行判断整个对象是否是一个 FactoryBean 对象,如果是这样的对象,就需要再继续执行获取 FactoryBean 具体对象中的 getObject 对象了。整个 getBean 过程中都会新增一个单例类型的判断factory.isSingleton(),用于决定是否使用内存存放对象信息

解析#

1、学习目标:

对象作用域和BeanFactory

2、本章目标

① 实际业务中不仅需要单例Bean对象,也需要原型模式的Bean对象

② 因此本章增加了对Bean对象作用域的定义、管理

③ 交由Spring管理的Bean对象,不一定都是由类创建出来的

③ 本章就是要把复杂且以代理方式动态变化的bean对象,注入Spring容器

④ 对外提供一个可以二次从 FactoryBean 的 getObject方法中获取对象的功能即可

3、工程结构调整

① +FactoryBean:接口中定义getObject方法,实现了该方法的类,可以自定义对象功能

② +IUserDao:测试使用,定义了一个查询接口

③ +ProxyBeanFactory:测试使用,实现FactoryBean中的getObject,模拟了 UserDao 的原有功能,类似于 MyBatis 框架中的代理操作

④ BeanDefinition:新增scope属性,默认为singleton,定义布尔类型的singleton、prototype,解析spring.xml中的scope标签值如果是singleton,则属性singleton=true,prototype=false

⑤ XmlBeanDefinitionReader:读取spring.xml中的scope属性注入beanDefinition

⑥ AbstractAutowireCapableBeanFactory:创建Bean对象初始化结束后,根据beanDefinition中的singleton属性判断是否要将Bean对象存入单例Bean对象容器;创建Bean对象时,也无需将非singleton类型的Bean对象注入disposableBeans容器,因为Singleton对象由Spring容器销毁,Prototype对象由JVM销毁

⑦ DefaultSingletonBeanRegistry:定义了一个NULL_OBJECT

⑧ +FactoryBeanRegistrySupport:定义factoryBeanObjectCache用来存放单例类型的对象,避免重复创建,对外提供获取容器对象的方法。取Bean对象分两种情况,

1)如果实现了FactoryBean接口的Bean对象是单例的,则从factoryBeanObjectCache单例缓存池中取,如果缓存池中没有,则调用getObject方法创建对象,并会在缓存中也保存一下,以备下次使用

2)如果Bean不是单例对象,则直接调用getObject方法创建新对象

⑨ AbstractBeanFactory:doGetBean方法的逻辑有较大调整,先从单例Bean对象容器中取Bean对象,如果取不到,则创建Bean对象,然后判断Bean对象是否是FactoryBean的实例或子类实例,如果不是,直接给用户返回Bean对象;如果是,则调用FactoryBeanRegistrySupport类中的方法,即获取单例Bean对象或创建新的Bean对象

4、什么是对象作用域?

① Spring中可以定义有状态的Bean对象作用域为singleton

② 有状态会话bean:每个用户私有一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。

③ Spring中可以定义无状态的Bean对象作用域为prototype

④ 无状态会话bean:bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。

5、什么是FactoryBean?

① FactoryBean是接口,其在IOC容器的基础上给实例化Bean对象添加了简单工厂模式和装饰器模式,用户可以在getObject方法中自定义实例化Bean,为IOC容器中复杂Bean的实例化提供了更加灵活的方式

② 一般情况下,Spring通过反射机制利用的class属性指定实例化Bean,但某些情况下,实例化Bean的过程比较复杂,如果按照原始方式,需要在中提供大量的配置信息,不够灵活,这时采用编码方式更简单,因此Spring提供了FactoryBean的工厂类接口,用户可以通过实现该接口,自定义实例化Bean的逻辑

③ 当spring.xml的的class配置的是FactoryBean的实现类时,通过getBean()方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()得到的Bean对象,替代了createBean()所得到的Bean对象

④ 如果bean实例不是FactoryBean类型,则直接返回bean实例;如果bean实例是FactoryBean类型,而beanName又是以&开头【beanFactory.getBean(“&user”)】,直接返回bean实例;如果bean实例是FactoryBean类型,而beanName不是以&开头,则返回bean实例的getObject()方法获取的对象实例(一般getObject()方法中就是我们需要的实例对象的创建过程)

6、参考 #BeanFactory 简介以及它 和FactoryBean的区别(阿里面试) - aspirant… #Spring拓展接口之FactoryBean,我们来看看其源码实现 - 青石路 - 博客园

收获#

AbstractBeanFactory类中doGetBean方法中:

image-20231121151820451

  • 此处新增加的功能主要是在 doGetBean 方法中,添加了调用 (T) getObjectForBeanInstance(sharedInstance, name) 对获取 FactoryBean 的操作。
  • 在 getObjectForBeanInstance 方法中做具体的 instanceof 判断,另外还会从 FactoryBean 的缓存中获取对象,如果不存在则调用 FactoryBeanRegistrySupport#getObjectFromFactoryBean,执行具体的操作。
  • getObjectFromFactoryBean方法中是由调用getObject方法的
protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) {
if (factory.isSingleton()) {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
}
return (object != NULL_OBJECT ? object : null);
} else {
return doGetObjectFromFactoryBean(factory, beanName);
}
}
private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName){
try {
//此处调用getObject
return factory.getObject();
} catch (Exception e) {
throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e);
}
}
protected <T> T doGetBean(final String name, final Object[] args) {
Object sharedInstance = getSingleton(name);
if (sharedInstance != null) {
// 如果是 FactoryBean,则需要调用 FactoryBean#getObject
return (T) getObjectForBeanInstance(sharedInstance, name);
}
BeanDefinition beanDefinition = getBeanDefinition(name);
Object bean = createBean(name, beanDefinition, args);
return (T) getObjectForBeanInstance(bean, name);
}
private Object getObjectForBeanInstance(Object beanInstance, String beanName) {
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
Object object = getCachedObjectForFactoryBean(beanName);
if (object == null) {
FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;
object = getObjectFromFactoryBean(factoryBean, beanName);
}
return object;
}

第 11 章:更上层楼,基于观察者实现,容器事件和事件监听器#

二、目标#

在 Spring 中有一个 Event 事件功能,它可以提供事件的定义、发布以及监听事件来完成一些自定义的动作。比如你可以定义一个新用户注册的事件,当有用户执行注册完成后,在事件监听中给用户发送一些优惠券和短信提醒,这样的操作就可以把属于基本功能的注册和对应的策略服务分开,降低系统的耦合。以后在扩展注册服务,比如需要添加风控策略、添加实名认证、判断用户属性等都不会影响到依赖注册成功后执行的动作。

那么在本章节我们需要以观察者模式的方式,设计和实现 Spring Event 的容器事件和事件监听器功能,最终可以让我们在现有实现的 Spring 框架中可以定义、监听和发布自己的事件信息。

三、方案#

其实事件的设计本身就是一种观察者模式的实现,它所要解决的就是一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

在功能实现上我们需要定义出事件类、事件监听、事件发布,而这些类的功能需要结合到 Spring 的 AbstractApplicationContext#refresh(),以便于处理事件初始化和注册事件监听器的操作。整体设计结构如下图:

img

  • 在整个功能实现过程中,仍然需要在面向用户的应用上下文 AbstractApplicationContext 中添加相关事件内容,包括:初始化事件发布者、注册事件监听器、发布容器刷新完成事件。
  • 使用观察者模式定义事件类、监听类、发布类,同时还需要完成一个广播器的功能,接收到事件推送时进行分析处理符合监听事件接受者感兴趣的事件,也就是使用 isAssignableFrom 进行判断。
  • isAssignableFrom 和 instanceof 相似,不过 isAssignableFrom 是用来判断子类和父类的关系的,或者接口的实现类和接口的关系的,默认所有的类的终极父类都是Object。如果A.isAssignableFrom(B)结果是true,证明B可以转换成为A,也就是A可以由B转换而来。

四、实现#

1. 工程结构#

small-spring-step-10
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── event
│ │ │ ├── AbstractApplicationEventMulticaster.java
│ │ │ ├── ApplicationContextEvent.java
│ │ │ ├── ApplicationEventMulticaster.java
│ │ │ ├── ContextClosedEvent.java
│ │ │ ├── ContextRefreshedEvent.java
│ │ │ └── SimpleApplicationEventMulticaster.java
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ ├── ApplicationEvent.java
│ │ ├── ApplicationEventPublisher.java
│ │ ├── ApplicationListener.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── event
│ ├── ContextClosedEventListener.java
│ ├── ContextRefreshedEventListener.java
│ ├── CustomEvent.java
│ └── CustomEventListener.java
└── ApiTest.java

工程源码公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

容器事件和事件监听器实现类关系,如图 11-2

图 10-2

  • 以上整个类关系图以围绕实现 event 事件定义、发布、监听功能实现和把事件的相关内容使用 AbstractApplicationContext#refresh 进行注册和处理操作。
  • 在实现的过程中主要以扩展 spring context 包为主,事件的实现也是在这个包下进行扩展的,当然也可以看出来目前所有的实现内容,仍然是以IOC为主。
  • ApplicationContext 容器继承事件发布功能接口 ApplicationEventPublisher,并在实现类中提供事件监听功能。
  • ApplicationEventMulticaster 接口是注册监听器和发布事件的广播器,提供添加、移除和发布事件方法。
  • 最后是发布容器关闭事件,这个仍然需要扩展到 AbstractApplicationContext#close 方法中,由注册到虚拟机的钩子实现。

1、学习目标:

容器事件和事件监听器

2、本章计划

① 在Spring框架中有Event事件功能,通过事件定义、发布、监听,来完成自定义的动作

② 例如Spring框架中的容器刷新事件,用于监听容器初始化时刷新是否成功

③ 在业务系统中可以定义用户注册事件,当用户注册完成,通过事件监听给用户发送短信通知、优惠券等

④ 又例如为注册服务添加实名认证(扩展注册服务),通过事件监听都不会影响到依赖注册成功后执行的动作

⑤ 引入事件监听,可以将基本业务功能与对应的策略服务分开,降低代码耦合性

3、项目扩展 - 事件

① +ApplicationEvent:定义抽象类继承EventObject,实例化时调用super(source),即实例化EventObject类,并将给成员变量source赋值,source就是上下文对象,包含beanFactory等资源

② +ApplicationContextEvent:定义应用上下文事件类,继承ApplicationEvent,实例化时给父类传入source(上下文对象)

③ +ContextClosedEvent:Spring框架自己实现的实现类,继承了ApplicationContextEvent,用于监听关闭动作,实例化时传入source(上下文对象,具备事件监听的功能)

④ +ContextRefreshedEvent:Spring框架自己实现的实现类,继承了ApplicationContextEvent,用于监听容器刷新动作,实例化时传入source(上下文对象,具备事件监听的功能)

⑤ +CustomEvent:自定义的事件,继承了ApplicationContextEvent,用于监听容器刷新动作,实例化时给自定义属性赋值,并传入source(上下文对象)

4、项目扩展 - 事件监听器

① +ApplicationListener:继承java.util.EventListener接口,定义onApplicationEvent(E event)事件处理方法,每个事件监听器都要实现该接口

② +CustomEventListener:实现ApplicationListener类,实现自定义的事件监听

③ +ContextClosedEventListener:Spring框架的容器关闭事件监听器,监听动作在容器关闭时执行

④ +ContextRefreshedEventListener:Spring框架的容器刷新事件监听器,监听动作在容器完成初始化刷新时执行

5、项目扩展 - 事件监听

① +ApplicationEventMulticaster:应用事件广播接口,

1)定义新增、删除监听器方法

2)定义事件广播方法multicastEvent(ApplicationEvent event)

② +AbstractApplicationEventMulticaster:应用事件广播抽象类,实现ApplicationEventMulticaster接口

1)定义applicationListeners应用监听器容器Map,并实现从容器中新增、删除监听器的方法

2)getApplicationListeners:将监听器Map容器中的监听器存入list

3)supportEvent:判断监听器(applicationListener)是否对事件(event)感兴趣

③ +SimpleApplicationEventMulticaster:简单的应用实践广播实现类,继承AbstractApplicationEventMulticaster

1)为父类提高beanFactory

2)实现ApplicationEventMulticaster中的multicastEvent(event)方法,该方法会调用事件对应的监听器,执 行onApplicationEvent(event)事件监听方法

④ ClassUtils:新增方法isCglibProxyClass(Class<?> clazz)判断Class的Bean对象是否是通过Cglib实例化的

6、项目扩展 - 发布、监听事件

① SingletonBeanRegistry:定义接口registerSingleton(beanName, singletonObject)

② DefaultSingletonBeanRegistry:实现registerSingleton接口,其实就是将Bean对象注入singletonObjects单例Bean对象容器

③ +ApplicationEventPublisher:定义事件发布接口,之后Spring内事件、自定义事件都通过该接口中的publishEvent(ApplicationEvent event)发布

④ ApplicationContext:接口继承ApplicationEventPublisher,使得应用上下文具备事件发布的能力

⑤ AbstractApplicationContext:在refresh()方法中完善事件发布、监听相关功能

1)initApplicationEventMulticaster():初始化事件发布者,即实例化SimpleApplicationEventMulticaster, 这样上下文就具备了SimpleApplicationEventMulticaster中定义的事件发布功能,并将实例化Bean对象applicationEventMulticaster 存入单例Bean对象容器singletonObjects,以供其他方法使用

2)registerListeners():获取所有实现了ApplicationListener接口的监听器类对象,例如CustomEventListener,调用applicationEventMulticaster.addApplicationListener(listener)将监听器Bean对象存入监听器池子中

3)finishRefresh():发布【容器刷新完成】事件,容器关闭事件已经其他自定义事件的发布也是调用这个方法,该方法的入参是对应的事件实例Bean对象,调用1)中applicationEventMulticaster.multicastEvent(event),该方法是从监听器池子中查询监听当前事件的监听器,调用listener.onApplicationEvent(event)即可监听事件,具体细节可以参考配图

4)在close()方法中发布容器关闭事件publishEvent(new ContextClosedEvent(this));

7、项目扩展

  • spring.xml 将事件监听器作为bean配置在spring.xml中

8、一些记录

① isAssignableFrom()方法与instanceof关键字的区别总结为以下两个点:

  • isAssignableFrom()方法是从类继承的角度去判断,

  • instanceof关键字是从实例继承的角度去判断。 isAssignableFrom()方法是判断是否为某个类的父类,instanceof关键字是判断是否某个类的子类。

② 观察者模式 当一个行为发生时,传递信息给另一个用户,执行相应的处理行为,这两个行为之间没有耦合关系,而是通过监听实现。例如 MQ 的消息生成与消费、总线监听,都体现了观察者模式,可以看bugstack中的设计模式篇

第 12 章:炉火纯青,基于JDK和Cglib动态代理,实现AOP核心功能#

1、学习目标:基于JDK、Cglib实现AOP切面

2、本章计划

① AOP(面向切面编程)是指通过预编译的方式和运行期间动态代理实现程序功能的统一维护

② 使用AOP对业务逻辑的各部分隔离,使各模块间的业务逻辑耦合度降低,提高代码的复用性,提高开发效率

③ AOP的核心技术是动态代理,即代理一个目标类,调用目标类的方法,并可以进行功能增强

④ 可以定义拦截器,通过规则找到被代理的方法,执行这些方法,并添加日志打印、异常监控等增强功能

⑤ 例如使用Spring AOP,只处理需要被拦截的方法,在拦截方法执行后,可以对方法做一些扩展

⑥ 那就需要定义代理方法的Proxy,使用方法拦截器MethodInterceptor#invoke实现

⑦ 使用 org.aspectj.weaver.tools.PointcutParser 处理拦截表达式

3、项目改造

① ClassFilter:定义match方法用于判断切点表达式中的类,与给定的接口或目标类是否匹配

② MethodMatcher:定义match方法用于判断切点表达式中的方法,与目标类中的方法是否匹配

③ Pointcut:定义切入点接口,用于获取ClassFilter、MethodMatcher这俩接口的实现类,但好像没用到

④ AspectJExpressionPointcut:实现了①②③接口中定义的方法,类实例化时,通过 aspectjweaver包提供的表达式解析器得到pointcutExpression,并执行类、方法的匹配校验

⑤ TargetSource:定义目标对象类,其中定义了Object属性用于保存目标对象,并提供获取目标对象、获取目标对象的Class类实现的接口集合

⑥ MethodInterceptor:是由aopalliance包提供的方法拦截器接口,由用户自定义类实现其中的invoke方法,该方法中包含目标对象的方法调用以及功能增强

⑦ AdvisedSupport:该类把目标对象(⑤)、用户自定义的拦截器(⑥的实现类)、方法匹配器(②)包装在一起,方便在Cglib和JDK的Proxy实现类中使用,类似于业务开发中对入参的包装

⑧ ReflectiveMethodInvocation:定义的包装类,整合了目标对象、方法、入参

⑨ UserServiceInterceptor:implement⑥,实现MethodInterceptor#invoke方法,就是自定义了增强方法,以供方法切入时调用,其中的MethodInvocation.proceed方法是调用代理对象中,调用的目标对象的方法,其实也是通过动态代理实现的,注意是先执行finally再执行return

⑩ AopProxy :定义一个标准接口,用于获取代理类,定义接口方便管理JDK、Cglib等代理实现类 ⑾ JdkDynamicAopProxy【图1】:

实现AopProxy、InvocationHandler接口,将获取代理对象与执行代理方法分开

1)实现AopProxy#getProxy方法:

✳ 该方法用于对外提供代理对象,

✳ 需要传入类加载器、目标对象的接口集合(通过⑦获取)、当前类(因为当前类重写了invoke方法)

2)实现InvocationHandler#invoke方法:

✳ 该方法用于调用目标对象的方法,并做一些功能增强

✳ 本方法的功能增强体现在校验定义的切点方法与代理对象的方法是否匹配,匹配成功后调用自定义的方法拦 截器(⑨),输出一些监控信息

⑿ Cglib2AopProxy【图2】:

✳ 基于Cglib使用Enhancer代理的类,使用ASM字节码增强技术,在运行期间为Java类或接口生成代理对象,不需要像JDK动态代理的实现那样继承InvocationHandler

✳ 创建Enhancer对象,注入目标对象、目标对象实现的接口集合、代理对象执行器创建代理对象

✳ 用户扩展的拦截方法在DynamicAdvisedInterceptor#intercept中实现,跟JDK动态代理中的invoke方法类似,先匹配切点方法,再调用目标对象的方法

⒀ ApiTest

✳ 测试动态代理,先实例化目标对象,实例化代理信息包装类AdvisedSupport,将目标对象、自定义的方法拦截器(⑨)、切点表达式注入其中

✳ 实例化Proxy,生成代理对象

✳ 通过代理对象调用方法

4、AOP与动态代理

① IOC解决了类之间的紧耦合,动态代理解决了方法之间的紧耦合

② 使用JDK动态代理,Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)创建代理类对象时,必须要传入目标对象实现的接口,在某些情况下目标类没有实现接口,因此就需要使用Cglib

③ 在Spring中,当Bean实现接口时,使用JDK动态代理,当Bean没有实现接口,使用Cglib动态代理

④ 可以在spring.xml配置中添加<aop proxyt-target-class=”true”/>强制使用Cglib

⑤ AOP三步走

✳ 确定目标对象targetObject,即需要进行无侵入式改造的Bean对象

✳ 编写Advice通知方法,即编写功能增强代码

✳ 配置切入点和切面

我只能说坚持不下去了,spring太复杂了#

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Spring核心:注解原理与手写Spring框架
https://onecodemaker.cn/posts/spring生态-01-spring/
作者
糖糖IT
发布于
2026-02-02
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录