1 介绍

创建并初始化spring容器中,关键一步就是读取并解析spring XML配置文件。这个过程比较复杂,本文将详细分析整个流程。先看涉及到的关键类。

XmlWebApplicationContext:web应用的默认Spring容器

XmlBeanDefinitionReader:读取XML并解析xml文件

DocumentLoader:文件先被读取为了原始的输入流InputStream,然后封装为InputSource。DocumentLoader加载inputSource,解析后得到Document对象

Document:代表一个XML或者HTML标记文件,包含docType,各种element节点等。

BeanDefinition:XML中bean在spring容器中的表示。Document会被解析为BeanDefinition。在Bean创建和初始化中它们会大展拳脚。

BeanDefinitionDocumentReader:解析Document中的节点元素Element,转换为BeanDefinition,并注册他们到BeanDefinition注册表中。默认实现类为DefaultBeanDefinitionDocumentReader

BeanDefinitionParserDelegate:实际解析Document中的节点元素,采用了代理模式。

2 流程

2.1 obtainFreshBeanFactory

初始化spring容器中的refresh()方法中,会调用obtainFreshBeanFactory()方法,它是读取并解析spring xml配置文件的入口。详细过程可以参看上一篇文章。下面从这个方法开始分析。

// obtainFreshBeanFactory 加载spring XML配置文件
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {// 关键所在,后面分析refreshBeanFactory();// log之类的东西,不是很关键了ConfigurableListableBeanFactory beanFactory = getBeanFactory();if (logger.isDebugEnabled()) {logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);}return beanFactory;
}
​
protected final void refreshBeanFactory() throws BeansException {// BeanFactory已存在,则先销毁它if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {// new DefaultListableBeanFactory,创建容器,设置id,个性化配置等DefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);// 加载xml配置文件,具体子ApplicationContext会实现它。不同子类实现会不同。重点节点,后面分析loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);}
}

2.2 loadBeanDefinitions

下面我们来分析web应用中默认的spring容器,也就是XmlWebApplicationContext,中的loadBeanDefinitions。

通过XmlBeanDefinitionReader来读取xml配置文件。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// 创建XmlBeanDefinitionReader,用它来读取XML配置文件XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
​// 配置beanDefinitionReader的环境和属性beanDefinitionReader.setEnvironment(getEnvironment());// ApplicationContext也继承了ResourceLoader接口beanDefinitionReader.setResourceLoader(this);// entityResolver在parse时会用到beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
​// 初始化beanDefinitionReader,子类可以实现这个方法,做一些个性化配置和初始化initBeanDefinitionReader(beanDefinitionReader);
​// 开始load xml文件,这一步开始才是真正读取XML文件了。前面都是做一些环境配置之类的事情loadBeanDefinitions(beanDefinitionReader);
}

loadBeanDefinitions()中先创建beanDefinitionReader,然后配置它的环境,设置成员属性,最后才是真正干活的,也就是读取XML配置文件。我们来看真正读取XML的这一步。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {// 获取XML配置文件的地址,还记得前面讲到过的web.xml中的contextConfigLocation元素吧,它指明了XML配置文件的地址。如果web.xml中没有配置,则读取默认的地址,参看后面分析String[] configLocations = getConfigLocations();if (configLocations != null) {// 遍历读取每个配置文件for (String configLocation : configLocations) {reader.loadBeanDefinitions(configLocation);}}
}

2.3 getConfigLocations()

我们先分析下getConfigLocations方法, 它会获取到spring XML配置文件的地址。

// 获取配置文件地址,从web.xml中读取。读取不到,则使用默认地址
protected String[] getConfigLocations() {return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}
​
// 获取默认的xml配置文件地址
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
​
protected String[] getDefaultConfigLocations() {if (getNamespace() != null) {// 设置了容器namespace时,返回/WEB-INF/ + ApplicationContext的namespace + .xmlreturn new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};}else {// 没有设置namespace时,直接返回 /WEB-INF/applicationContext.xmlreturn new String[] {DEFAULT_CONFIG_LOCATION};}
}

我们先获取XML文件地址,然后再读取XML文件,下面重点分析reader如何读取xml文件的

2.4 XmlBeanDefinitionReader.loadBeanDefinitions()

// XmlBeanDefinitionReader读取XML文件
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {// 一段log,省略
​// 将Resource对象添加到hashSet中,不是很关键Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {currentResources = new HashSet<>(4);this.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");}try {// 获取Resource对象的输入流InputStream inputStream = encodedResource.getResource().getInputStream();try {// 将inputStream封装到InputSource对象中,并设置编码格式InputSource inputSource = new InputSource(inputStream);if (encodedResource.getEncoding() != null) {inputSource.setEncoding(encodedResource.getEncoding());}// 加载封装好的inputSource对象,读取XML配置文件。关键步骤,后面分析return doLoadBeanDefinitions(inputSource, encodedResource.getResource());}finally {inputStream.close();}}catch (IOException ex) {// 异常处理throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);}finally {// 资源释放currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.remove();}}
}

XmlBeanDefinitionReader做了一些成员变量设置后,获取传入的Resource对象的输入流,封装成InputSource后,开始真正解析配置文件。下面来看doLoadBeanDefinitions()如何解析XML文件。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 加载并解析XML文件,解析方案为社区通用方法,不是Spring所特有的Document doc = doLoadDocument(inputSource, resource);
​return registerBeanDefinitions(doc, resource);}// 各种异常处理,省略catch (BeanDefinitionStoreException ex) {}
}

先利用documentLoader加载XML配置文件,然后注册beans。

2.4.1 doLoadDocument 加载xml文件,将InputSource输入流转换为Document对象

// 加载XML配置文件
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {// 获取entityResolver,errorHandler, validationMode,namespaceAware,它们都有默认值,也可以由用户来设置// entityResolver: 解析器,默认ResourceEntityResolver// errorHandler: 解析XML时错误处理,默认SimpleSaxErrorHandler// validationMode: xml验证模式,默认VALIDATION_XSD// namespaceAware: XML命名空间是否敏感,默认falsereturn this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());
}
​
// DefaultDocumentLoader的loadDocument方法
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {// 创建DocumentBuilderFactory,对应多个实现类,默认com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImplDocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);if (logger.isDebugEnabled()) {logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");}// 创建DocumentBuilder,通过factory创建DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);// 解析输入流,并返回一个Document对象。解析采用的是通用的DocumentBuilderImpl对象,使用DomParser解析xml文件。解析这一步是通用的,不是Spring特有的方法。比较复杂,不展开了。只要知道通过parse后得到了Document对象就可以了。return builder.parse(inputSource);
}

2.4.2 registerBeanDefinitions 将读取XML后的Document转换为BeanDefinition

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 反射创建documentReader实例,默认为DefaultBeanDefinitionDocumentReaderBeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 获取容器中当前beans数量,已经注册的BeanDefinition会存储在一个Map中,获取Map的size即可。int countBefore = getRegistry().getBeanDefinitionCount();// 注册beanDefinition,这是关键所在,后面分析documentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 返回本次注册的数量return getRegistry().getBeanDefinitionCount() - countBefore;
}
​
// 反射创建documentReader,默认为DefaultBeanDefinitionDocumentReader
private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));}

registerBeanDefinitions的作用就是将上一步中,输入流转换为的Document对象,转换为BeanDefinition对象。主要工作在documentReader.registerBeanDefinitions()中,下面来分析。

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;logger.debug("Loading bean definitions");// root为<beans />标签Element root = doc.getDocumentElement();doRegisterBeanDefinitions(root);
}
​
// 采用代理进行解析,代理为BeanDefinitionParserDelegate
protected void doRegisterBeanDefinitions(Element root) {// 创建代理BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);
​if (this.delegate.isDefaultNamespace(root)) {String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {return;}}}
​// 解析前的处理,DefaultBeanDefinitionDocumentReader没有实现它,子类可以实现,来扩展功能preProcessXml(root);// 解析root内的XML标签,如<import> <alias> <bean>等parseBeanDefinitions(root, this.delegate);// 解析后的处理,同样没有实现它,子类可以实现。postProcessXml(root);
​this.delegate = parent;}

registerBeanDefinitions() 解析下的XML标签,通过BeanDefinitionParserDelegate代理来进行。具体工作在parseBeanDefinitions()中。这里是本篇文章中比较关键的地方。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {// 获取<beans>的子节点NodeList nl = root.getChildNodes();// 遍历子节点for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {// 子节点是Element对象,默认的子节点,如<import>都是Element对象Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {// 在默认的命名空间url中的元素,是默认定义好的节点,采用parseDefaultElement方法解析parseDefaultElement(ele, delegate);}else {// 用户自定义的命名空间url中的元素,采用parseCustomElement方法解析delegate.parseCustomElement(ele);}}}}else {// 子节点不是标准的Element元素,比如用户自定义的,采用parseCustomElement方法解析delegate.parseCustomElement(root);}
}

parseBeanDefinitions()方法会循环遍历的子节点。如果是默认命名空间内的Element,则采用parseDefaultElement()方法解析,否则采用parseCustomElement()方法。DefaultElement包括、、、嵌套的,其余都为CustomElement

2.4.2.1 parseDefaultElement() 解析DefaultElement

public static final String IMPORT_ELEMENT = "import";
public static final String ALIAS_ATTRIBUTE = "alias";
public static final String BEAN_ELEMENT = "bean";
public static final String NESTED_BEANS_ELEMENT = "beans";
​
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {// 解析<import>if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {importBeanDefinitionResource(ele);}// 解析<alias>else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {processAliasRegistration(ele);}// 解析<bean>else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {processBeanDefinition(ele, delegate);}// 解析<beans>else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// recursedoRegisterBeanDefinitions(ele);}
}

下面我们重点分析下processBeanDefinition(),因为这个和spring息息相关。这个过程十分复杂,所以我们不进行很细致的分析,抓住主要流程就OK了。也不建议非要去了解每行代码做了什么事情,避免过度陷入其中,而忽略了主流程。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 解析Element为BeanDefinition,这是重点,后面详细分析BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// 将BeanDefinition注册到BeanDefinitionMap中,key为beanNameBeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}// 发送注册的消息,相应的监听器就会收到并处理消息了getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}
}
​
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {// 获取bean的id属性String id = ele.getAttribute(ID_ATTRIBUTE);// 获取bean的name属性String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
​// 解析name属性,支持多个nameList<String> aliases = new ArrayList<>();if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}
​// id属性赋值到beanName变量中,注意不是name属性。如果没有id属性,则使用name属性的第一个值String beanName = id;if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {beanName = aliases.remove(0);}
​// 校验beanname的唯一性,这也是为啥id属性值必须唯一的原因if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}
​// 解析bean节点为GenericBeanDefinition,后面详细分析AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);// 后面不是很重要了if (beanDefinition != null) {if (!StringUtils.hasText(beanName)) {try {if (containingBean != null) {beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {beanName = this.readerContext.generateBeanName(beanDefinition);// Register an alias for the plain bean class name, if still possible,// if the generator returned the class name plus a suffix.// This is expected for Spring 1.2/2.0 backwards compatibility.String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null &&beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}
​}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}String[] aliasesArray = StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}
​return null;
}
​
public static final String PARENT_ATTRIBUTE = "parent";
public static final String CLASS_ATTRIBUTE = "class";
​
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {this.parseState.push(new BeanEntry(beanName));// 获取class和parent属性String className = null;if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}
​try {// 反射实例化bean为GenericBeanDefinitionAbstractBeanDefinition bd = createBeanDefinition(className, parent);// 解析节点中其他属性,如scope, singleton等。后面详细分析parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
​// 解析子节点meta属性,如果有的话parseMetaElements(ele, bd);// 解析子节点lookup-method属性parseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析子节点replaced-method属性parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
​// 解析constructor-arg属性parseConstructorArgElements(ele, bd);// 解析property属性parsePropertyElements(ele, bd);// 解析qualifier属性parseQualifierElements(ele, bd);
​bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));
​return bd;}// 各种异常处理catch (ClassNotFoundException ex) {error("Bean class [" + className + "] not found", ele, ex);}...return null;
}
​
// 反射实例化,创建GenericBeanDefinition对象
public static AbstractBeanDefinition createBeanDefinition(@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {​GenericBeanDefinition bd = new GenericBeanDefinition();bd.setParentName(parentName);if (className != null) {if (classLoader != null) {bd.setBeanClass(ClassUtils.forName(className, classLoader));}else {bd.setBeanClassName(className);}}return bd;
}
​
​
// 解析<bean>中的各种属性,比如scope,lazy-init等
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {​// 解析singletonif (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);}// 解析scopeelse if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));}else if (containingBean != null) {// Take default from containing bean in case of an inner bean definition.bd.setScope(containingBean.getScope());}// 解析abstractif (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));}// 解析lazy-initString lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);if (DEFAULT_VALUE.equals(lazyInit)) {lazyInit = this.defaults.getLazyInit();}bd.setLazyInit(TRUE_VALUE.equals(lazyInit));// 解析autowireString autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);bd.setAutowireMode(getAutowireMode(autowire));// 解析depends-onif (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));}// 解析autowire-candidateString autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {String candidatePattern = this.defaults.getAutowireCandidates();if (candidatePattern != null) {String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));}}else {bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));}// 解析primaryif (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));}// 解析init-methodif (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);if (!"".equals(initMethodName)) {bd.setInitMethodName(initMethodName);}}else if (this.defaults.getInitMethod() != null) {bd.setInitMethodName(this.defaults.getInitMethod());bd.setEnforceInitMethod(false);}// 解析destroy-methodif (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);bd.setDestroyMethodName(destroyMethodName);}else if (this.defaults.getDestroyMethod() != null) {bd.setDestroyMethodName(this.defaults.getDestroyMethod());bd.setEnforceDestroyMethod(false);}
​// 解析factory-methodif (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));}// 解析factory-beanif (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));}
​return bd;
}

2.4.2.2 parseCustomElement() 解析CustomElement

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间url获取具体的NamespaceHandler,比如<context:component-scan>对应的就是用户自定义的ContextNamespaceHandler。NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 交给用户自定义的NamespaceHandler来解析用户自定义的CustomElementreturn handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

parseCustomElement()首先根据自定义标签的命名空间,生成具体的NamespaceHandler。一般要由用户自己定义。然后调用parse方法进行解析,这个也是用户自定义的。这里就不展开分析了。

3 流程图

applicationcontext添加配置_Spring源码分析2 — spring XML配置文件的解析流程相关推荐

  1. Spring源码分析3 — spring XML配置文件的解析流程

    1 介绍 创建并初始化spring容器中,关键一步就是读取并解析spring XML配置文件.这个过程比较复杂,本文将详细分析整个流程.先看涉及到的关键类. XmlWebApplicationCont ...

  2. Mybatis 源码分析(一)配置文件加载流程

    Mybatis 源码分析(一)配置文件加载流程 1.项目构建 引入依赖 <dependency><groupId>org.mybatis</groupId>< ...

  3. uboot源码分析(1)uboot 命令解析流程简析

    uboot 命令解析流程简析 uboot正常启动后,会调用main_loop(void)函数,进入main_loop()之后,如果在规定的时间(CONFIG_BOOTDELAY)内,没有检查到任何按键 ...

  4. android 开发零起步学习笔记(二十二):ANDROID应用ACTIVITY、DIALOG、POPWINDOW、TOAST窗口添加机制及源码分析(一)

    原文:http://www.cnblogs.com/shanzei/p/4654817.html 第一部分: ANDROID应用ACTIVITY.DIALOG.POPWINDOW.TOAST窗口添加机 ...

  5. spring源码分析第六天------spring经典面试问题

    spring源码分析第六天------spring经典面试问题 1.Spring5 新特性及应用举例 2.Spring 经典的面试问题 a.什么是 Spring 框架?Spring 框架有哪些主要模块 ...

  6. springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三)

    springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三) 例: package com.example.demo.service;import com.exa ...

  7. 【Android SDM660源码分析】- 02 - UEFI XBL QcomChargerApp充电流程代码分析

    [Android SDM660源码分析]- 02 - UEFI XBL QcomChargerApp充电流程代码分析 一.加载 UEFI 默认应用程序 1.1 LaunchDefaultBDSApps ...

  8. React Native 源码分析(三)——Native View创建流程

    1.React Native 源码分析(一)-- 启动流程 2.React Native 源码分析(二)-- 通信机制 3.React Native 源码分析(三)-- Native View创建流程 ...

  9. 【SA8295P 源码分析】14 - Passthrough配置文件 /mnt/vm/images/linux-la.config 内容分析

    系列文章汇总见:<[SA8295P 源码分析]00 - 系列文章链接汇总> 本文链接:<[SA8295P 源码分析]14 - Passthrough配置文件 /mnt/vm/imag ...

最新文章

  1. oracle中sp,sp是什么?
  2. Windows10 ISO下载
  3. safari浏览器横屏怎么设置_如何避免苹果safari自带浏览器“跟踪”你的信息!
  4. 134_Power BI Report Server之某消费品运营数据监控
  5. 理解SQL Server中的权限体系(下)----安全对象和权限
  6. 暗藏 15 年,Linux 惊曝 3 大 Bug 直取 root 权限!
  7. 如何数分钟创建并成功运行数千台云主机?
  8. 《代码整洁之道》—第1章1.1节要有代码
  9. kpi绩效考核流程图_XX公司KPI绩效考核案例.doc
  10. 100道练习理解SQL语法
  11. 【POJ3683】Priest John's Busiest Day
  12. 如何批量抠图换背景?这两个方法可以做到
  13. 打印101~150之间的质数
  14. 荐书 | 手牵手一步两步望着天,看星星一颗两颗连成线
  15. D2 日报 2019年5月20日
  16. 2017苹果全球开发者大会直播地址
  17. SQL Server2012 序列号 注册码
  18. c++语言字母转换,c++大小写字母转换的思路有几种?
  19. jsp拆迁管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目
  20. Spine IK 约束

热门文章

  1. 登录DMOZ/如何登录DMOZ分类目录
  2. Java知多少(23)类的基本运行顺序
  3. linux下文件系统不丢数据扩容方法
  4. 读书笔记之:C/C++程序员实用大全—C/C++最佳编程指南
  5. Windows 7官方主题之“海上航行”
  6. ubuntu vscode_如何在Ubuntu-18.04下用VSCode编译LibTorch
  7. matlab 删除路径_MATLAB自动管理文件
  8. android 多个属性值,android布局属性值fill_parent和match_parent
  9. wordpress模版post.php,WordPress主题开发手册
  10. 设计MM32-LINK自动复位器,上电复位