Spring源码-IOC之resolveBeanClass

版本 Spring Framework 6.0.9​

1. resolveBeanClass

resolveBeanClass是bean工厂类AbstractBeanFatory的方法,用于解析给定的bean定义的Bean类,返回bean的对象类型。
逻辑分为两部分,先从bean定义的beanClass属性获取,获取不到再调用doResolveBeanClass方法解析。

在这里插入图片描述

1.1 hasBeanClass

判断bean定义的beanClass属性是不是Class类型。
在这里插入图片描述

1.2 getBeanClass

验证bean定义的class属性是不是Class类型,不是则抛出异常。
在这里插入图片描述

2. doResolveBeanClass

doResolveBeanClass大致逻辑如下,更详细的看图注释:

  1. 从bean定义中的beanClass属性的类名className,调用evaluateBeanDefinitionString方法解析
  2. 通过放射获取类名className的对象类型

在这里插入图片描述

2.1 getBeanClassLoader

获取bean工厂的beanClassLoader属性值,类型是ClassLoader,beanClassLoader属性有默认值ClassUtils.getDefaultClassLoader()

在这里插入图片描述
ClassUtils#getDefaultClassLoader方法按顺序尝试获取线程上下文的类加载器、ClassUtils类的类加载器、系统加载器,一般情况下都是线程上下文类加载器。
在这里插入图片描述

2.2 getTempClassLoader

获取bean工厂的tempClassLoader属性值,类型是ClassLoader。
在这里插入图片描述
如果bean工厂存在loadTimeWeaver,实例化一个ContextTypeMatchClassLoader对象,赋值给bean工厂
在这里插入图片描述
在refresh的finishBeanFactoryInitialization阶段将templateClassLoader置空,而下面的preInstantiateSingletons方法是实例化单例对象,说明templateClassLoader的调用期间是早尚未创建实际实例。
在这里插入图片描述
另外ContextTypeMatchClassLoader类是DecoratingClassLoader的子类,如果typesToMatch入参不为空,则将其类名添加到临时类加载器的excludedClasses属性中。

在这里插入图片描述

2.3 getBeanClassName

获取bean定义的beanClass属性(类型是Object),三元表达式中如果beanClass是Class类型,则通过getName获取类名,否则强转成String类型。
在这里插入图片描述

2.4 evaluateBeanDefinitionString

如果bean工厂的表达式解析器不为空,则调用其evaluate方法解析。

在这里插入图片描述
在refresh的prepareBeanFactory阶段,会实例化一个StandardBeanExpressionResolver赋值给bean工厂的beanExpressionResolver属性,所以不为空。

在这里插入图片描述

2.4.1 getRegisteredScope

从bean工厂的scopes map中获取scopeName的关联值。
在这里插入图片描述

2.4.2 BeanExpressionContext

BeanExpressionContext对象保存了bean工厂和scope对象,在解析表达式期间作为上下文使用,可以从中获取所需实例。
在这里插入图片描述

2.5 evaluate 解析Spel表达式

从2.4 知道 beanExpressionResolver是一个StandardBeanExpressionResolver对象,StandardBeanExpressionResolver实现了BeanExpressionResolver接口,从接口方法定义:将给定的值作为表达式解析,否则直接返回value本身。

在这里插入图片描述

StandardBeanExpressionResolver#evaluate方法大致实现:

  1. 从缓存中获取表达式对象,没有则将表达式字符串解析成表达式对象
  2. 解析表达式对象,返回解析结果。
    在这里插入图片描述

在分析StandardBeanExpressionResolver#evaluate的源码前,先展示个一个例子,方便debug理解,例子的目的是在解析User的class属性时,可以调用UserClassNameUtil中的getUserClass方法,获取实际的User类型。

  • User.class
    在这里插入图片描述

  • UserClassNameUtil
    在这里插入图片描述

  • spelbeans.xml
    在这里插入图片描述

  • 启动类
    在这里插入图片描述
    运行结果如下,说明可以将 #{userClassNameUtil.getUserClass()} 解析成 org.springframework.learn.ioc.spel.User:
    在这里插入图片描述

2.5.1 解析UserClassNameUtil的class属性

从UserClassNameUtil的bean定义中获取beanClass属性,值为org.springframework.learn.ioc.spel.UserClassNameUtil。
在这里插入图片描述
在这里插入图片描述

TemplateAwareExpressionParser#parseExpression

第一次解析,expressionCache缓存结果为空,调用expressionParser的parseExpression方法,将“org.springframework.learn.ioc.spel.UserClassNameUtil”转换成Expression对象。
在这里插入图片描述

在上文 2.4章节提过当前StandardBeanExpressionResolver对象是在refresh的prepareBeanFactory阶段实例化,调用StandardBeanExpressionResolver有参构造时会为其属性expressionPrefix赋值一个新的SeplExpressionParser对象,是TemplateAwareExpressionParser的子类。
在这里插入图片描述
在这里插入图片描述

parseExpression方法入参有两个,value是表达式字符串“org.springframework.learn.ioc.spel.UserClassNameUtil”,beanExpressionParserContext属性值是StandardBeanExpressionResolver的内部实现类,一个前缀为#{,后缀为},认为当前要解析的表达式是为模板的ParserContext对象,
在这里插入图片描述
context是ParserContext对象,不为空,且siTemplate方法返回true,调用TemplateAwareExpressionParser#parseTemplate方法。如果不是模板方法则调用doParseExpression方法直接解析表达式字符串。
在这里插入图片描述
表达式字符串不为空,继续调用parseExpressions方法。
在这里插入图片描述

TemplateAwareExpressionParser#parseExpressions

由于userClassNameUtil对象的表达式字符串是org.springframework.learn.ioc.spel.UserClassNameUti,不包含前缀"#{",转换成持有表达式字符串原值的LiteralExpression对象,并添加到expressions集合中。parseExpressions方法返回expressions集合转成的数组。
在这里插入图片描述
在这里插入图片描述

将“org.springframework.learn.ioc.spel.UserClassNameUti”表达式字符串存储到LiteralExpression对象的literalValue属性值。
在这里插入图片描述

最终根据前缀#{ 后缀} 解析表达式字符串,返回一个LiteralExpression对象
在这里插入图片描述
在这里插入图片描述

StandardEvaluationContext

第一次解析bean类型,evaluationCache缓存为空,实例化一个StandardEvaluationContext对象与BeanExpressionContext表达式上下文对象关联并放入缓存。从上文知道BeanExpressionContext只保存了bean工厂和Scope对象。新创建的StandardEvaluationContext除了持有BeanExpressionContext外,添加了一些其他实例对象,增强解析功能。
在这里插入图片描述

LiteralExpression

expr变量是上面将表达式变量转换成表达式对象LiteralExpression,getValue是Expression接口的方法,该接口有三个实现类。
在这里插入图片描述

LiteralExpression#getValue(EvaluationContext context) 方法直接返回literalValue属性值,即表达式字符串。
在这里插入图片描述

解析表达式后的值evaluated与bean定义的beanClass类名一致。
在这里插入图片描述

2.5.2 解析User的class属性

从Use的bean定义中获取beanClass属性,值为org.springframework.learn.ioc.spel.UserClassNameUtil。
在这里插入图片描述
在这里插入图片描述

TemplateAwareExpressionParser#parseExpression

expressionCache缓存中也不存在#{userClassNameUtil.getUserClass()}表达式缓存与表达式对象Expression关系,调用SeplExpressionParser#parseExpression方法解析。
在这里插入图片描述
#{userClassNameUtil.getUserClass()}表达式字符串存在前缀和后缀,截取中间内容后调用doParseExpression方法解析。
在这里插入图片描述

SeplExpressionParser#doParseExpression

doParseExpression是个抽象方法,有两个子类实现了该方法,当前实例是SeplExpressionParser对象。
在这里插入图片描述

SpelExpressionParser#doParseExpression方法实际上调用另外一个子类InternalSpelExpressionParser的doParseExpression方法。
在这里插入图片描述

InternalSpelExpressionParser#doParseExpression

将输入的表达式字符串按照预定义的规则拆分成一系列的 Token 对象,构建对应的SpelNodeImpl对象树,并返回一个新的SpelExpression对象,保存表达式解析结果ast。
在这里插入图片描述

checkExpressionLength

如果判断入参string比设定的最大长度maxLength大,则抛出SpelEvaluationException。
在这里插入图片描述
maxLength在SpelParserConfiguration配置类实例化时,构造方法设定默认值10000
在这里插入图片描述

new Tokenizer(expressionString)

调用接受一个String类型的Tokenizer构造方法,保存要处理的原始字符串,然后往字符串后添加 \0 形成新字符串,用于遍历时定位结束为止,并记录新字符串的起始位置和结束为止。
在这里插入图片描述

tokenizer.process

解析表达式,将输入的表达式字符串按照预定义的规则拆分成一系列的 Token 对象。

  1. 将非字母、数字、下划线、美元符号的字符作为分隔符,拆分字符串,生成Token对象
  2. 分隔符根据不同类型的字符 执行相应操作.(部分生成Token对象)
    在这里插入图片描述

比如当前案例的原始字符userClassNameUtil.getUserClass(),添加 \0(空字符串) 后新字符串为“userClassNameUtil.getUserClass() ”,
,将非字母、数字、下划线、美元符号的字符作为分隔符,拆分字符串并生成Token,分隔符根据不同类型的字符 执行相应操作,也生成Token(基本上)。结果如下:

  • “userClassNameUtil” 的Token
  • “.”的Token
  • “getUserClass”的Token
  • “(”的Token
  • “)”的Token
    在这里插入图片描述
eatExpression

解析tokens中的所有token,构建对应的SpelNodeImpl对象树,当前案例的解析结果,根节点是CompoundExpression对象,其由两个子节点,分别是名称为userClassNameUtil的PropertyOrFieldReference对象和MethodReference对象。
在这里插入图片描述

peekToken

用于查看当前token流中的下一个未处理的token而不移动指针。这里用于检查是否还有未成功处理的Token对象,如果有则抛出SpelParseException异常。
在这里插入图片描述

StandardEvaluationContext

第一次解析UserClassNameUtil的class属性时,已经新建了一个StandardEvaluationContext对象放入evaluationCache map中,缓存不为空。
在这里插入图片描述

并且BeanExpressionContext类是重写equals和hashCode方法,hashCode的值是beanFactory的哈希码,当前工厂实例是一样的,哈希码一致且相等,可以获取到缓存中的StandardEvaluationContext对象。
在这里插入图片描述

SpelExpression

expr是解析ClassNameUtil.getUserClass()后新创建的SpelExpression对象,包含SpelNodeImpl对象树。
在这里插入图片描述

进入SpelExpression#getValue方法,当前案例中compiledAst为空,而ast是解析表达式后生成的语法对象树CompoundExpression,继续调用CompoundExpression#getValue方法。
在这里插入图片描述

SeplNodeImpl#getValue

ast.getValue调用其CompoundExpression父类的SeplNodeImpl#getValue方法,而getValueInternal是一个模板方法,由子类实现,调用CompoundExpression#getValueInternal方法
在这里插入图片描述
在这里插入图片描述

CompoundExpression#getValueInternal

getValueInternal方法里继续调用getValueRef方法。
在这里插入图片描述
getValue方法中从CompoundExpression的childrens集合中,先解析第一个节点获取结果值,接结果值压入上下文对象栈中方便引用,继续遍历获取结果值。直到最后一个节点才调用getValueRef方法,获取表达式真正的结果值引用对象。
本案例中CompoundExpression的childrens中有两个对象,PropertyOrFieldReference和MethodReference。
结果上看PropertyOrFieldReference#getValueInternal,从bean工厂中获取了UserClassNameUtil对象,再通过MethodReference#getValaueRef获取getUserClass方法的返回值"org.springframework.learn.ioc.spel.User。
在这里插入图片描述

PropertyOrFieldReference#getValueInternal

一直定位到PropertyOrFieldReference#readProperty方法,从上下文中获取的5个Accessors,经过过滤后剩下2个,BeanExpressionContextAccessor和ReflectivePropertyAccessor。
在这里插入图片描述
BeanExpressionContextAccessor#read实际调用的是bean工厂的getBean方法,获取UserClassNameUtil实例对象
在这里插入图片描述
在此处调用的Accessor,是实例化对象时添加进去的。以及addPropertyAccessor中初始化的ReflectivePropertyAccessor。
在这里插入图片描述
在这里插入图片描述

MethodReference#getValueRef

返回方法引用MethodValueRef,最后调用其getValue方法,通过反射调用getUserClass方法获取结果。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.6 !className.equals(evaluated)

当className与解析表达式的结果值不一致时,如果结果值是Class类型则直接返回,String类型则调用loadClass方法或通过ClassUtils.forName方法获取对象类型。根据上面案例,className是#{userClassNameUtil.getUserClass()},结果值是org.springframework.learn.ioc.spel.User。
在这里插入图片描述

2.7 resolveBeanClass

除了根据给定的类名称和类加载器加载并返回一个Java类,同时将加载结果缓存到bean定义中。
在这里插入图片描述

3. 总结

  1. bean定义的beanClass属性不为空并且是class类型直接返回。
  2. 如果存在类型匹配参数(typesToMatch),将动态加载器替换为临时类加载器,并标记为“freshResolve”(表示本次解析为新的或临时的)。
  3. 从RootBeanDefinition (mbd) 中获取Bean的类名称(className),如果类名称包含表达式(如前缀为#{},后缀为}),则对其进行动态解析。
  4. 如果解析后的类名称与原始类名称不同,则根据解析结果加载对应的类。若解析结果为一个Class对象,则直接返回;若为字符串,则用动态加载器加载对应类。不需要缓存结果到Bean定义中。
  5. 按照常规方式解析类名,并将解析得到的类信息缓存到Bean定义的 beanClass 属性中,然后返回这个类。
  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值