当前位置: 移动技术网 > IT编程>开发语言>Java > Spring中自定义Schema如何解析生效详解

Spring中自定义Schema如何解析生效详解

2019年07月19日  | 移动技术网IT编程  | 我要评论
前言 随着 spring boot 的日渐流行,应用里的大部分配置都被隐藏了起来,我们仅需要关心真正的业务内容, controller, service, reposit

前言

随着 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文件加载过来。

我们再来看这个解析类,内容如下:

 public class elasticsearchnamespacehandler extends namespacehandlersupport { 
 public elasticsearchnamespacehandler() { 
 } 
 
 public void init() { 
 repositoryconfigurationextension extension = new elasticsearchrepositoryconfigextension(); 
 repositorybeandefinitionparser parser = new repositorybeandefinitionparser(extension); 
 this.registerbeandefinitionparser("repositories", parser); 
 this.registerbeandefinitionparser("node-client", new nodeclientbeandefinitionparser()); 
 this.registerbeandefinitionparser("transport-client", new transportclientbeandefinitionparser()); 
 } 
} 

首先是继承自namesapcehandlersupport

然后在重写的init方法中注册了一系列的parser,每个parser对应一个字符串,就是我们在xml配置文件是使用的自定义内容,比如上面的es的配置

<elasticsearch:transport-client id="client" 
 cluster-nodes="192.168.73.186:9300" cluster 

这里的配置最终就会通过 transportclientbeandefinitionparser 来进行解析

而上面提到的各个parser,在init方法中,保存在了一个map中

private final map<string, beandefinitionparser> parsers = new hashmap(); 

所谓注册parser,就是在向这个parsers的map进行put操作。

那回过头来,在spring中,最核心的内容是什么呢? 是bean。而实际上我们配置到xml里的这些内容,最终也会生在一个对应的bean,所有的配置,只是为了生成bean,这些自定义的配置,都称之为beandefinition。

所以,spring 在解析配置文件是,会有如下的判断,是否是defaultnamespace,不是的话就走customelementparse

protected void parsebeandefinitions(element root, beandefinitionparserdelegate delegate) { 
  if(delegate.isdefaultnamespace(root)) { 
  nodelist nl = root.getchildnodes(); 
 
  for(int i = 0; i < nl.getlength(); ++i) { 
   node node = nl.item(i); 
   if(node instanceof element) { 
   element ele = (element)node; 
   if(delegate.isdefaultnamespace(ele)) { 
   this.parsedefaultelement(ele, delegate); 
   } else { 
   delegate.parsecustomelement(ele); 
   } 
  } 
  } 
 } else { 
  delegate.parsecustomelement(root); 
 } 
 } 

而是不是defaultnamespace的判断更直接:namespace的uri有没有内容,或者是不是spring 的beans的声明

public boolean isdefaultnamespace(string namespaceuri) { 
 return !stringutils.haslength(namespaceuri) || "http://www.springframework.org/schema/beans".equals(namespaceuri); 
 } 

所以请求都走到了parsecustomelement里,这里开始进行配置的解析, parse的时候,通过uriresolver查到对应的handler

public beandefinition parsecustomelement(element ele, beandefinition containingbd) { 
 string namespaceuri = this.getnamespaceuri(ele); 
 namespacehandler handler = this.readercontext.getnamespacehandlerresolver().resolve(namespaceuri); 
 if(handler == null) { 
  this.error("unable to locate spring namespacehandler for xml schema namespace [" + namespaceuri + "]", ele); 
  return null; 
 } else { 
  return handler.parse(ele, new parsercontext(this.readercontext, this, containingbd)); 
 } 
 } 

那此时返回的仅仅是spring.handlers里配置的handler,而每个handler又注册了不少的parse,还得需要一个获取parser的过程

 public beandefinition parse(element element, parsercontext parsercontext) { 
  return this.findparserforelement(element, parsercontext).parse(element, parsercontext); 
 } 
 
 private beandefinitionparser findparserforelement(element element, parsercontext parsercontext) { 
  string localname = parsercontext.getdelegate().getlocalname(element); 
  beandefinitionparser parser = (beandefinitionparser)this.parsers.get(localname); 
  if(parser == null) { 
  parsercontext.getreadercontext().fatal("cannot locate beandefinitionparser for element [" + localname + "]", element); 
 } 
 
 return parser; 
 } 

这个获取的过程,就是通过传入的string,在我们开始声明的map里 get对应的parser,再使用它进行配置的解析啦。
有了parser,后面就是生成beandefinition的过程。

我们看,这些parser,都是继承自abstracebeandefinitionparser,或者实现了beandefinitionparser 的接口,统一解析的入口处,是接口的parse方法。

public class transportclientbeandefinitionparser extends abstractbeandefinitionparser { 
 public transportclientbeandefinitionparser() { 
 } 
 
 protected abstractbeandefinition parseinternal(element element, parsercontext parsercontext) { 
 beandefinitionbuilder builder = beandefinitionbuilder.rootbeandefinition(transportclientfactorybean.class); 
 this.setconfigurations(element, builder); 
 return this.getsourcedbeandefinition(builder, element, parsercontext); 
 } 
} 

在重写的parseinternal方法中,返回解析配置后对应的beandefinition。这里也是各个框架自由抽象的地方。比如有些框架是简单直接一个类,而有些在此处会应用一些类似策略、装饰器等设计模式,提供更多的灵活性。

具体获取到beandefinition之后,放到整个context中如何生成 spring bean的内容,以后我们再做分析。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网