mini-Spring-Boot 源码阅读

项目地址 阅读一下前辈编写的代码,帮助自己理解Spring Boot框架,感兴趣的朋友可以给前辈贡献Star。下面记录自己的理解。

感悟写在开头

如果还没看懂,或者还没明白有些类为什么需要,抱着疑问继续往下读,因为框架的设计,不是一蹴而就的,新需求和新功能需要不停的迭代,所以总是会在核心功能以外添加其他的代码,所以如果有不懂的地方,记录TODO,不要停下来。

项目地址

下面我将阅读源码,并且将自己的深入理解进行记录。

1 基础篇:IoC

Inversion of Control (IoC),在软件开发中,IoC 是一种设计思想,主要用于实现对象之间的解耦。它将对象的创建和依赖关系的管理交由容器处理,而不是由对象自身负责。Spring框架是一个典型的IOC容器,它通过依赖注入(Dependency Injection, DI)实现了IOC的核心思想。

简单来说IoC就是控制反转,意味着创建Bean的过程交给框架,而不是自己手动去New进行创建。

1.1 最简单的bean容器

首先来看看框架是如何保存我们的所有的Java Bean。

创建一个 BeanFactory,内部包含一个 map 用来保存 bean,只有注册 bean 和获取 bean两个方法。

typescript 复制代码
public class BeanFactory {
	// 按照 name 来进行 bean 的控制
	private Map<String, Object> beanMap = new HashMap<>();

	public void registerBean(String name, Object bean) {
		beanMap.put(name, bean);
	}

	public Object getBean(String name) {
		return beanMap.get(name);
	}
}

看到这里就暂时足够理解Bean是如何存放的。

1.2 BeanDefinition和BeanDefinitionRegistry

我们了解了 Bean 是如何放在工厂当中的了,为了实现 IoC,Bean 的创建也应该是交给框架来进行实现的,那么本节当中的两个类就可以看到具体是如何创建的了。

上一节容器里存的是已经 new 出来的对象 ,配置与创建绑在一起,无法描述「延迟创建」「构造参数」「作用域」等元信息。本节约定用 BeanDefinition 描述「如何创建一个 Bean」(此处先简化为只保存 Class),用 BeanDefinitionRegistry 管理这些定义;再用 SingletonBeanRegistry 缓存已创建的单例。容器同时扮演「定义注册中心」与「单例仓库」:先 register 定义,再在 getBean 时按需实例化,这就是 Spring 里懒加载与统一生命周期的起点。

新增了一些类:

  • BeanDefinition,顾名思义,用于定义bean信息的类,包含bean的class类型、构造参数、属性值等信息,每个bean对应一个BeanDefinition的实例。简化BeanDefinition仅包含bean的class类型。
  • BeanDefinitionRegistry,BeanDefinition注册表接口,定义注册BeanDefinition的方法。
  • SingletonBeanRegistry及其实现类DefaultSingletonBeanRegistry,定义添加和获取单例bean的方法。
    bean容器作为BeanDefinitionRegistry和SingletonBeanRegistry的实现类,具备两者的能力。向bean容器中注册BeanDefinition后,使用bean时才会实例化。
    详细代码位于:beans/config/BeanDefinition.class

在作者的代码当中,BeanDefinition当中定义了 Bean 的作用域和初始化方法名称等属性,对其他的属性包括class类型、方法构造参数、bean属性、bean的scope等没有实现具体的方法,但是已经足够理解我们需要Beandefinition来描述清楚每一个Bean当中的属性。

那么到此为止,我们就可以创建完整的工厂了:DefaultListableBeanFactory

typescript 复制代码
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

其中包含着registerBeanDefinitiongetBeanDefinition等方法。

1.3 Bean实例化策略InstantiationStrategy

上面我们创建的了完整了BeanFactory,还需要BeanFactory创建对象。

现在bean是在AbstractAutowireCapableBeanFactory.doCreateBean方法中用beanClass.newInstance()来实例化,仅适用于bean有无参构造函数的情况。

针对bean的实例化,抽象出一个实例化策略的接口InstantiationStrategy,有两个实现类:

  • SimpleInstantiationStrategy,使用bean的构造函数来实例化,通过反射直接来获取。
  • CglibSubclassingInstantiationStrategy,使用CGLIB动态生成子类。

1.4 为bean填充属性

不是所有的Bean都是无状态的(即没有成员变量),如果Bean当中需要有属性,比如Person需要有age、gender等属性,那么我们需要将这些属性都注入到BeanDefinition当中,进行存储。

在BeanDefinition中增加和bean属性对应的PropertyValues,实例化bean之后,为bean填充属性(AbstractAutowireCapableBeanFactory#applyPropertyValues)。

这里的PropertyValue有两个属性:String name, Object value。

1.5 为bean注入bean

当我阅读源码到这里的时候,觉得万事开头难是真的,从开始的迷茫,到静下心来看到这里,觉得还是非常好理解的。

增加BeanReference类,包装一个bean对另一个bean的引用。实例化beanA后填充属性时,若PropertyValue#value为BeanReference,引用beanB,则先去实例化beanB。 由于不想增加代码的复杂度提高理解难度,暂时不支持循环依赖,后面会在高级篇中解决该问题。

这里的BeanReference当中,只有一个属性:beanName。

1.6 资源和资源加载器

Resource是资源的抽象和访问接口,简单写了三个实现类:

  • FileSystemResource,文件系统资源的实现类,得到文件系统输入流。
  • ClassPathResource,classpath下资源的实现类
  • UrlResource,对java.net.URL进行资源定位的实现类

ResourceLoader接口则是资源查找定位策略的抽象,DefaultResourceLoader是其默认实现类。

1.7 在xml文件中定义bean

当我们有了资源加载器,就可以在xml格式配置文件中声明式地定义bean的信息,资源加载器读取xml文件,解析出bean的信息,然后往容器中注册BeanDefinition。

BeanDefinitionReader 是读取bean定义信息的抽象接口,XmlBeanDefinitionReader 是从xml文件中读取的实现类。BeanDefinitionReader 需要有获取资源的能力,且读取bean定义信息后需要往容器中注册BeanDefinition,因此BeanDefinitionReader 的抽象实现类AbstractBeanDefinitionReader拥有ResourceLoader和BeanDefinitionRegistry两个属性。

由于从xml文件中读取的内容是String类型,所以属性仅支持String类型和引用其他Bean。后面会讲到类型转换器,实现类型转换。

1.8 BeanFactoryPostProcessor和BeanPostProcessor

BeanFactoryPostProcessor和BeanPostProcessor是spring框架中具有重量级地位的两个接口,理解了这两个接口的作用,基本就理解spring的核心原理了。为了降低理解难度分两个小节实现。

BeanFactoryPostProcessor是spring提供的容器扩展机制,允许我们在bean实例化之前修改bean的定义信息即BeanDefinition的信息。其重要的实现类有PropertyPlaceholderConfigurer和CustomEditorConfigurer,PropertyPlaceholderConfigurer的作用是用properties文件的配置值替换xml文件中的占位符,CustomEditorConfigurer的作用是实现类型转换。

ini 复制代码
// CustomBeanFactoryPostProcessor
BeanDefinition personBeanDefinition = beanFactory.getBeanDefinition("person");  
PropertyValues propertyValues = personBeanDefinition.getPropertyValues();  
//将person的name属性改为ivy  
propertyValues.addPropertyValue(new PropertyValue("name", "ivy"));

也就是说:我们可以从BeanFactory当中拿到Beandefination,然后对其中的属性进行修改。

BeanPostProcessor也是spring提供的容器扩展机制,不同于BeanFactoryPostProcessor的是,BeanPostProcessor在bean实例化后修改bean或替换bean。BeanPostProcessor是后面实现AOP的关键。

来看看借口当中的代码:

javascript 复制代码
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

总结,BeanFactoryPostProcessor允许我们在Bean实例化之前进行操作,BeanPostProcessor允许我们在Bean实例化之后进行操作。

1.9 应用上下文ApplicationContext

应用上下文ApplicationContext是spring中较之于BeanFactory更为先进的IOC容器,ApplicationContext除了拥有BeanFactory的所有功能外,还支持特殊类型bean如上一节中的BeanFactoryPostProcessor和BeanPostProcessor的自动识别、资源加载、容器事件和监听器、国际化支持、单例bean自动初始化等。总结如下:

  • ApplicationContextSpringBeanFactory之外的另一个核心接口或容器;
  • 其允许容器通过应用程序上下文环境 创建、获取、管理bean
  • 为应用程序提供配置的中央接口。
  • 在应用程序运行时 这是只读 的,但如果实现支持这一点,则可以重新加载
scss 复制代码
@Override  
public void refresh() throws BeansException {  
    //创建 BeanFactory,并加载 BeanDefinition  
    refreshBeanFactory();  
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();  
  
    //添加ApplicationContextAwareProcessor,让继承自ApplicationContextAware的bean能感知bean  
    beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));  
  
    //在bean实例化之前,执行BeanFactoryPostProcessor  
    invokeBeanFactoryPostProcessors(beanFactory);  
  
    //BeanPostProcessor需要提前与其他bean实例化之前注册  
    registerBeanPostProcessors(beanFactory);  
  
    //初始化事件发布者  
    initApplicationEventMulticaster();  
  
    //注册事件监听器  
    registerListeners();  
  
    //注册类型转换器和提前实例化单例bean  
    finishBeanFactoryInitialization(beanFactory);  
  
    //发布容器刷新完成事件  
    finishRefresh();  
}

详细解释:

refreshBeanFactory():创建DefaultListableBeanFactory,以及从xml文件当中读取Bean。

ConfigurableListableBeanFactory beanFactory = getBeanFactory():取出上一步构建好的factory。

addBeanPostProcessor(new ApplicationContextAwareProcessor(this)):这是手工提前注册一个 BeanPostProcessor,目的是让实现了 ApplicationContextAware 的 bean 在初始化前拿到 ApplicationContext。背后时机:它会在每个 bean 的 initializeBean 阶段被调用(postProcessBeforeInitialization)。

因为它本身不是通过配置扫描出来再注册的,而是框架内建能力。

容器要保证:后续任何 bean 在创建时,都能经过这个处理器。

手动添加这个类的原因是:如果不提前加进去,那么实现了 ApplicationContextAware 的 bean 就拿不到 ApplicationContextsetApplicationContext(...) 不会被调用。

invokeBeanFactoryPostProcessors(beanFactory):这一步做两件事:

  1. getBeansOfType(BeanFactoryPostProcessor.class) 找出所有 BFPP
  2. 逐个执行 postProcessBeanFactory(beanFactory)
    关键点:这个实现里的 getBeansOfType() 会直接 getBean(beanName),也就是查找过程会触发这些处理器自身的实例化。
    为什么这一步需要提前: 因为 BFPP 主要改的是 BeanDefinition 元数据(例如属性、scope、占位符解析等),要在普通 bean 大规模实例化前完成。

registerBeanPostProcessors(beanFactory):逻辑类似:

  1. getBeansOfType(BeanPostProcessor.class) 找出全部 BPP(同样会触发这些 BPP bean 实例化)
  2. addBeanPostProcessor(...) 注册到工厂内部列表
    为什么必须在普通 bean 前注册:
    因为后续 bean 创建过程会反复经过 BPP 回调(AOP 代理、@Autowired 注入扩展、Aware 回调等都在这条链上)。

initApplicationEventMulticaster():初始化事件广播器:

  • 创建 SimpleApplicationEventMulticaster(beanFactory)
  • 以单例名 applicationEventMulticaster 注册到容器
    背后作用:后面 publishEvent() 就会委托给它做多播分发。

registerListeners()

  • getBeansOfType(ApplicationListener.class) 找到监听器(会触发监听器 bean 创建)
  • 全部注册到 multicaster 的监听集合里
    SimpleApplicationEventMulticaster 分发时会按泛型类型判断监听器是否支持该事件,再调用 onApplicationEvent

finishBeanFactoryInitialization(beanFactory):包含两件事:

  1. 设置类型转换器:如果容器里有名为 conversionService 的 bean 且实现了 ConversionService,就注入给 BeanFactory。这个转换器会在属性注入时用于类型转换(String -> int 等)。
  2. preInstantiateSingletons():遍历所有 BeanDefinition,把"单例且非懒加载"全部提前创建。
    getBean() 进入完整生命周期(非常关键):
  • 先查单例缓存

  • 没有就 createBean

  • createBean 里会走:

    • 实例化前扩展(可返回代理)
    • 实例化对象
    • 提前暴露对象工厂(解决循环依赖)
    • 属性填充(含 BeanReference 递归依赖注入、类型转换)
    • 初始化前后 BPP
    • InitializingBean / init-method
    • 注册销毁回调
    • 放入单例池

finishRefresh():发布容器刷新完成事件 ContextRefreshedEvent:然后 multicaster 会把这个事件推给匹配的 ApplicationListener

  • 先 BFPP,后 BPP,再普通 bean:先改"定义",再挂"拦截器",最后造对象。
  • 先初始化事件系统,再注册监听器,再发 refresh 事件:否则 ContextRefreshedEvent 无人接收。
  • ConversionService 必须在大规模属性注入前准备好:否则注入时无法转换类型。

1.10 bean的初始化和销毁方法

在spring中,定义bean的初始化和销毁方法有三种方法:

  • 在xml文件中制定init-method和destroy-method
  • 继承自InitializingBean和DisposableBean
  • 在方法上加注解PostConstruct和PreDestroy

第三种通过BeanPostProcessor实现,在扩展篇中实现,本节只实现前两种。

针对第一种在xml文件中指定初始化和销毁方法的方式,在BeanDefinition中增加属性initMethodName和destroyMethodName。

初始化方法在AbstractAutowireCapableBeanFactory#invokeInitMethods执行。DefaultSingletonBeanRegistry中增加属性disposableBeans保存拥有销毁方法的bean,拥有销毁方法的bean在AbstractAutowireCapableBeanFactory#registerDisposableBeanIfNecessary中注册到disposableBeans中。

为了确保销毁方法在虚拟机关闭之前执行,向虚拟机中注册一个钩子方法,查看AbstractApplicationContext#registerShutdownHook(非web应用需要手动调用该方法)。当然也可以手动调用ApplicationContext#close方法关闭容器。

到此为止,bean的生命周期如下:

1.11 Aware接口

Aware是感知、意识的意思,Aware接口是标记性接口,其实现子类能感知容器相关的对象。常用的Aware接口有BeanFactoryAware和ApplicationContextAware,分别能让其实现者感知所属的BeanFactory和ApplicationContext。

让实现BeanFactoryAware接口的类能感知所属的BeanFactory,实现比较简单,查看AbstractAutowireCapableBeanFactory#initializeBean前三行。

实现ApplicationContextAware的接口感知ApplicationContext,是通过BeanPostProcessor。由bean的生命周期可知,bean实例化后会经过BeanPostProcessor的前置处理和后置处理。定义一个BeanPostProcessor的实现类ApplicationContextAwareProcessor,在AbstractApplicationContext#refresh方法中加入到BeanFactory中,在前置处理中为bean设置所属的ApplicationContext。

改用dom4j解析xml文件。

至止,bean的生命周期如下:

1.12 bean作用域,增加prototype的支持

每次向容器获取prototype作用域bean时,容器都会创建一个新的实例。在BeanDefinition中增加描述bean的作用域的字段scope,创建prototype作用域bean时(AbstractAutowireCapableBeanFactory#doCreateBean),不往singletonObjects中增加该bean。prototype作用域bean不执行销毁方法,查看AbstractAutowireCapableBeanFactory#registerDisposableBeanIfNecessary方法。

1.13 FactoryBean

FactoryBean是一种特殊的bean,当向容器获取该bean时,容器不是返回其本身,而是返回其FactoryBean#getObject方法的返回值,可通过编码方式定义复杂的bean。

实现逻辑比较简单,当容器发现bean为FactoryBean类型时,调用其getObject方法返回最终bean。当FactoryBean#isSingleton==true,将最终bean放进缓存中,下次从缓存中获取。改动点见AbstractBeanFactory#getBean。

1.14 容器事件和事件监听器

ApplicationContext容器提供了完善的事件发布和事件监听功能。

ApplicationEventMulticaster接口是注册监听器和发布事件的抽象接口,AbstractApplicationContext包含其实现类实例作为其属性,使得ApplicationContext容器具有注册监听器和发布事件的能力。在AbstractApplicationContext#refresh方法中,会实例化ApplicationEventMulticaster、注册监听器并发布容器刷新事件ContextRefreshedEvent;在AbstractApplicationContext#doClose方法中,发布容器关闭事件ContextClosedEvent。

2 基础篇:AOP

2.1 切点表达式

Joinpoint,织入点,指需要执行代理操作的某个类的某个方法(仅支持方法级别的JoinPoint);Pointcut是JoinPoint的表述方式,能捕获JoinPoint。

最常用的切点表达式是AspectJ的切点表达式。需要匹配类,定义ClassFilter接口;匹配方法,定义MethodMatcher接口。PointCut需要同时匹配类和方法,包含ClassFilter和MethodMatcher,AspectJExpressionPointcut是支持AspectJ切点表达式的PointCut实现,简单实现仅支持execution函数。

2.2 基于JDK的动态代理

AopProxy是获取代理对象的抽象接口,JdkDynamicAopProxy的基于JDK动态代理的具体实现。TargetSource,被代理对象的封装。MethodInterceptor,方法拦截器,是AOP Alliance的"公民",顾名思义,可以拦截方法,可在被代理执行的方法前后增加代理行为。

JDK 动态代理只能代理实现了InvocationHandler接口的类,在执行时获得对方的类和方法,并且重写Invoke方法,执行前置/后置处理, 完成动态增强。

2.3 基于CGLIB的动态代理

基于CGLIB的动态代理实现逻辑也比较简单,查看CglibAopProxy。与基于JDK的动态代理在运行期间为接口生成对象的代理对象不同,基于CGLIB的动态代理能在运行期间动态构建字节码的class文件,为类生成子类,因此被代理类不需要继承自任何接口。

核心原理:

  • 运行时用字节码技术(底层常见是 ASM)生成一个类:TargetClass$$EnhancerByCGLIB$$...
  • 这个类 extends 目标类
  • 在子类里重写可代理的方法
  • 重写的方法里先执行拦截器逻辑,再决定是否调用父类原方法

所以它是"子类增强",而 JDK Proxy 是"接口转发"。

  • Enhancer:代理类生成器
  • MethodInterceptor:方法拦截器(相当于 JDK 的 InvocationHandler
  • MethodProxy:更高效地调用原方法(比纯反射通常更快)
ini 复制代码
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 前置增强
        Object result = proxy.invokeSuper(obj, args); // 调用父类原方法
        // 后置增强
        return result;
    }
});
TargetClass proxy = (TargetClass) enhancer.create();

方法调用链:

调用 proxy.doSomething() 时:

  1. 进入 CGLIB 生成的子类重写方法
  2. 重写方法转到 intercept(...)
  3. 你在 intercept 里写前置/后置/异常增强
  4. 通过 proxy.invokeSuper(obj, args) 调用目标原始逻辑
  5. 返回结果

2.4 AOP代理工厂

增加AOP代理工厂ProxyFactory,由AdvisedSupport#proxyTargetClass属性决定使用JDK动态代理还是CGLIB动态代理。

2.5 几种常用的Advice:BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice...

Spring将AOP联盟中的Advice细化出各种类型的Advice,常用的有BeforeAdvice/AfterAdvice/AfterReturningAdvice/ThrowsAdvice,我们可以通过扩展MethodInterceptor来实现。

只简单实现BeforeAdvice,有兴趣的同学可以帮忙实现另外几种Advice。定义MethodBeforeAdviceInterceptor拦截器,在执行被代理方法之前,先执行BeforeAdvice的方法。

java 复制代码
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice {  
  
    private MethodBeforeAdvice advice;  
  
    public MethodBeforeAdviceInterceptor() {  
    }  
  
    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {  
       this.advice = advice;  
    }  
  
    public void setAdvice(MethodBeforeAdvice advice) {  
       this.advice = advice;  
    }  
  
    @Override  
    public Object invoke(MethodInvocation mi) throws Throwable {  
       //在执行被代理方法之前,先执行before advice操作  
       this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());  
       return mi.proceed();  
    }  
}

2.6 PointcutAdvisor:Pointcut和Advice的组合

Advisor是包含一个Pointcut和一个Advice的组合,Pointcut用于捕获JoinPoint,Advice决定在JoinPoint执行某种操作。实现了一个支持aspectj表达式的AspectJExpressionPointcutAdvisor。

2.7 动态代理融入bean生命周期

结合前面讲解的bean的生命周期,BeanPostProcessor处理阶段可以修改和替换bean,正好可以在此阶段返回代理对象替换原对象。不过我们引入一种特殊的BeanPostProcessor------InstantiationAwareBeanPostProcessor,如果InstantiationAwareBeanPostProcessor处理阶段返回代理对象,会导致短路,不会继续走原来的创建bean的流程,具体实现查看AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation。

DefaultAdvisorAutoProxyCreator是处理横切逻辑的织入返回代理对象的InstantiationAwareBeanPostProcessor实现类,当对象实例化时,生成代理对象并返回。

至此,bean的生命周期如下:

3 扩展篇

3.1 PropertyPlaceholderConfigurer

经常需要将配置信息配置在properties文件中,然后在XML文件中以占位符的方式引用。

实现思路很简单,在bean实例化之前,编辑BeanDefinition,解析XML文件中的占位符,然后用properties文件中的配置值替换占位符。而BeanFactoryPostProcessor具有编辑BeanDefinition的能力,因此PropertyPlaceholderConfigurer继承自BeanFactoryPostProcessor。

3.2 包扫描

结合bean的生命周期,包扫描只不过是扫描特定注解的类,提取类的相关信息组装成BeanDefinition注册到容器中。

在XmlBeanDefinitionReader中解析<context:component-scan />标签,扫描类组装BeanDefinition然后注册到容器中的操作在ClassPathBeanDefinitionScanner#doScan中实现。

3.3 @Value注解

注解@Value和@Autowired通过BeanPostProcessor处理。InstantiationAwareBeanPostProcessor增加postProcessPropertyValues方法,在bean实例化之后设置属性之前执行,查看AbstractAutowireCapableBeanFactory#doCreateBean方法。

ini 复制代码
//处理@Value注解  
Class<?> clazz = bean.getClass();  
Field[] fields = clazz.getDeclaredFields();  
for (Field field : fields) {  
    Value valueAnnotation = field.getAnnotation(Value.class);  
    if (valueAnnotation != null) {  
       Object value = valueAnnotation.value();  
       value = beanFactory.resolveEmbeddedValue((String) value);  
  
       //类型转换  
       Class<?> sourceType = value.getClass();  
       Class<?> targetType = (Class<?>) TypeUtil.getType(field);  
       ConversionService conversionService = beanFactory.getConversionService();  
       if (conversionService != null) {  
          if (conversionService.canConvert(sourceType, targetType)) {  
             value = conversionService.convert(value, targetType);  
          }  
       }  
  
       BeanUtil.setFieldValue(bean, field.getName(), value);  
    }  
}

增加AutowiredAnnotationBeanPostProcessor用于处理注解@Value,@Autowired的处理在下一节实现,在ClassPathBeanDefinitionScanner#doScan将其添加到容器中。查看AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues,其中字符解析器StringValueResolver在PropertyPlaceholderConfigurer中添加到BeanFactory中。

3.4 @Autowired注解

核心代码当中可以看到

ini 复制代码
//处理@Autowired注解  
for (Field field : fields) {  
    Autowired autowiredAnnotation = field.getAnnotation(Autowired.class);  
    if (autowiredAnnotation != null) {  
       Class<?> fieldType = field.getType();  
       String dependentBeanName = null;  
       Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class);  
       Object dependentBean = null;  
       if (qualifierAnnotation != null) {  
          dependentBeanName = qualifierAnnotation.value();  
          dependentBean = beanFactory.getBean(dependentBeanName, fieldType);  
       } else {  
          dependentBean = beanFactory.getBean(fieldType);  
       }  
       BeanUtil.setFieldValue(bean, field.getName(), dependentBean);  
    }  
}

3.5 类型转换(一)

spring在org.springframework.core.convert.converter包中定义了三种类型转换器接口:Converter、ConverterFactory、GenericConverter。

3.5.1 Converter

csharp 复制代码
public interface Converter<S, T> {
	/**
	 * 类型转换
	 */
	T convert(S source);
}

比较简单的例子:

ini 复制代码
Integer num = new StringToIntegerConverter().convert("999");

3.5.2 ConverterFactory

csharp 复制代码
public interface ConverterFactory<S, R> {
	<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

Converter<S,T>接口适合一对一的类型转换,如果要将String类型转换为Ineger/Long/Float/Double/Decimal等类型,就要实现一系列的StringToInteger/StringToLongConverter/StringToFloatConverter转换器,非常不优雅。

ConverterFactory接口则适合一对多的类型转换,可以将一种类型转换为另一种类型及其子类。比如将String类型转换为Ineger/Long/Float/Double/Decimal等Number类型时,只需定义一个ConverterFactory转换器:

typescript 复制代码
public class StringToNumberConverterFactory implements ConverterFactory<String, Number> {

	@Override
	public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
		return new StringToNumber<T>(targetType);
	}

	private static final class StringToNumber<T extends Number> implements Converter<String, T> {

		private final Class<T> targetType;

		public StringToNumber(Class<T> targetType) {
			this.targetType = targetType;
		}

		@Override
		public T convert(String source) {
			if (source.length() == 0) {
				return null;
			}

			if (targetType.equals(Integer.class)) {
				return (T) Integer.valueOf(source);
			} else if (targetType.equals(Long.class)) {
				return (T) Long.valueOf(source);
			}
			//TODO 其他数字类型

			else {
				throw new IllegalArgumentException(
						"Cannot convert String [" + source + "] to target class [" + targetType.getName() + "]");
			}
		}
	}

}

3.5.3 GenericConverter

vbnet 复制代码
public interface GenericConverter {

	Set<ConvertiblePair> getConvertibleTypes();

	Object convert(Object source, Class sourceType, Class targetType);
}

String类型转换为Boolean类型的实现类:

typescript 复制代码
public class StringToBooleanConverter implements GenericConverter {
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(String.class, Boolean.class));
	}

	@Override
	public Object convert(Object source, Class sourceType, Class targetType) {
		return Boolean.valueOf((String) source);
	}
}

使用:

vbnet 复制代码
Boolean flag = new StringToBooleanConverter().convert("true", String.class, Boolean.class);

ConversionService是类型转换体系的核心接口,将以上三种类型转换器整合到一起,GenericConversionService是其实现类,DefaultConversionService在GenericConversionService的基础上添加内置转换器。

3.6 类型转换(二)

上一节实现了spring中的类型转换体系,本节将类型转换的能力整合到容器中。

为了方便使用,提供了创建ConversionService的FactoryBean------ConversionServiceFactoryBean。

如果有定义ConversionService,在AbstractApplicationContext#finishBeanFactoryInitialization方法中设置到容器中。

类型转换的时机有两个:

  • 为bean填充属性时,见AbstractAutowireCapableBeanFactory#applyPropertyValues
  • 处理@Value注解时,见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues

你可能会有疑问,如果没有定义ConversionService,是怎么进行基本类型的转换的?其实spring为了向下兼容保留了一套比较旧的类型转换机制,没有定义ConversionService时会使用其进行基本类型的转换工作,不必关注旧的类型转换机制。

4 高级篇

4.1 解决循环依赖问题(一):没有代理对象

三级缓存与循环依赖详解

kotlin 复制代码
public class A {

	private B b;

	//getter and setter
}
kotlin 复制代码
public class B {

	private A a;

	//getter and setter
}

A依赖B,B又依赖A,循环依赖。容器加载时会执行依赖流程:

  • 实例化A,发现依赖B,然后实例化B
  • 实例化B,发现依赖A,然后实例化A
  • 实例化A,发现依赖B,然后实例化B
  • ...
    死循环直至栈溢出。

解决该问题的关键在于何时将实例化后的bean放进容器中,设置属性前还是设置属性后。现有的执行流程,bean实例化后并且设置属性后会被放进singletonObjects单例缓存中。如果我们调整一下顺序,当bean实例化后就放进singletonObjects单例缓存中,提前暴露引用,然后再设置属性,就能解决上面的循环依赖问题,执行流程变为:

  • 步骤一:getBean(a),检查singletonObjects是否包含a,singletonObjects不包含a,实例化A放进singletonObjects,设置属性b,发现依赖B,尝试getBean(b)
  • 步骤二:getBean(b),检查singletonObjects是否包含b,singletonObjects不包含b,实例化B放进singletonObjects,设置属性a,发现依赖A,尝试getBean(a)
  • 步骤三:getBean(a),检查singletonObjects是否包含a,singletonObjects包含a,返回a
  • 步骤四:步骤二中的b拿到a,设置属性a,然后返回b
  • 步骤五:步骤一中的a拿到b,设置属性b,然后返回a

可见调整bean放进singletonObjects(人称一级缓存)的时机到bean实例化后即可解决循环依赖问题。但为了和spring保持一致,我们增加一个二级缓存earlySingletonObjects,在bean实例化后将bean放进earlySingletonObjects中(见AbstractAutowireCapableBeanFactory#doCreateBean方法第6行),getBean()时检查一级缓存singletonObjects和二级缓存earlySingletonObjects中是否包含该bean,包含则直接返回(见AbstractBeanFactory#getBean第1行)。

单测见CircularReferenceWithoutProxyBeanTest#testCircularReference。

增加二级缓存,不能解决有代理对象时的循环依赖。原因是放进二级缓存earlySingletonObjects中的bean是实例化后的bean,而放进一级缓存singletonObjects中的bean是代理对象(代理对象在BeanPostProcessor#postProcessAfterInitialization中返回),两个缓存中的bean不一致。比如上面的例子,如果A被代理,那么B拿到的a是实例化后的A,而a是被代理后的对象,即b.getA() != a,见单测CircularReferenceWithProxyBeanTest。


看到这里了。。。下次继续,我要 ALL IN 几天修改盲审论文和答辩。同时抽一点时间把单元测试的部分补齐,测试才是真的能够加深理解的部分。先记录到这里

4.2 解决循环依赖问题(二):有代理对象

解决有代理对象时的循环依赖问题,需要提前暴露代理对象的引用,而不是暴露实例化后的bean的引用(这是上节的遗留问题的原因,应该提前暴露A的代理对象的引用)。

spring中用singletonFactories(一般称第三级缓存)解决有代理对象时的循环依赖问题。在实例化后提前暴露代理对象的引用(见AbstractAutowireCapableBeanFactory#doCreateBean方法第6行)。

getBean()时依次检查一级缓存singletonObjects、二级缓存earlySingletonObjects和三级缓存singletonFactories中是否包含该bean。如果三级缓存中包含该bean,则挪至二级缓存中,然后直接返回该bean。见AbstractBeanFactory#getBean方法第1行。

最后将代理bean放进一级缓存singletonObjects,见AbstractAutowireCapableBeanFactory第104行。

单测见CircularReferenceWithProxyBeanTest。

4.3 支持懒加载和多切面增强(By @zqczgl)

4.3.1 懒加载

事实上,并不是所有的bean在初始化容器的时候都会创建。随着项目规模的不断扩大,bean的数目也越来越多。如果每次启动容器都需要加载大量的bean,这无疑会带来大量的资源浪费。所有spring提供了懒加载机制,我们可以将我们认为暂时用不到的bean设为懒加载,这样只有在我们需要这个bean的时候这个bean才会被创建。

4.3.2 多个切面匹配同一方法

虽然在前面我们完成了对方法的增强,但并不完美。我们的目前的代码只能支持对方法的单个增强。作为spring的核心功能如果不支持多切面的话有点太别扭了。spring利用了拦截器链来完成了对多个切面的支持。

4.3.2.1 ProxyFactory

让我们从ProxyFactory开始,来看一下代理对象的整个创建流程。至于为什么从ProxyFactory开,这是因为代理对象最终是用ProxyFactory的getProxy()函数来获得的。

scala 复制代码
public class ProxyFactory extends AdvisedSupport{


	public ProxyFactory() {
	}

	public Object getProxy() {
		return createAopProxy().getProxy();
	}

	private AopProxy createAopProxy() {
		if (this.isProxyTargetClass()||this.getTargetSource().getTargetClass().length==0) {
			return new CglibAopProxy(this);
		}
		return new JdkDynamicAopProxy(this);
	}
}

为了更贴合spring的实现,这里更改了ProxyFactory使其继承了AdvisedSupport,正如spring源码中做的那样。

4.3.2.2 基于JDK动态代理

ProxyFactory只是简单的做了下选择,当我们设置proxyTargetClass属性或者被代理对象没有接口时会调用cjlib动态代理,否则调用jdk动态代理。二者实现并没有太大区别,这里只贴出jdk动态代理的实现。

typescript 复制代码
	public Object getProxy() {
		return Proxy.newProxyInstance(getClass().getClassLoader(), advised.getTargetSource().getTargetClass(), this);
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 获取目标对象
		Object target=advised.getTargetSource().getTarget();
		Class<?> targetClass = target.getClass();
		Object retVal = null;
		// 获取拦截器链
		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
		if(chain==null||chain.isEmpty()){
			return method.invoke(target, args);
		}else{
			// 将拦截器统一封装成ReflectiveMethodInvocation
			MethodInvocation invocation =
					new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
			// Proceed to the joinpoint through the interceptor chain.
			// 执行拦截器链
			retVal = invocation.proceed();
		}
		return retVal;
	}

jdk动态代理可以分为获取拦截器链,将拦截器统一封装成ReflectiveMethodInvocation,执行拦截器链三部分。我们来逐一看一下这三部分。

4.3.2.2.1 1.获取拦截器链

首先将获取到所有与当前method匹配的advice(增强),跟踪getInterceptorsAndDynamicInterceptionAdvice代码,我们发现Spring AOP也使用缓存进行提高性能,如果该方法已经获取过拦截器,则直接取缓存,否则通过advisorChainFactory获取拦截器链。AdvisorChainFactory是用来获得拦截器链接口。它的一个实现类为DefaultAdvisorChainFactory

AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice:

ini 复制代码
	public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) {
		Integer cacheKey=method.hashCode();
		List<Object> cached = this.methodCache.get(cacheKey);
		if (cached == null) {
			cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
					this, method, targetClass);
			this.methodCache.put(cacheKey, cached);
		}
		return cached;
	}

整体代码并不复杂,首先获取所有Advisor(切面),通过pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)校验当前代理对象是否匹配该Advisor,再通过pointcutAdvisor.getPointcut().getMethodMatcher()校验是否匹配当前调用method。如果通过校验,则提取advisor中的interceptors增强,添加到interceptorList中。这里可能有读者会疑惑,我们明明是要获取MethodInterceptor,可AdvisedSupport的getAdvice()返回的是Advice(增强),其实如果我们点开MethodInterceptor的源码,我们会发现MethodInterceptor继承了Interceptor接口,而Interceptor又继承了Advice接口。因为这里的Advice和MethodInterceptor我们都是用的AOP联盟的接口,所以特此说明一下。

DefultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice

ini 复制代码
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(AdvisedSupport config, Method method, Class<?> targetClass) {
        Advisor[] advisors = config.getAdvisors().toArray(new Advisor[0]);
        List<Object> interceptorList = new ArrayList<>(advisors.length);
        Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
        for (Advisor advisor : advisors) {
            if (advisor instanceof PointcutAdvisor) {
                // Add it conditionally.
                PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                // 校验当前Advisor是否适用于当前对象
                if (pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                    boolean match;
                    // 校验Advisor是否应用到当前方法上
                    match = mm.matches(method,actualClass);
                    if (match) {
                        MethodInterceptor interceptor = (MethodInterceptor) advisor.getAdvice();
                            interceptorList.add(interceptor);
                    }
                }
            }
        }
        return interceptorList;
    }
4.3.2.2.2 2.将拦截器封装成ReflectiveMethodInvocation

这里也是重写了ReflectiveMethodInvocation的实现,来支持多切面。

ini 复制代码
	public ReflectiveMethodInvocation(Object proxy,Object target, Method method, Object[] arguments,Class<?> targetClass,List<Object> chain) {
		this.proxy=proxy;
		this.target = target;
		this.method = method;
		this.arguments = arguments;
		this.targetClass=targetClass;
		this.interceptorsAndDynamicMethodMatchers=chain;
	}
4.3.2.2.3 3.执行拦截器链

spring能够保证多个切面同时匹配同一方法的而不出现乱序的关键就在下面一段代码了。

ReflectiveMethodInvocation#proceed()

kotlin 复制代码
	public Object proceed() throws Throwable {
		// 初始currentInterceptorIndex为-1,每调用一次proceed就把currentInterceptorIndex+1
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			// 当调用次数 = 拦截器个数时
			// 触发当前method方法
			return method.invoke(this.target, this.arguments);
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		// 普通拦截器,直接触发拦截器invoke方法
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
	}

我们看到,MethodInvocation只是简单的将拦截器链的所有拦截器一一执行,最后再触发当前的method方法。这是很简单高效的方法,但问题是我们希望某些增强比如AfterReturningAdvice能够在方法执行完才被执行,这就涉及到不同增强的执行顺序的问题了。而MethodInvocation显然没有考虑顺序的问题,一个AfterReturningAdvice很可能在BeforeAdvice之前被调用。那么该如何保证顺序问题呢?

答案是,控制增强的调用顺序其实由每个拦截器负责,所以我们需要分析MethodBeforeAdviceInterceptorAfterReturningAdviceInterceptor

java 复制代码
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice {

	private MethodBeforeAdvice advice;

	public MethodBeforeAdviceInterceptor() {
	}

	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		this.advice = advice;
	}

	public void setAdvice(MethodBeforeAdvice advice) {
		this.advice = advice;
	}

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		return mi.proceed();
	}
}
java 复制代码
package org.springframework.aop.framework.adapter;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;

/**
 * @author zqc
 * @date 2022/12/20
 */
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice {

    private  AfterReturningAdvice advice;

    public AfterReturningAdviceInterceptor() {
    }

    public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
        this.advice = advice;
    }


    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }
}

看了源码大家应该就清楚了,拦截器链执行的顺序正时在各个拦截器的invoke方法中实现的。before会先执行advice增强方法再链式调用,这个比较好理解而after则是先执行链式调用,再调用advice增强方法,也就是一个递归的过程。和二叉树的遍历有些异曲同工之处。

4.3.2.3 多切面动态代理融入bean生命周期
java 复制代码
	public void testAutoProxy() throws Exception {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:auto-proxy.xml");

		//获取代理对象
		WorldService worldService = applicationContext.getBean("worldService", WorldService.class);
		worldService.explode();
	}

auto-proxy.xml:

ini 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	         http://www.springframework.org/schema/beans/spring-beans.xsd
		 http://www.springframework.org/schema/context
		 http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <bean id="worldService" class="org.springframework.test.service.WorldServiceImpl"/>

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

    <bean id="pointcutAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
        <property name="expression" value="execution(* org.springframework.test.service.WorldService.explode(..))"/>
        <property name="advice" ref="methodInterceptor"/>
    </bean>
    <bean id="pointcutAdvisor2" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
        <property name="expression" value="execution(* org.springframework.test.service.WorldService.explode(..))"/>
        <property name="advice" ref="methodInterceptor2"/>
    </bean>

    <bean id="methodInterceptor" class="org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
        <property name="advice" ref="beforeAdvice"/>
    </bean>
    <bean id="methodInterceptor2" class="org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor">
        <property name="advice" ref="afterAdvice"/>
    </bean>
    <bean id="afterAdvice" class="org.springframework.test.common.WorldServiceAfterReturnAdvice"/>
    <bean id="beforeAdvice" class="org.springframework.test.common.WorldServiceBeforeAdvice"/>

</beans>

输出:

vbnet 复制代码
BeforeAdvice: do something before the earth explodes
The null is going to explode
AfterAdvice: do something after the earth explodes

进程已结束,退出代码为 0

至此,我们已经解决多切面匹配同一方法的问题。

相关推荐
码农进化录10 小时前
Java 程序员的 AI 进化论 | LangChain4j 入门,Java 开发者的 AI 应用框架
java·spring boot·openai
Boop_wu14 小时前
SpringBoot 集成阿里云短信认证服务(个人)
spring boot·后端·阿里云
小蒜学长15 小时前
“守望自然”招募志愿者环保行动网站的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
weixin_BYSJ198716 小时前
【java项目分享】springboot阅读推荐平台10600
java·javascript·spring boot·python·django·flask·php
PC2005-cloud16 小时前
PgVector从入门到实战:用PostgreSQL做向量检索,附SpringBoot+MyBatisPlus整合
数据库·spring boot·postgresql
腾渊信息科技公司17 小时前
Spring Boot集成TDengine实战:工业时序数据存储选型与迁移方案
spring boot·后端·tdengine
zoyation17 小时前
Spring Boot集成OAuth2授权码模式的三种登录策略
java·spring boot·后端
xiaoqiMikko1 天前
Dependabot 面板全绿,不代表你的 Tomcat 没洞
java·spring boot
摇滚侠1 天前
SpringBoot 官网 阅读笔记 启用生产就绪功能 端点
spring boot·笔记·后端
evans在进步1 天前
HashMap 为什么线程不安全?ConcurrentHashMap 如何解决?
java·spring boot·spring