版本 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大致逻辑如下,更详细的看图注释:
- 从bean定义中的beanClass属性的类名className,调用evaluateBeanDefinitionString方法解析
- 通过放射获取类名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方法大致实现:
- 从缓存中获取表达式对象,没有则将表达式字符串解析成表达式对象
- 解析表达式对象,返回解析结果。
在分析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 对象。
- 将非字母、数字、下划线、美元符号的字符作为分隔符,拆分字符串,生成Token对象
- 分隔符根据不同类型的字符 执行相应操作.(部分生成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. 总结
- bean定义的beanClass属性不为空并且是class类型直接返回。
- 如果存在类型匹配参数(typesToMatch),将动态加载器替换为临时类加载器,并标记为“freshResolve”(表示本次解析为新的或临时的)。
- 从RootBeanDefinition (mbd) 中获取Bean的类名称(className),如果类名称包含表达式(如前缀为#{},后缀为}),则对其进行动态解析。
- 如果解析后的类名称与原始类名称不同,则根据解析结果加载对应的类。若解析结果为一个Class对象,则直接返回;若为字符串,则用动态加载器加载对应类。不需要缓存结果到Bean定义中。
- 按照常规方式解析类名,并将解析得到的类信息缓存到Bean定义的 beanClass 属性中,然后返回这个类。