随着 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 的配置
cluster-nodes="192.168.73.186:9300" cluster
这里的配置最终就会通过 TransportClientBeanDefinitionParser 来进行解析
而上面提到的各个 parser, 在 init 方法中, 保存在了一个 Map 中
private final Map 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 的内容, 以后我们再做分析.
来源: http://zhuanlan.51cto.com/art/201807/578129.htm