Spring 框架底层原理
1、SpringIOC
1、ConfigurableApplicationContext 接口?
设置上下文 ID,设置父应用上下文,添加监听器,刷新容器,关闭,判断是否活跃等方法。
是 SpringBoot 最核心的内容-应用容器。
该接口的核心方法包括设置当前容器环境变量、添加BeanFactory后置处理器、添加容器监听器、加载或者刷新配置持久化代理。
该接口是 ApplicationContext 的子类,主要任务就是配置应用程序上下文功能。
2、BeanFactory 是容器最基本的接口?
BeanFactory 负责配置、创建、管理 Bean。
BeanFactory 是 Spring 的心脏,那么 ApplicationContext 就是完整的身躯了。
ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。
而ConfigurableApplicationContext作为实现ApplicationContext 的核心接口。 核心方法就是 refresh() 方法实现加载或者刷新配置持久化代理。
2.1、BeanDefinition 接口?
用于保存Bean的相关信息,包括属性、构造方法参数、依赖的Bean名称及是否单例、延迟加载等,它是实例化 Bean 的原材料,存储Bean初始化时的元数据 Spring 就是根据 BeanDefinition 中的信息实例化 Bean。
2.2、单例 DefaultSingletonBeanRegistry ?
维护一个 Map 的单例池存储单例对象,提供一个公开的 getSingleton 方法和一个 保护类型的 addSingleton 方法。
2.3、AbstractBeanFactory 抽象类?
- 继承 DefaultSingletonBeanRegistry 默认的单例抽象类,同时也具备了使用单例注册类的方法。
- 重点:实现了接口 BeanFactory 的实现,方法 getBean 的实现过程中可以看到,主要是对单例 Bean 对象的获取以及在获取不到时需要拿到 Bean 的定义做相应 Bean 实例化操作。那么 getBean 并没有自身的去实现这些方法,而是只定义了调用过程以及提供了抽象方法,由实现此抽象类的其他类做相应实现。
- 那 AbstractBeanFactory 的实现类有哪些?主要包括 AbstractAutowireCapableBeanFactory、DefaultListableBeanFactory 来分别实现对应的 createBean、getBeanDefinition 方法。
2.4、AbstractAutowireCapableBeanFactory 实现类
实现父类 AbstactBeanFactory 的创建实例的方法。
继承抽象类AbstractBeanFactory实现相应的抽象方法,实现其中的createBean方法但是AbstractAutowireCapableBeanFactory 本身也是一个抽象类,所以它只会实现属于自己的抽象方法,其他抽象方法由继承 AbstractAutowireCapableBeanFactory 的类实现。这里就体现了类实现过程中的各司其职。
3、什么是 refresh() 方法?
refresh() 方法实现加载或者刷新配置持久化代理。
3.1、AbstractApplicationContext 类?
抽象类使用到了模板方法模式,定义refresh()方法的调用模板,需要具体的子类去实现抽象方法。
3.2、AbstractRefreshableApplicationContext 抽象类
获取了 DefaultListableBeanFactory 的实例化以及对资源配置的加载操作。
实现热刷新功能,内部存在 DefaultListableBeanFactory 的实例对象。
通过调用子类实现 **AbstractXmlApplicationContext **实现类提供XmlBeanDefinitionReader解析xml文件获取 BeanDefinition 对象。
3.3、DefaultListableBeanFactory 类
DefaultListableBeanFactory 作为BeanFactory的默认实现抽象类,基于BeanDefinition对象,负责Bean注册后的处理。就是一个容器接口,可以独立使用的类。
内部维护一个 BeanDefinition 的 Map 对象作为 Spring 容器。
3.4、XMLBeanDefinitionReader 类
该类主要作用正如名字而言,基于xml文件的解析类。并且将解析后的数据进行封装到 BeanDefinition 对象中注册到 DefaultListableBeanFactory 类维护的 Map 容器中。
3.5、创建实例 createBeanInstance()方法
首先通过反射获取构造方法然后通过遍历进行参数匹配赋值构造器方法。
获取指定的动态代理策略方法来实例化当前的Bean对象。
由于目前还未进行对象的初始化和属性填充所以需要使用代理对象后期进行对象增强。
3.6、applyPropertyValues() 填充属性方法
获取 BeanDefinition 对象中封装的 Bean 的参数进行遍历匹配填充属性。
3.7、initializeBean() 初始化方法
上述的 refresh() 方法中还存在一步骤
所以在初始化 Bean 对象时还需要对对象 Aware 感知类的匹配并且赋值。
- 调用 BeanPostProcessor#before() 方法进行增强处理。
- before 方法也是调用 BeanPostProcessor 接口的实现类中进行对ApplicationContext设置所属的ApplicationContext 上下文方便后期获取该对象。
- 调用代理对象的 invokeInitMethods() 方法进行对象的初始化。
- 实现接口 InitializingBean 判断是否实现了初始化接口
- 注解配置 init-method {判断是为了避免二次执行初始化}
- 获取指定的初始化方法然后调用代理的 invoke 方法
- 调用 BeanPostProcessor#after() 方法处理后置增强处理。
- after 方法中只是返回了一个 bean 对象未做增强处理。
3.8、销毁方法
实现接口 DisposableBean、配置信息 destroy-method
4、Aware 接口?
- BeanFactoryAware
- BeanClassLoaderAware
- BeanNameAware
- ApplicationContextAware
4.1、Aware 接口的作用?
通常使用 Spring Aware 的目的是为了让 Bean 获得 Spring 容器的服务。
bean实现个某某Aware接口,然后这个bean就可以通过实现接口的方法,来接收接口方法传递过来的资源。
面向用户操作,我们不可能把 BeanFactoryPostProcessor 、BeanPostProcessor 这种类提供给用户操作吧,那么我们就需要扩展在这些框架被使用时怎么被调用,就需要提供一种能够感知容器操作的接口,能够获取接口入参中的各类功能。
通过 Aware 机制可以对 Bean 实现的方法进行回调扩展,上面就添加了一个 ApplicationContextAware 的类对象 Bean 进行赋值对象,后期可以通过 ApplicationContextAwareProcessor 中的方法进行回调
可以通过 Aware 的具体实现接口进行 instanceof 判断是否存在该感知类就可以判断是否需要设置该对应的类对象。ApplicationContext 对应 ApplicationContextAwareProcessor 感知类。就和RandomAccess 接口一样,只是为了标识 ArrayList 集合存在随机存储的作用。
/**
* @ClassName: BeanClassLoaderAware
* @Author: 小飞
* @Date: 2023/3/28 17:36
* @Description: 现此接口,既能感知到所属的 ClassLoader
*/
public interface BeanClassLoaderAware extends Aware{
void setBeanClassLoader(ClassLoader classLoader);
}
- 实现此接口,既能感知到所属的 ClassLoader
4.2、ApplicationContextAwareProcessor 类
- 由于 ApplicationContext 的获取不能直接创建 Bean 的时候就获取到,那么就需要在 refresh 的时候将 ApplicationContext 注册到一个 BeanPostProcessor 中 就可以将其注册到感知类中。
5、对象作用域
- 整个的实现过程包括了两部分,一个解决单例还是原型对象,另外一个处理 FactoryBean 类型对象创建过程中关于获取具体调用对象的 getObject 操作。
- SCOPE_SINGLETON、SCOPE_PROTOTYPE,对象类型的创建获取方式,主要区分在于 AbstractAutowireCapableBeanFactory#createBean 创建完成对象后是否放入到内存中,如果不放入则每次获取都会重新创建。
- createBean 执行对象创建、属性填充、依赖加载、前置后置处理、初始化等操作后,就要开始做执行判断整个对象是否是一个 FactoryBean 对象,如果是这样的对象,就需要再继续执行获取 FactoryBean 具体对象中的 getObject 对象了。整个 getBean 过程中都会新增一个单例类型的判断factory.isSingleton(),用于决定是否使用内存存放对象信息。
5.1、FactoryBeanRegistrySupport 抽象类
管理和创建 FactoryBean 产生的单例对象
维护一个 单例FactoryBean 对象Map容器。
- FactoryBeanRegistrySupport 类主要经济 FactoryBean 对象的创建,可以实现不同的领域只需要负责完成各自需要的功能,避免扩展导致类膨胀。
- 定义了 缓存操作 factoryBeanObjectCache 用来存放单例类型的对象,避免重复创建。
- doGetObjectFromFactoryBean 是具体的获取 FactoryBean#getObject() 方法。
6、容器事件和事件监听
- 在整个功能实现过程中,仍然需要在面向用户的应用上下文 AbstractApplicationContext 中添加相关事件内容,包括:初始化事件发布者、注册事件监听器、发布容器刷新完成事件。
- 使用观察者模式定义事件类、监听类、发布类,同时还需要完成一个广播器的功能,接收到事件推送时进行分析处理符合监听事件接受者感兴趣的事件,也就是使用 isAssignableFrom 进行判断。
- isAssignableFrom 和 instanceof 相似,不过 isAssignableFrom 是用来判断子类和父类的关系的,或者接口的实现类和接口的关系的,默认所有的类的终极父类都是Object。如果A.isAssignableFrom(B)结果是true,证明B可以转换成为A,也就是A可以由B转换而来。
6.1、ApplicationEvent 抽象类
/**
* @ClassName: ApplicationEvent
* @Author: 小飞
* @Date: 2023/3/28 22:11
* @Description: 提供 一套集事件注册、监听、触发为一体的事件实现 监听上下文
*/
public abstract class ApplicationEvent extends EventObject {
public ApplicationEvent(Object source) {
super(source);
}
}
- 以继承 java.util.EventObject 定义出具备事件功能的抽象类 ApplicationEvent,后续所有事件的类都需要继承这个类。
6.2、ApplicationContextEvent
监听事件应用上下文
6.3、ApplicationEventMulticaster 接口
- 在事件广播器中定义了添加监听和删除监听的方法以及一个广播事件的方法 multicastEvent 最终推送时间消息也会经过这个接口方法来处理谁该接收事件。
6.4、AbstractApplicationEventMulticaster 抽象类
继承 BeanFactoryAware 就是为了获取 BeanFactory 对象实现了 ApplicationEventMulticaster 接口就是管理事件监听的核心类,存储了全部的监听事件对象同时实现了监听事件的添加、移除、获取全部监听事件、判断当前Bean对象是否设置了监听。
维护了一个事件监听Set集合。
7、动态代理实例化策略
用动态代理来实现含参构造。一个是基于 Java 本身自带的方法 DeclaredConstructor,另外一个是使用 Cglib 来动态创建 Bean 对象。Cglib 是基于字节码框架 ASM 实现,所以你也可以直接通过 ASM 操作指令码来创建对象。
7.1、BeanFactory 接口中重载了多个getBean 方法?
/**
* @ClassName: BeanFactory
* @Author: 小飞
* @Date: 2023/3/25 21:24
* @Description: BeanFactory是实现IOC容器的核心接口,通过beanName或者beanName+参数获取指定的Bean对象
* 它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
*/
public interface BeanFactory {
// 空参构造
Object getBean(String name) throws BeansException;
// 含参构造
Object getBean(String name, Object... args) throws BeansException;
}
重载getBean方法增加健壮性 提供含参方法获取Bean对象。
7.2、InstantiationStrategy 接口?
Bean 实例化策略,实例化策略的接口
- 实例化接口 instantiate 方法中添加必要的入参信息,包括:beanDefinition、 beanName、ctor、args
- Constructor 的作用就是匹配到符合对应参数的构造函数
- args 就是一个具体的入参信息,实例化需要使用到参数。
7.3、JDK 实例化?
通过 JDK 的 DeclaredConstructor 来实现含参数构造。
// 传入的构造器不为空则进行含参构造实例化
if (null != ctor) {
return clazz.getDeclaredConstructor(ctor.getParameterTypes()).newInstance(args);
} else {
// 如果构造器为空说明是无参构造
return clazz.getDeclaredConstructor().newInstance();
}
- 可以通过BeanDefinition对象获取需要的元数据。包括反射类型。
- 接下来判断 ctor 是否为空,如果为空则是无构造函数实例化,否则就是需要有构造函数的实例化。
7.4、CGLIB 实例化?
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(beanDefinition.getBeanClass());
enhancer.setCallback(new NoOp() {
@Override
public int hashCode() {
return super.hashCode();
}
});
// 下面也是通过传入的构造函数是否为空来选择实例化方式为含参还是无参
if (null == ctor) return enhancer.create();
return enhancer.create(ctor.getParameterTypes(), args);
- 这里直接创建 含参或者无参构造器还是比较简单的。
8、文件注册对象解析
我们可以和其他的设计框架一样,规范好配置格式,然后我们再使用策略模式选择对应的解析方式来实现自动解析参数并且注入到对象中
8.1、Resource 接口
- 在 Spring 框架下创建 core.io 核心包,在这个包中主要用于处理资源加载流。
- 定义 Resource 接口,提供获取 InputStream 流的方法,接下来再分别实现三种不同的流文件操作:classPath、FileSystem、URL
通过策略模式实现多种解析策略 分别为ClassPathResource、FileSystemResource、UrlResource
8.2、包装资源加载器
按照资源加载的不同方式,资源加载器可以把这些方式集中到统一的类服务下进行处理,外部用户只需要传递资源地址即可,简化使用。
public class DefaultResourceLoader implements ResourceLoader {
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));
}
else {
try {
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException e) {
return new FileSystemResource(location);
}
}
}
}
8.3、Bean 解析器接口
- 这是一个简单的定义BeanDefinition解析器的方法接口,重载三个加载Bean定义的方法。
- 这里需要注意 getRegistry()、getResourceLoader(),都是用于提供给后面三个方法的工具,加载和注册,这两个方法的实现会包装到抽象类中,以免污染具体的接口实现方法。
子类通过 AbstractBeanDefinitionReader 实现具体的加载方法。
8.4、XmlBeanDefinitionReader 类
- 在 doLoadBeanDefinitions 方法中,主要是对xml的读取 XmlUtil.readXML(inputStream) 和元素 Element 解析。在解析的过程中通过循环操作,以此获取 Bean 配置以及配置中的 id、name、class、value、ref 信息。
- XmlBeanDefinitionReader 的核心就是解析xml配置文件 然后将解析的内容封装成PropertyValue对象,最后将PropertyValue 存储到一个PropertyValues集合中 存储到BeanDefinition对象中作为属性进行注入。
- 最后在获取父类提供的getRegistry() 接口获取Bean的注册类将当前封装的BeanDefinition对象注册到DefaultListableBeanFactory#beanDefinitionMap 中也就是容器中。
8.5、ListableBeanFactory 接口
- 枚举它们的所有bean信息,而不用一个个通过bean的名称或类型一个个查找。或者通过一种类型返回所有的数据
8.6、AutowireCapableBeanFactory 自动化处理 Bean 配置
为已经实例化的对象装配属性、实例化一个Bean,并自动装配,这些被装配的属性对象都是Spring管理的。
- 前面定义了 BeanPostProcessor 接口的后置处理方法 自然要用到,通过AbstractAutowireCapableBeanFactory # createBean 创建Bean对象后初始化前后通过调用AutowireCapableBeanFactory 接口中的两个方法来调用所有的 BeanPostProcessor 对象执行。
8.7、ConfigurableBeanFactory 配置化接口
- 可配置的BeanFactory工厂,定义各种配置能力,巨大的工厂接口。提供 配置 BeanFactory 的各种方法和配置参数等。
- 增加方法 addBeanPostProcessor 增加到 AbstractBeanFactory#BeanPostProcessorList中
9、应用上下文
BeanFactoryPostProcessor 和 BeanPostProcessor,这两个接口都是 Bean的后置处理器,只不过BeanFactoryPostProcessor 是对于实例化前后的后置处理器而BeanPostProcessor 初始化后置处理器。作用的时间不同,但是作用是差不多,都是扩展内部消息。
同时如果只是添加这两个接口,不做任何包装,那么对于使用者来说还是非常麻烦的。我们希望于开发 Spring 的上下文操作类(就相当一个控制类,控制Bean对象的创建,实例化,初始化一个周期的执行),把相应的 XML 加载 、注册、实例化以及新增的修改和扩展都融合进去,让 Spring 可以自动扫描到我们的新增服务,便于用户使用。
9.1、BeanFactoryPostProcessor 接口
- 这个接口是满足于在所有的 BeanDefinition 加载完成后,实例化 Bean 对象之前,提供修改 BeanDefinition 属性的机制。
9.2、BeanPostProcessor 接口
- 提供了修改新实例化 Bean 对象的扩展点,用于在 Bean 对象执行初始化方法之前,执行此方法、postProcessAfterInitialization用于在 Bean 对象执行初始化方法之后,执行此方法。
- 包含两种方法 BeanPostProcessor#postProcessBeforeInitialization and BeanPostProcessor#postProcessAfterInitialization
9.3、AutowireCapableBeanFactory 接口
- 自动装配的BeanFactory接口,就是执行 Bean对象中的 BeanPostProcessor 接口中对应的方法。
9.4、ApplicationContext 接口
- Context 是本次实现应用上下文功能新增的服务包
- ApplicationContext,继承于 ListableBeanFactory,也就继承了关于 BeanFactory 方法,比如一些 getBean 的方法。另外 ApplicationContext 本身是 Central 接口,但目前还不需要添加一些获取ID和父类上下文,所以暂时没有接口方法的定义。
2、SpringAOP
1、AOP 自动代理时机
在service bean的创建过程中(也就是getBean(“service”)),AOP通过BeanPostProcess后置处理器操作进行介入 分为2种情况:
- 用户自定义了targetSource,则bean的创建(实例化、填充、初始化)均由用户负责,Spring Ioc不会在管该代理目标对象traget,这种情况基本上不会发生,很多人用了几年Spring可能都不知道有它的存在。
- 正常情况下都是Spring Ioc完成代理对象target的实例化、填充、初始化。然后在初始化后置处理器中进行介入,对bean也就是service进行代理。
2、解决循环依赖
3、基于 JDK、CGLIB 实现 AOP 切面
- 只处理一些需要被拦截的方法。在拦截方法后,执行你对方法的扩展操作。
- 我们就需要先来实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用 MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args) 这块是整个使用时的差异。
3.1、Pointcut 切点接口
/**
* @ClassName: Pointcut
* @Author: 小飞
* @Date: 2023/3/29 16:11
* @Description: 切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,
* 这两个接口获取都是切点表达式提供的内容
*/
public interface Pointcut {
/**
* 获取反射类拦截器(匹配器)
* @return
*/
ClassFilter getClassFilter();
/**
* 获取方法匹配器
* @return
*/
MethodMatcher getMethodMatcher();
}
- 切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,这两个接口获取都是切点表达式提供的内容。
3.2、ClassFilter 类匹配
定义类匹配类,用于切点找到给定的接口和目标类。
3.3、MethodMatcher 方法匹配
方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())
3.4、AspectJExpressionPointcut 切点表达式
切点表达式实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用。
3.5、TargetSource 切面通知信息
被代理的目标对象,获取 AOP 调用的当前目标在调用 AOP 代理对象的方法时,在执行完所有的 advice chain 之后,最终会通过反射调用这个目标 target 的方法。
3.6、AdvisedSupport(封装类)
- AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。这和你的业务开发中包装入参是一个道理
- TargetSource,是一个目标对象,在目标对象类中提供 Object 入参属性,以及获取目标类 TargetClass 信息。
- MethodInterceptor,是一个具体拦截方法实现类,由用户自己实现 MethodInterceptor#invoke 方法,做具体的处理。
3.7、Cglib2AopProxy 实现类
- 基于 Cglib 使用Enhancer代理的类可以在运行期间为接口使用ASM字节码增强处理对象生成。
- 扩展进去的用户拦截方法使用Enhancer#setCallback 中处理,用户自己的新增的拦截处理。
3.8、JdkDynamicAopProxy 实现类
- 基于 JDK 实现的代理类,实现 AopProxy、InvocationHandler 就可以将代理对象 getProxy 和反射调用方法 invoke 隔离处理。
- invoke 方法中主要处理匹配的方法后,使用用户自己提供的方法拦截实现,做反射调用 methodInterceptor.invoke 。
- ReflectiveMethodInvocation,其他它就是一个入参的包装信息,提供了入参对象:目标对象、方法、入参。
4、将AOP融合Bean生命周期
我们不可能把底层的方法提供给用户使用,首先操作逻辑太复杂,然后底层提供用户会不安全性。所以我们需要将 AOP 代理的对象融入到Bean周期中,在生成AOP对象的时候一样通过配置对应的spring.xml信息来封装,在createBean方法中判断AOP 对象会特定实现了某个对象,选择其他的实例化方式。
4.1、Advice 拦截链
- 定义前置通知 BeforeAdvice 通知链,在目标对象被执行前被调用,通知类型。
MethodBeforeAdvice
4.2、Advisor 访问者
用于把一个Advice和一个Pointcut组合起来。 访问者Advisor是Spring AOP的顶层抽象,用来管理Advice和Pointcut(PointcutAdvisor和切点有关,但IntroductionAdvisor和切点无关)
子类接口 PointcutAdvisor 承担了 Pointcut 和 Advice 的组合,Pointcut 用于获取 JoinPoint,而 Advice 决定于 JoinPoint 执行什么操作。
4.3、AdvisedSupport (封装类)
添加了动态代理类型的配置信息
要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。AOP代理配置管理器的基类。 它封装了AOP中对通知 (Advice)和通知器 (Advisor)的相关操作, 这些操作对于不同的AOP的代理对象的生成都是一样的,但对于具体的AOP代理对象的创建,AdvisedSupport把它交给子类去实现。
4.4、ProxyFactory
代理工厂
通过AdvisedSupport中的配置信息 创建对应类型的动态代理对象。(默认是JDK代理)
4.5、InstantiationAwareBeanPostProcessor
实例化前后感知类 作为扩展类接口
postProcessBeforeInstantiation 接口方法作用:提供给用户的扩展接口 如果自定义的代理类则直接返回Bean 不需要执行Spring容器的流程
4.6、DefaultAdvisorAutoProxyCreator
默认将bean容器中所有的 Advisor 都取到,如果有能够匹配某一个bean的 Advisor 存在,则会基于能够匹配该bean的所有 Advisor 创建对应的代理对象。
- 这个 DefaultAdvisorAutoProxyCreator 类的主要核心实现在于 postProcessBeforeInstantiation 方法中,从通过 beanFactory.getBeansOfType 获取 AspectJExpressionPointcutAdvisor 开始。
- 获取了 advisors 以后就可以遍历相应的 AspectJExpressionPointcutAdvisor 填充对应的属性信息,包括:目标对象、拦截方法、匹配器,之后返回代理对象即可。
- 那么现在调用方获取到的这个 Bean 对象就是一个已经被切面注入的对象了,当调用方法的时候,则会被按需拦截,处理用户需要的信息。
4.7、AbstractAutowireCapableBeanFactory
继承 AbstractBeanFactory 类的抽象方法,实现对应的 createBean 方法,在第一步判断是否需要返回代理的 Bean 对象
调用 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法
而该类被 AdvicedSupport 继承进行包装成 AdvisedSupport 对象传入到 ProxyFactory 返回一个代理对象。
5、自动扫描 Bean 对象注册
5.1、PropertyPlaceholderConfigurer 类
资源属性的配置器
依赖于 BeanFactoryPostProcessor 在 Bean 生命周期的属性,可以在 Bean 对象实例化之前,改变属性信息。
5.2、ClassPathScanningCandidateComponentProvider
- 这里先要提供一个可以通过配置路径 basePackage=cn.wen.springframework.test.bean,解析出 classes 信息的工具方法 findCandidateComponents,通过这个方法就可以扫描到所有 @Component 注解的 Bean 对象了。
6、注解注入属性信息
自动扫描包注册 Bean 对象之后,就需要把原来在配置文件中通过 property name=“token” 配置属性和Bean的操作,也改为可以自动注入。这就像我们使用 Spring 框架中 @Autowired、@Value 注解一样,完成我们对属性和对象的注入操作。
- 要处理自动扫描注入,包括属性注入、对象注入,则需要在对象属性 applyPropertyValues 填充之前 ,把属性信息写入到 PropertyValues 的集合中去。这一步的操作相当于是解决了以前在 spring.xml 配置属性的过程。
- 还有一个是关于 @Autowired 对于对象的注入,其实这一个和属性注入的唯一区别是对于对象的获取 beanFactory.getBean(fieldType),其他就没有什么差一点了。
6.1、StringValueResolver
接口 StringValueResolver 是一个解析字符串操作的接口
- 在解析属性配置的类 PropertyPlaceholderConfigurer 中,最主要的其实就是这行代码的操作 beanFactory.addEmbeddedValueResolver(valueResolver) 这是把属性值写入到了 AbstractBeanFactory 的 embeddedValueResolvers 中。
将解析后的StringValue列表存储到 AbstractBeanFactory 抽象类的List集合中。
6.2、InstantiationAwareBeanPostProcessor
添加postProcessPropertyValues的方法来实现 Bean 对象执行初始化之前,执行该方法。
6.3、AutowiredAnnotationBeanPostProcessor
处理 @Value、@Autowired,注解的 BeanPostProcessor
- AutowiredAnnotationBeanPostProcessor 是实现接口 InstantiationAwareBeanPostProcessor 的一个用于在 Bean 对象实例化完成后,设置属性操作前的处理属性信息的类和操作方法。
- 核心方法 postProcessPropertyValues,主要用于处理类含有 @Value、@Autowired 注解的属性,进行属性信息的提取和设置。
/**
* @ClassName: ClassPathBeanDefinitionScanner
* @Author: 小飞
* @Date: 2023/3/30 14:26
* @Description: 扫描指定路径的注解并且解析到BeanDefinition对象中
*/
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
private BeanDefinitionRegistry registry;
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public void doScan(String... basePackages) {
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition beanDefinition : candidates) {
// 解析 Bean 的作用域 singleton、prototype
String beanScope = resolveBeanScope(beanDefinition);
if (StrUtil.isNotEmpty(beanScope)) {
beanDefinition.setScope(beanScope);
}
registry.registerBeanDefinition(determineBeanName(beanDefinition), beanDefinition);
}
}
// 注册处理注解的 BeanPostProcessor(@Autowired、@Value)
registry.registerBeanDefinition("cn.wen.springframework.context.annotation.internalAutowiredAnnotationProcessor", new BeanDefinition(AutowiredAnnotationBeanPostProcessor.class));
}
/**
* 扫描 Scope 注解并且返回内容
* @param beanDefinition
* @return
*/
private String resolveBeanScope(BeanDefinition beanDefinition) {
Class<?> beanClass = beanDefinition.getBeanClass();
Scope scope = beanClass.getAnnotation(Scope.class);
if (null != scope) return scope.value();
return StrUtil.EMPTY;
}
/**
* 扫描 Component 注解并且返回 Value
* @param beanDefinition
* @return
*/
private String determineBeanName(BeanDefinition beanDefinition) {
Class<?> beanClass = beanDefinition.getBeanClass();
Component component = beanClass.getAnnotation(Component.class);
String value = component.value();
if (StrUtil.isEmpty(value)) {
value = StrUtil.lowerFirst(beanClass.getSimpleName());
}
return value;
}
}
ClassPathBeanDefinitionScanner#doScan 方法中注册扫描方法。
6.4、注册到Bean的生命周期
在设置 Bean 属性注入之前,允许 BeanPostProcessor 修改属性值
AbstractAutowireCapableBeanFactory#createBean 方法中 Bean 实例化之后 Bean属性填充之前调用该方法。
- bean = createBeanInstance(beanDefinition, beanName, args);
- applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);
- applyPropertyValues(beanName, bean, beanDefinition);
7、代理对象填充属性
因为之前的Aop动态代理是融入了Bean的生命周期,创建动态代理是在整个创建Bean对象之前的,并不能执行Spring容器的创建Bean 初始化的执行流程,需要将动态代理的的逻辑迁移到 Bean 对象执行初始化之前,实例化之后。
7.1、Bean 生命周期插入
- 在 AbstractAutowireCapableBeanFactory#createBean 方法中,其实关注点就在于initializeBean -> applyBeanPostProcessorsAfterInitialization 这一块逻辑的调用,最终完成 AOP 代理对象的创建操作。
- applyBeanPostProcessorsAfterInstantiation 方法的作用就是给用户提供扩展的接口,用户实现 接口InstantiationAwareBeanPostProcessor 同时重写postProcessAfterInstantiation 方法配置是否需要注入到spring容器中,可以实现定制化注入操作。
这一段代码是Spring用来提供给程序员扩展使用的, 如果我们不希望一个bean参与到属性注入, 自动装配的流。程中, 那么就可以创建一个InstantiationAwareBeanPostProcessor后置处理器的实现类, 重写其 postProcessAfterInstantiation方法, 如果该方法返回false, 那么continueWithPropertyPopulation这个变量会被置为false, 而这个变量被置为false, 在下面我们可以看到直接就return了, 从而Spring就不会对属性进行注入。
8、循环依赖
循环依赖主要分为这三种,自身依赖于自身、互相循环依赖、多组循环依赖。
所以需要 Spring 提供了除了构造函数注入和原型注入外的,setter 循环依赖注入 。
就是用于解决循环依赖就必须是三级缓存呢,二级行吗?一级可以不?其实都能解决,只不过 Spring 框架的实现要保证几个事情,如只有一级缓存处理流程没法拆分,复杂度也会增加,同时半成品对象可能会有空指针异常。而将半成品与成品对象分开,处理起来也更加优雅、简单、易扩展。另外 Spring 的两大特性中不仅有 IOC 还有 AOP,也就是基于字节码增强后的方法,该存放到哪,而三级缓存最主要,要解决的循环依赖就是对 AOP 的处理,但如果把 AOP 代理对象的创建提前,那么二级缓存也一样可以解决。但是,这就违背了 Spring 创建对象的原则,Spring 更喜欢把所有的普通 Bean 都初始化完成,在处理代理对象的初始化。
不过,没关系我们可以先尝试仅适用一级缓存来解决循环依赖,通过这样的方式从中学习到处理循环依赖的最核心原来,也就是那 20%的地方。
- 如果仅以一级缓存解决循环依赖,那么在实现上可以通过在 A 对象 newInstance 创建且未填充属性后,直接放入缓存中。
- 在 A 对象的属性填充 B 对象时,如果缓存中不能获取到 B 对象,则开始创建 B 对象,同样创建完成后,把 B 对象填充到缓存中去。
- 接下来就开始对 B 对象的属性进行填充,恰好这会可以从缓存中拿到半成品的 A 对象,那么这个时候 B 对象的属性就填充完了。
- 最后返回来继续完成 A 对象的属性填充,把实例化后并填充了属性的 B 对象赋值给 A 对象的 b 属性,这样就完成了一个循环依赖操作。
在创建对象的 AbstractAutowireCapableBeanFactory#doCreateBean 方法中,提前暴露对象以后,就可以通过接下来的流程,getSingleton 从三个缓存中以此寻找对象,一级、二级如果有则直接取走,如果对象是三级缓存中则会从三级缓存中获取后并删掉工厂对象,把实际对象放到二级缓存中。
8.1、DefaultSingletonBeanRegistry
提供三级缓存的容器:
- singletonObjects(普通对象):存储完全初始化好的 Bean 对象,可直接使用
- earlySingletonObjects(二级缓存):提前暴漏对象,没有完全实例化的对象
- singletonFactories(三级缓存):存放代理对象
- 在用于提供单例对象注册的操作的 DefaultSingletonBeanRegistry 类中,共有三个缓存对象的属性;singletonObjects、earlySingletonObjects、singletonFactories,如它们的名字一样,用于存放不同类型的对象(单例对象、早期的半成品单例对象、单例工厂对象)。
- 紧接着在这三个缓存对象下提供了获取、添加和注册不同对象的方法,包括:getSingleton、registerSingleton、addSingletonFactory,其实后面这两个方法都较简单,主要是 getSingleton 的操作,它是在一层层处理不同时期的单例对象,直至拿到有效的对象。
- 一级缓存:在最上层的缓存singletonObjects中,缓存的是完整的单例bean,这里面拿到的bean是已经实例化好和初始化好的bean,可以直接使用的bean。如果没有取到,则进入2处
- 二级缓存:在二级缓存earlySingletonObjects中,查找bean。二级缓存主要缓存的分成两种情况,只要看这个bean是否被AOP切面处理:
- 否:保存半成品的bean原始实例,属性未填充
- 是:保存的是代理的bean,即proxyBean,目标bean还是未填充属性的半成品
- 三级缓存:如果在二级缓存中,还是没有找到,则在三级缓存中查找对应的工厂对象,利用拿到的工厂对象(工厂中有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但是还没有注入属性的bean)去获取包装后的bean,或者说,代理后的bean。
主要功能就是通过ObjectFactory 的一个内部类来获取一个单例代理对象,需要添加到三级缓存中,三级缓存就是存放的一个Bean对象的 ObjectFactory 对象。
8.2、三级缓存解决循环依赖
我们以这样一个示例:
- AService -> BService
- BService -> AService
AService依赖BService,BService也依赖AService,加入在getBean(aService)的时候,进入doCreateBean:
Spring首先是加个三级缓存,里面存的不是具体的bean,而是一个工厂对象,是可以拿到代理的bean的。
在上述的doCreateBean中有一段如下代码:
private Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) {
Object bean = null;
try {
// 实例化 bean
bean = createBeanInstance(beanDefinition, beanName, args);
// 处理循环依赖,将实例化后的Bean对象提前放入缓存中暴露出来
// 为避免后期循环依赖, 可以在bean初始化完成前将创建实例的ObjectFactory加入工厂
if (beanDefinition.isSingleton()) {
Object finalBean = bean;
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));
}
}
}
这里new了一个ObjectFactory,然后会存入到如下的第三级缓存中:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory){
if (!this.singletonObjects.containsKey(beanName)) {
// 存储到三级缓存 然后再将二级缓存中的记录删除
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
}
}
- 虽然我们的 AService 不能再一级缓存中命中,但是我们在创建AService时已经将AService的ObjectFactory 存入到三级缓存中,在三级缓存中命中了 AService 的ObjectFactory 可以获取到 AService的代理对象只是还未属性注入而已。通过三级缓存拿到其ObjectFactory,然后getObject()方法获取到其代理对象。这个getObject就是上文中提到的匿名内部类getEarlyBeanReference获取的对象,即代理对象AService。
- 此时BService完成AService的注入,BService变成一个完整的对象,且是经过BeanPostProcessor改变后的代理对象。并存放到了一级缓存中。
继续走AService的属性注入流程,此时BService的代理对象已经创建完成并返回,AService注入BService时BService其实是一个完整的代理对象。AService注入并放入一级缓存,这样就完美的解决了循环依赖的问题。
最后我们总结一下上面的创建流程:
- AService首先实例化,实例化通过ObjectFactory半成品暴露在三级缓存中
- 填充属性BService,发现BService还未进行加载,就会先去加载BService
- 在加载BService过程中,实例化,通过ObjectFactory半成品暴露在三级缓存中
- 填充属性AService的时候,这时候能够从三级缓存中拿到半成品的ObjectFactory
- 拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法,此方法最终会调用到getEarlyBeanReference()方法,这个方法主要逻辑是如果bean被AOP切面代理则返回的是proxyBean,如果未被代理则返回的是原始originBean。
- 此时拿到AService的半成品代理bean,然后从三级缓存中移除,放到二级缓存earlySingletonObjects中,此时BService注入的AService其实是一个半成品的AService代理对象(原始对象),最终BService完成的创建,
- 回来继续走AService的属性填充,最终完成的BService会被注入进去,AService完成创建
- 最终BService的属性AService也是一个完整的对象,因为他们是同一个对象
8.3、二级缓存解决不了?
其实到这里,很多人想为什么不用二级缓存,而非得用三级缓存才能解决循环依赖呢。
二级缓存有两种组合方式:
- singletonObjects和earlySingletonObjects
- singletonObjects和singletonFactories
不管哪种组合方式,我们看上面的示例(AService依赖BService,BService同样依赖AService)能否走通就可以了。
singletonObjects和earlySingletonObjects组合
我们直接看流程:
- AService创建,放入早期的earlySingletonObjects缓存,缓存的是半成品AService对象。走到属性注入BService
- 发现BService没有创建,开始创建BService
- 创建BService,属性注入AService,首先从缓存中获取AService,能获取到半成品的AService,进行注入到BService的字段中,BService完成创建并返回。删除earlySingletonObjects并添加singletonObjects缓存
- 继续走AService注入属性的流程,拿到完整的BService对象,注入到对象中去。然后删除earlySingletonObjects缓存并添加singletonObjects缓存
- 此时BService拿到的AService对象是一个完整的对象,因为他们是同一个对象
singletonObjects和singletonFactories组合
同样,我们也直接看流程
- AService创建,拿到ObjectFactory放入singletonFactories缓存中,缓存的同样是半成品的AService的ObjectFactory,走到属性注入BService
- 发现BService没有创建,开始创建BService
- 创建BService,属性注入AService,首先从缓存中singletonFactories能获取到AService的ObjectFactory,然后调用其getObject函数拿到AService,此时AService同样是个半成品的,注入到BService的字段中,BService完成创建并返回。删除singletonFactories并添加singletonObjects缓存
- 继续走AService注入属性的流程,拿到完成的BService对象,注入到对象中去,然后删除singletonFactories缓存并添加singletonObjects缓存
- 此时BService拿到的AService对象是一个完整的对象,因为他们是同一个对象
不管是哪一种组合,我们看这个流程似乎都没有问题,也确实,这种流程走下来确实是没有问题的。但是我们忽略了一个重要的特性:AOP。
存在AOP的情况(singletonObjects和singletonFactories)
我们都知道AOP其实是通过BeanPostProcessor的postProcessAfterInitialization织入的,并且我们在存在AOP代理的情况下,ObjectFactory.getObject()最终调用的是getEarlyBeanReference方法:
getEarlyBeanReference方法,我们看其父类AbstractAutoProxyCreator的方法:
我们可以看到先生成缓存key,再存入缓存,最后调用wrapIfNecessary方法,wrapIfNecessary这个方法我们似乎很熟悉,在AOP的一章节中,发现AOP的postProcessAfterInitialization调用的就是这个方法,具体实现是在AbstractAutoProxyCreator中:
是的,他们最终走到的都是wrapIfNecessary方法,但是整个加载流程只会走一次,因为这里有一个缓存key。现在理解这个缓存的作用了。
那么我们继续看,拿到的ObjectFactory其实是一个匿名内部类,内部类的bean是AOP代理的bean。每次getObject的时候其实是走AOP代理的逻辑,那么这里就有问题了,如果每次都ObjectFactory.getObject()都是一个新的对象,前后无法关联,最终导致BService注入的AService并不是一个完整的AService。所以,此时我们可以否定singletonObjects和singletonFactories的组合了。
存在AOP的singletonObjects和earlySingletonObjects
注意,这里是关键,我们直接将三级缓存去掉,而是直接调用singletonFactory.getObject()存放缓存。而这个ObjectFactory我们知道是从getEarlyBeanReference获取到的匿名内部类。所以存进去的二级缓存直接是早期的未填充属性的对象。
结论就是二级缓存(singletonObjects和earlySingletonObjects)可以解决循环依赖
- 问题就在于调用getEarlyReference,这一步在很多时候其实是没有必要的,在没有循环依赖的时候,这个方法是不会被调用。也就是说,我们为了解决系统中那百分之一的可能,而让99%的bean创建的时候都走上一圈。效率上说不过去
- Spring官方建议我们使用构造器方式注入,而使用构造器注入我们知道是无法解决循环依赖问题的。这么看的话其实不是Spring的自相矛盾吗?所以猜测Spring是不希望我们使用Spring的循环依赖的,因为能在项目启动中就爆出问题远远比在项目运行后不知道什么时候爆出问题的严重程度要小。
- 方便后续的扩展
如果创建的Bean有对应的aop代理,那其他对象注入时,注入的应该是对应的代理对象;「但是Spring无法提前知道这个对象是不是有循环依赖的情况」,而正常情况下(没有循环依赖情况),Spring都是在对象初始化后才创建对应的代理。这时候Spring有两个选择:
- 不管有没有循环依赖,实例化后就直接创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入(只需要2级缓存,singletonObjects和earlySingletonObjects即可)
- 「不提前创建好代理对象,在出现循环依赖被其他对象注入时,才提前生成代理对象(此时只完成了实例化)。这样在没有循环依赖的情况下,Bean还是在初始化完成才生成代理对象」(需要3级缓存)
所以说,从功能上来说,完全可以使用二级缓存(singletonObjects和earlySingletonObjects)解决循环依赖的问题,但是出于效率性和扩展性来说,Spring采用了三级缓存解决这个问题。
普 通 用 户: 这篇博客让我受益匪浅。作者通过自己的经验和见解,给我们提供了很多有价值的信息,非常感谢!
Kwan的解忧杂货铺@新空间代码工作室: 博主的文章一直都是我的学习圣经,内容详实,通俗易懂,希望博主能一如既往地分享知识,帮助更多人成长。你的博文总是给予我新的思考和启发,真的很感谢你一直以来的辛勤付出。每篇博客都是知识的瑰宝,我真的很喜欢你的风格,期待你的下一次精彩分享。
CSDN-Ada助手: 恭喜您撰写了第10篇博客!标题“RSA & ECDHE 算法”听起来非常引人注目。您对密码学算法的深入探索令人印象深刻。我很欣赏您对这些复杂主题的解释和分享。 在接下来的创作中,我建议您可以考虑探索更多与这两种算法相关的实际应用案例,如数据加密、数字签名等方面。这将进一步拓宽读者的知识领域,并激发更多的讨论和思考。 请继续保持博客创作的势头,我期待着您未来更多的精彩内容!