随着 Spring Boot 的日渐流行,应用里的大部分配置都被隐藏了起来,我们仅需要关心真正的业务内容, Controller, Service, Repository,拿起键盘就是一通业务代码的Coding,具体的 Component Scan,View,PlaceHolder ... 都可以抛在脑后。但其实这种零配置在 Java 应用开发中,还真不太久。 「 由奢入俭难 」,不少开发者都经历过 Spring XML 配置的冗长,再回到这种配置确实不好受。
但有些时候,由于配置的内容在 Spring Boot这种零配置中并不能很好的实现,就需要我们仍使用 XML 的配置形式,然后再 ImportSource进来。或者一些项目受环境等影响,未使用Boot进行开发,此时也需要对配置有一定的了解。
那这次让我们往回看一些,看看在 XML 配置中,一些自定义的 Schema 内容,是如何融合到 Spring 中进行配置的。例如:
Spring data es
dubbo
还有许多这样的例子,我们不再一一罗列。但通过上述两个图,我们发现一个共同的特点:
都是通过schemaLocation将对应的schema引入
在对应的beans元素中增加更具体的自定义配置
那这些自定义的配置,是在什么时候工作的呢?如何校验是否配置正确?
我们来看 Spring 中包含一个名为 spring.handlers 的文件,所有的自定义扩展,都是通过这个文件生效的。spring 官方的aop, tx 也都是这个原理。
这个文件在哪呢?
如上图所示,是META-INF/spring.handlers。文件内容也超级简单:
http/://www.springframework.org/schema/data/elasticsearch=org.springframework.data.elasticsearch.config.ElasticsearchNamespaceHandler
前面是各个schema前缀,后面是schema 对应的解析类。这个spring.handlers文件是什么时候加载的呢?
这个也是发生在解析自定义配置文件过程中,有一个resolve的过程,此时会将当前classLoader对应的所有包含spring.handlers文件加载过来。
我们再来看这个解析类, 内容如下:
1public class ElasticsearchNamespaceHandler extends NamespaceHandlerSupport {
2 public ElasticsearchNamespaceHandler() {
3 }
4
5 public void init() {
6 RepositoryConfigurationExtension extension = new ElasticsearchRepositoryConfigExtension();
7 RepositoryBeanDefinitionParser parser = new RepositoryBeanDefinitionParser(extension);
8 this.registerBeanDefinitionParser("repositories", parser);
9 this.registerBeanDefinitionParser("node-client", new NodeClientBeanDefinitionParser());
10 this.registerBeanDefinitionParser("transport-client", new TransportClientBeanDefinitionParser());
11 }
12}
首先是继承自NamesapceHandlerSupport
然后在重写的init方法中注册了一系列的parser,每个parser对应一个字符串,就是我们在xml配置文件是使用的自定义内容,比如上面的es的配置
<elasticsearch:transport-client id="client" cluster-nodes="192.168.73.186:9300" cluster-name="elasticsearch" />
这里的配置最终就会通过 TransportClientBeanDefinitionParser 来进行解析
而上面提到的各个parser,在init方法中,保存在了一个Map中
private final Map<String, BeanDefinitionParser> parsers = new HashMap();
所谓注册parser,就是在向这个parsers的map进行 put 操作。
作者:陈韶健
广告
购买
作者:[美] Craig Walls 沃尔斯
广告
购买
那回过头来,在Spring中,最核心的内容是什么呢? 是 Bean 。而实际上我们配置到XML里的这些内容,最终也会生在一个对应的Bean,所有的配置,只是为了生成Bean,这些自定义的配置,都称之为BeanDefinition。
所以,Spring 在解析配置文件是,会有如下的判断,是否是defaultNamespace,不是的话就走customElementParse
1protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
2 if(delegate.isDefaultNamespace(root)) {
3 NodeList nl = root.getChildNodes();
4
5 for(int i = 0; i < nl.getLength(); ++i) {
6 Node node = nl.item(i);
7 if(node instanceof Element) {
8 Element ele = (Element)node;
9 if(delegate.isDefaultNamespace(ele)) {
10 this.parseDefaultElement(ele, delegate);
11 } else {
12 delegate.parseCustomElement(ele);
13 }
14 }
15 }
16 } else {
17 delegate.parseCustomElement(root);
18 }
19
20 }
而是不是defaultNameSpace的判断更直接:namespace的uri有没有内容,或者是不是spring 的beans的声明
1public boolean isDefaultNamespace(String namespaceUri) {
2 return !StringUtils.hasLength(namespaceUri) || "http://www.springframework.org/schema/beans".equals(namespaceUri);
3 }
所以请求都走到了parseCustomElement里,这里开始进行配置的解析, parse的时候,通过uriResolver查到对应的Handler
1public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
2 String namespaceUri = this.getNamespaceURI(ele);
3 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
4 if(handler == null) {
5 this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
6 return null;
7 } else {
8 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
9 }
10 }
那此时返回的仅仅是spring.handlers里配置的Handler,而每个Handler又注册了不少的parse,还得需要一个获取parser的过程
1public BeanDefinition parse(Element element, ParserContext parserContext) {
2 return this.findParserForElement(element, parserContext).parse(element, parserContext);
3 }
4
5 private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
6 String localName = parserContext.getDelegate().getLocalName(element);
7 BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
8 if(parser == null) {
9 parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
10 }
11
12 return parser;
13 }
这个获取的过程,就是通过传入的string,在我们开始声明的Map里 get对应的parser,再使用它进行配置的解析啦。
有了parser,后面就是生成BeanDefinition的过程。
我们看,这些parser,都是继承自AbstraceBeanDefinitionParser,或者实现了BeanDefinitionParser 的接口,统一解析的入口处,是接口的parse方法。
1public class TransportClientBeanDefinitionParser extends AbstractBeanDefinitionParser {
2 public TransportClientBeanDefinitionParser() {
3 }
4
5 protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
6 BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TransportClientFactoryBean.class);
7 this.setConfigurations(element, builder);
8 return this.getSourcedBeanDefinition(builder, element, parserContext);
9 }
10}
在重写的parseInternal方法中,返回解析配置后对应的BeanDefinition。这里也是各个框架自由抽象的地方。比如 有些框架是简单直接一个类,而有些在此处会应用一些类似策略、装饰器等设计模式,提供更多的灵活性 。
具体获取到BeanDefinition之后,放到整个Context中如何生成 Spring Bean的内容,以后我们再做分析。
相关阅读:
类加载器与类冲突
类加载器与类的热替换(Hotswap)
Tomcat 是怎样处理 SpringBoot应用的?
Tomcat系列文件,请参考汇总文:
Tomcat 从眼熟到脸熟
关注『 Tomcat那些事儿 』 ,发现更多精彩文章!了解各种常见问题背后的原理与答案。深入源码,分析细节,内容原创,欢迎关注。
转发是最大的支持 ,谢谢
更多精彩内容:
一台机器上安装多个Tomcat 的原理(回复001)
监控Tomcat中的各种数据 (回复002)
启动Tomcat的安全机制(回复003)
乱码问题的原理及解决方式(回复007)
Tomcat 日志工作原理及配置(回复011)
web.xml 解析实现(回复 012)
线程池的原理( 回复 014)
Tomcat 的集群搭建原理与实现 (回复 015)
类加载器的原理 (回复 016)
类找不到等问题 (回复 017)
代码的热替换实现(回复 018)
Tomcat 进程自动退出问题 (回复 019)
为什么总是返回404? (回复 020)
...
PS: 对于一些 Tomcat常见问 题,在公众号的【 常见问题 】菜单中,有需要的朋友欢迎关注查看。