相关背景及资源:html
曹工说Spring Boot源码(1)-- Bean Definition究竟是什么,附spring思惟导图分享java
曹工说Spring Boot源码(2)-- Bean Definition究竟是什么,我们对着接口,逐个方法讲解git
曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,咱们来试一下spring
曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?json
曹工说Spring Boot源码(5)-- 怎么从properties文件读取beanapp
曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的ide
曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中获得了什么(上)spring-boot
工程结构图:测试
先给你们看看spring支持的xml配置,我列了个表格以下:
namespace | element |
---|---|
util | constant、property-path、list、set、map、properties |
context | property-placeholder、property-override、annotation-config、component-scan、load-time-weaver、spring-configured、mbean-export、mbean-server |
beans | import、bean、alias |
task | annotation-driven、scheduler、scheduled-tasks、executor |
cache | advice、annotation-driven |
aop | config、scoped-proxy、aspectj-autoproxy |
我标题的意思是,既然spring支持这么多种xml配置,那解析这些xml的代码,是不是有共性的呢?仍是说,就是很随意的,产品经理说要支持这个元素的解析,就写个分支呢?
看过我上讲的同窗应该知道,无论是什么元素,无论在哪一个namespace下,其对应的解析代码,都是一种类,这种类,叫作:BeanDefinitionParser
,这个类的接口以下:
org.springframework.beans.factory.xml.BeanDefinitionParser public interface BeanDefinitionParser { /** * 解析指定额element,注册其返回的BeanDefinition到BeanDefinitionRegistry * (使用参数ParserContext#getRegistry()获得BeanDefinitionRegistry) */ BeanDefinition parse(Element element, ParserContext parserContext); }
这个接口的实现类,至关多,除了beans命名空间下的xml元素,其余namespace下的xml元素的解析代码都实现了这个接口。
首先是util命名空间下:
其次是context命名空间:
这里面有你们熟悉的
这里就不一一列举了,因此你们知道了,每一个xml元素的解析器,都是实现了BeanDefinitionParser
,这个接口的方法,就是交给各个子类去实现:针对指定的xml元素,如何获取到对应的bean definition
。
有的xml元素,比较简单,好比上一篇提到的
bean definition
(factory bean);还有的xml元素,则是群攻魔法,好比<context:component-scan>这种,一把就能捞一大波
bean definition
上来。
本讲,咱们会继续从util namespace开始
,将比较常见的xml元素,一路扫过去
用法以下:
#test.properties name=xxx system
import lombok.Data; @Data public class TestPropertiesBean { private String appName; }
spring xml中以下配置: <util:properties id="properties" location="classpath:test.properties"/> <bean class="org.springframework.utilnamespace.TestPropertiesBean"> // 注意,这里的value,#{properties.name},点号前面引用了上面的properties bean的id,点号后面 // 是properties文件里key的名称 <property name="appName" value="#{properties.name}"></property> </bean>
测试类以下:
@Slf4j public class TestProperties { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:util-namespace-test-properties.xml"},false); context.refresh(); List<BeanDefinition> list = context.getBeanFactory().getBeanDefinitionList(); MyFastJson.printJsonStringForBeanDefinitionList(list); Object o = context.getBean(TestPropertiesBean.class); System.out.println(o); } }
输出以下:
TestPropertiesBean(appName=xxx system)
在UtilNamespaceHandler
中,咱们看看该元素对应的BeanDefinitionParser
是啥:
public class UtilNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser()); registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser()); registerBeanDefinitionParser("list", new ListBeanDefinitionParser()); registerBeanDefinitionParser("set", new SetBeanDefinitionParser()); registerBeanDefinitionParser("map", new MapBeanDefinitionParser()); registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser()); } }
ok! 是PropertiesBeanDefinitionParser
。
具体的解析过程,和上一讲里的
private static class PropertiesBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { // 这里就是指定了bean definition里的bean class @Override protected Class getBeanClass(Element element) { return PropertiesFactoryBean.class; } // 一些定制逻辑,无需操心 @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { super.doParse(element, parserContext, builder); Properties parsedProps = parserContext.getDelegate().parsePropsElement(element); builder.addPropertyValue("properties", parsedProps); String scope = element.getAttribute(SCOPE_ATTRIBUTE); if (StringUtils.hasLength(scope)) { builder.setScope(scope); } } }
这里其实,主要就是指定了beanClass
,其余逻辑都不甚重要。这里的beanClass就是PropertiesFactoryBean
,类型是一个工厂bean。
由于咱们的主题是,Spring解析xml文件,从中获得了什么,因此咱们不会进一步剖析实现,从对上面这个元素的解析来讲,就是获得了一个工厂bean
用法以下:
<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <util:list id="testList" list-class="java.util.ArrayList"> <value>a</value> <value>b</value> <value>c</value> </util:list> <bean id="testPropertiesBeanA" class="org.springframework.utilnamespace.TestPropertiesBean"> <property name="appName" value="xxx"/> </bean> <bean id="testPropertiesBeanB" class="org.springframework.utilnamespace.TestPropertiesBean"> <property name="appName" value="yyy"/> </bean> <util:list id="testBeanList" list-class="java.util.ArrayList"> <ref bean="testPropertiesBeanA"/> <ref bean="testPropertiesBeanB"/> </util:list> </beans>
测试代码:
@Slf4j public class TestUtilListElement { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( new String[]{"classpath:util-namespace-test-list.xml"},false); context.refresh(); Map<String, Object> map = context.getDefaultListableBeanFactory().getAllSingletonObjectMap(); log.info("singletons:{}", JSONObject.toJSONString(map)); List<BeanDefinition> list = context.getBeanFactory().getBeanDefinitionList(); MyFastJson.printJsonStringForBeanDefinitionList(list); Object bean = context.getBean("testList"); System.out.println("bean:" + bean); bean = context.getBean("testBeanList"); System.out.println("bean:" + bean); } }
输出以下:
23:32:06.396 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'testList' bean:[a, b, c] 23:32:06.396 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'testBeanList' bean:[TestPropertiesBean(appName=xxx), TestPropertiesBean(appName=yyy)]
咱们看看这两个bean的beanDefinitionParser
,
private static class ListBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { // 这里指定本bean的class,能够看到,这也是一个工厂bean @Override protected Class getBeanClass(Element element) { return ListFactoryBean.class; } //解析元素里的属性等 @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String listClass = element.getAttribute("list-class"); List parsedList = parserContext.getDelegate().parseListElement(element, builder.getRawBeanDefinition()); builder.addPropertyValue("sourceList", parsedList); if (StringUtils.hasText(listClass)) { builder.addPropertyValue("targetListClass", listClass); } String scope = element.getAttribute(SCOPE_ATTRIBUTE); if (StringUtils.hasLength(scope)) { builder.setScope(scope); } } }
回到题目,spring 从这个
咱们能够仔细看看beandefinition,我这里的测试类是用json输出了的:
{ "abstract": false, "autowireCandidate": true, "autowireMode": 0, "beanClassName": "org.springframework.beans.factory.config.ListFactoryBean", "constructorArgumentValues": { "argumentCount": 0, "empty": true, "genericArgumentValues": [], "indexedArgumentValues": {} }, "dependencyCheck": 0, "enforceDestroyMethod": true, "enforceInitMethod": true, "lazyInit": false, "lenientConstructorResolution": true, "methodOverrides": { "empty": true, "overrides": [] }, "nonPublicAccessAllowed": true, "primary": false, "propertyValues": { "converted": false, "empty": false, "propertyValueList": [ { "converted": false, "name": "sourceList", "optional": false, "value": [ { "beanName": "testPropertiesBeanA", "toParent": false }, { "beanName": "testPropertiesBeanB", "toParent": false } ] }, { "converted": false, "name": "targetListClass", "optional": false, "value": "java.util.ArrayList" } ] }, "prototype": false, "qualifiers": [], "resolvedAutowireMode": 0, "role": 0, "scope": "", "singleton": true, "synthetic": false }
从上面能够看出,beanClass是ListFactoryBean
,而咱们xml里配置的元素,则被解析后,存放到了propertyValues
,被做为了这个bean的属性对待。
util命名空间还有几个别的元素,好比map、set,都差很少,spring都把它们解析为了一个工厂bean。
工厂bean和普通bean的差异,会放到后面再说,下一讲,会继续讲解context命名空间的元素。
源码我放在:
欢迎你们和我一块儿学习spring/spring boot源码,有问题欢迎一块儿交流!