继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

spring利用spring.handlers解析自定义配置

绝地无双
关注TA
已关注
手记 211
粉丝 59
获赞 324
一、问题

我们在spring的xml配置文件里经常定义各种各样的配置(tx、bean、mvc、bean等等)。以及集成第三方框架时,也会看到一些spring之外的配置,例如dubbo的配置、security的配置、redis的配置等等。spring作为一个庞大的容器,到底是怎么工作的呢?这篇文章将简单的介绍一下其实现机制,具体细节朋友们可以自己查看相关代码。

二、代码分析

稍微了解一点spring的朋友都知道spring的入口方法就是refresh(),所以我们直接进入refresh()方法【不清楚的可以查看一下ClassPathXmlApplicationContext,一步一步跟踪进去】。

public void refresh() throws BeansException, IllegalStateException {         synchronized (this.startupShutdownMonitor) {             // Prepare this context for refreshing.             prepareRefresh();             // Tell the subclass to refresh the internal bean factory.             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();             // Prepare the bean factory for use in this context.             prepareBeanFactory(beanFactory);             try {                 // Allows post-processing of the bean factory in context subclasses.                 postProcessBeanFactory(beanFactory);                 // Invoke factory processors registered as beans in the context.                 invokeBeanFactoryPostProcessors(beanFactory);                 // Register bean processors that intercept bean creation.                 registerBeanPostProcessors(beanFactory);                 // Initialize message source for this context.                 initMessageSource();                 // Initialize event multicaster for this context.                 initApplicationEventMulticaster();                 // Initialize other special beans in specific context subclasses.                 onRefresh();                 // Check for listener beans and register them.                 registerListeners();                 // Instantiate all remaining (non-lazy-init) singletons.                 finishBeanFactoryInitialization(beanFactory);                 // Last step: publish corresponding event.                 finishRefresh();             } ...由于篇幅,我省略了一部分

而解析spring配置的入口就在obtainFreshBeanFactory().refreshBeanFactory()中:

protected final void refreshBeanFactory() throws BeansException {         if (hasBeanFactory()) {             destroyBeans();             closeBeanFactory();         }         try {             DefaultListableBeanFactory beanFactory = createBeanFactory();             beanFactory.setSerializationId(getId());             customizeBeanFactory(beanFactory);             loadBeanDefinitions(beanFactory);//就是这里             synchronized (this.beanFactoryMonitor) {                 this.beanFactory = beanFactory;             }         }         catch (IOException ex) {             throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);         }     }

我们首先会告诉spring的配置文件所在路径,即setLocations(),spring创建完BeanFactory,接下来要做的就是解析配置,完成其他各种属性的封装(BeanDefinition、各种代理、映射关系等等)。这里的loadBeanDefinitions就是解析配置的核心方法。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {         // Create a new XmlBeanDefinitionReader for the given BeanFactory.         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);         // Configure the bean definition reader with this context's         // resource loading environment.         beanDefinitionReader.setEnvironment(this.getEnvironment());         beanDefinitionReader.setResourceLoader(this);         beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));         // Allow a subclass to provide custom initialization of the reader,         // then proceed with actually loading the bean definitions.         initBeanDefinitionReader(beanDefinitionReader);         loadBeanDefinitions(beanDefinitionReader);     }

DefaultListableBeanFactory 作为BeanFactory的一个核心实例,且实现了BeanDefinitionRegistry接口,在解析之前当然是要给配置文件解析器(XmlBeanDefinitionReader)设置一个存放BeanDefinition的容器(即将注册bean的功能委托给BeanFactory)。然后配置文件解析器就开始它的解析工作了。

我们直接定位到XmlBeanDefinitionReader的doLoadBeanDefinitions方法:

try {             Document doc = doLoadDocument(inputSource, resource);             return registerBeanDefinitions(doc, resource);         }         catch (BeanDefinitionStoreException ex) {             throw ex;         }

利用w3c的DOM将配置文件包装成了一个Document对象,接下来就是利用Dom来解析childNode相关属性。

接下来,我们沿着代码定位到DefaultBeaDefinitionDocumentReader中的的parseDefinitions方法:

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)) {                         parseDefaultElement(ele, delegate);                     }                     else {                         delegate.parseCustomElement(ele);                     }                 }             }         }         else {             delegate.parseCustomElement(root);         }     }

核心部分来了,spring迭代根节点<beans/>,一个一个的解析其子节点.如果是<beans/>节点,则走parseDefaultElement方法,我们这里关注的是parseCustomElement():

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {         String namespaceUri = getNamespaceURI(ele);         NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);         if (handler == null) {             error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);             return null;         }         return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));     }

根据元素的namespaceUri(也就是xml文件头那一串schema)找到对应的NamespaceHandler.【这里就是扩展自定义配置的入口】

我们接下来看一下resolve()方法:

public NamespaceHandler resolve(String namespaceUri) {         Map<String, Object> handlerMappings = getHandlerMappings();         Object handlerOrClassName = handlerMappings.get(namespaceUri);         if (handlerOrClassName == null) {             return null;         }         else if (handlerOrClassName instanceof NamespaceHandler) {             return (NamespaceHandler) handlerOrClassName;         }         else {             String className = (String) handlerOrClassName;             try {                 Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);                 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {                     throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +                             "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");                 }                 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);                 namespaceHandler.init();                 handlerMappings.put(namespaceUri, namespaceHandler);                 return namespaceHandler;             }             catch (ClassNotFoundException ex) {                 throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +                         namespaceUri + "] not found", ex);             }             catch (LinkageError err) {                 throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +                         namespaceUri + "]: problem with handler class file or dependent class", err);             }         }     }

对应的NamespaceHandler就是根据handlerMappings找到匹配关系的,所以我们只需要看DefaultNamespaceHandlerResolver.getHandlerMappings()即可。

private Map<String, Object> getHandlerMappings() {         if (this.handlerMappings == null) {             synchronized (this) {                 if (this.handlerMappings == null) {                     try {                         Properties mappings =                                 PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);//handlerMappingsLocation默认的路径就是META-INF/spring.handlers                         if (logger.isDebugEnabled()) {                             logger.debug("Loaded NamespaceHandler mappings: " + mappings);                         }                         Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());                         CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);                         this.handlerMappings = handlerMappings;                     }                     catch (IOException ex) {                         throw new IllegalStateException(                                 "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);                     }                 }             }         }         return this.handlerMappings;     }

spring将classpath下的META-INF/spring.handlers中的配置加载到Map中.这个spring.handler文件就是以NamespaceUrl作为key,对应的Handler作为value的键值对,例如:
···
http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

···
当我们解析到<context/>配置的时候,我们会根据context的schema,也就是[http://www.springframework.org/schema/context]作为key找到对应的ContextNamespacehandler进行解析的,一切都清楚了。

三、总结

spring这点做得很强大,利用这种类似于Serviceloader的功能来平等对待第三方,方便别人扩展集成。我们也要停下来想一想,当我们自己开发这种核心组件的时候一定要做到"平等对待第三方"以及核心足够小,依赖少。这也是一种修行!



作者:jerrik
链接:https://www.jianshu.com/p/91f6068adff2


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP