之前提到过Spring中的标签包括默认标签和自定义标签,而这两种标签的用法以及解析方式存在着很大不同。
默认标签的解析是在parseDefaultElement函数进行的,函数中的功能逻辑一目了然,分别对4中不同标签(import、alisa、bean和beans)做了不同处理。

private void parseDefaultElement(Element ele,BeanDefinitionParserDetegate detegate)
// 对import标签的处理
importBeanDefinitionResource(ele);
// 对alisa标签的处理
proccessAlisaRegistration(ele);
// 对bean标签的处理
proccessBeanDefinition(ele,detegate);
// 对beans标签的处理0
doRegisterDefinition(ele);

3.1 bean标签的解析以及注册

在4种标签的解析中,对bean标签的解析最为复杂也最为重要,所以从此标签开始深入分析,如果能理解此标签的解析过程,其他标签的解析过程自然就迎刃而解,首先进入processBeanDefinition(ele,detegate)。

  1. 首先委托BeanDefinitionDetegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例包括配置文件的各个属性了,例如class、name、id、alisa之类的属性。
  2. 当返回bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
  3. 解析完成后,需要对解析后的bdHolder进行注册,同样注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法。
  4. 最后发出响应事件,通知想关的监听器这个bean已经加载完成了
3.1.1 解析BeanDefinition

首先从元素解析以及信息提取开始,也就是
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele),进入BeanDefinitionDelegate类的parseBeanDefinitionElement方法。

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele,BeanDefinition containingBean)
// 解析id属性
// 解析name属性
// 分割name属性

以上便是对默认标签解析的全过程了,当然,对Spring的解析犹如剥洋葱一样,一层一层地进行,尽管现在只能看到对属性id和name解析,但是很庆幸,思路我们已经了解了。在开始对属性展开全面解析前,Spring在外层又做了一个当前层的功能架构,在当前层完成的主要工作包括如下内容:

  1. 提取元素中的id以及name属性;
  2. 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中;
  3. 如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName;
  4. 将获取到的信息封装到BeanDefinitionHolder的实例中。

进一步的查看步骤2中对其他标签其他属性的解析过程

publicAbstractBeanDefinition parseBeanDefinitionElement(Element ele,String beanName,BeanDefinition containBean)
// 解析class属性
// 解析parent属性
// 创建用于承载属性AbstractBeanDefinition类型的GeneriBeanDefinition
// 硬编码解析默认bean的各种属性
// 解析元数据
// 解析look-method属性
// 解析replace-method属性
// 解析构造函数参数
// 解析property子元素
/// 解析qualifier子元素

终于,bean标签的所有属性,不论常用的还是不常用的我们都看到了,尽管复杂的属性需要进一步解析,接下来,继续一些复杂标签属性的解析。

1. 创建用于属性承载的BeanDefinition

BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition三种均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件< bean>元素标签在容器中的内部表示形式。< bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和< bean>中的属性是一一对应的,其中RootBeanDefinition是最常用的实现类,它对应一般性的< bean>元素标签,GenericBeanDefinition是自2.5版本以后新加入到bean文件配置属性的定义类,是一站式服务类。

在配置文件中可以定义父< bean>和子< bean>,父< bean>用RootBeanDefinition表示,而子< bean>用ChildBeanDefinition表示,而没有父< bean>和子< bean>就用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。

Spring通过BeanDefinition将配置信息转换为容器内部标识,并将这些BeanDefinition注册到BeanDefinitionRegistry中,Spring容器的BeanDefinitionRegistry就像Spring配置信息的内存数据库,主要以map的形式保存,后续的操作直接从BeanDefinitionRegistry中读取配置信息。


由于可知,要解析属性首先要创建用于承载属性的实例,就是创建GenericBeanDefinition类型的实例,而代码createBeanDefinition(className,parent)的作用就是实现此功能。

2. 解析各种属性

当创建bean信息的承载实例后,并可以进行bean信息的各种属性解析,首先进入parseBeanDefinitionAttributes方法。parseBeanDefinitionAttributes方法对于element所有元素属性进行解析:

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele,String beanName,BeanDefinition containBean,AbstractBeanDefinition bd)
// 解析scope属性
// 解析singleton属性
// 解析abstract属性
// 解析lazy-init属性
// 解析autowire属性
// 解析dependency-check属性
// 解析denpends-on属性
// 解析autowire-candidate属性
// 解析primary属性
// 解析init-method属性
// 解析destory-method属性
// 解析factory-method属性
// 解析factory-bean属性

3. 解析子元素meta

public void parseMetaElement(Element ele,BeanMetadataAttributeAccessor attributeAccessor)
// 获取当前节点的所有元素
// 提取meta
// 使用key、value构造BeanMetadataAttribute
// 记录信息

4 解析子元素lookup-method

子元素lookup-method似乎并不是很常用,但是在某些时候它的确是非常有用的,通常称它为获取器注入,引用《Spring in Action》中的一句话:获取器注入是一种特殊的方法注入,它是一个方法声明为放回某种类型的bean,但是实际要返回的bean是在配置文件里面配置的,此方法在涉及有些可插拔的功能上,解除程序依赖。

public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides)
// 仅当在Spring默认bean的子元素下且为< lookup-method>时有效
// 获取要修饰的方法
// 获取配置返回的bean

上面的代码很眼熟,似乎与parseMetaElements的代码大同小异,最大的区别就是在if判断中的节点名称在这里被秀爱为LOOKUP_METHOD_ELEMENT。还有,在数据存储上面通过使用LookupOverride类型的实体进行数据承载并记录在AbstractBeanDefinition中的methodOverrides属性中。

5. 解析子元素replaced-method

这个方法主要是针对bean中replaced-method子元素的提取,方法替换:可以在运行时用新的方法替换现有的方法,与之前look-up不同的是,replace-method不但可以动态的替换返回实体bean,而且还能动态的更改原有方法的逻辑。

public void parseReplaceMethodSubElements(Element beanEle,MethodOverrides overrides)
// 仅当在Spring默认bean的子元素下且为< repalce-method>时有效
// 提取需要替换的旧方法
// 提取新的替换方法
// 记录参数

无论是look-up还是replace-method都是构造了一个MethodOverride,并记录在AbstractBeanDefinition中的methodOverrides属性中。

6. 解析子元素constructor-arg

对于constructor-arg子元素的解析,Spring通过parseConstructorArgElements函数来实现的

public void parseConstructorArgElements(Element beanEle,BeanDefinition bd)
// 解析constructor-arg子元素

这个结构遍历所有子元素,也就是提取所有的constructor-arg,然后进行解析,但是具体的解析却被安置在另一个函数parseConstructorArgElement中

public void parseConstructorArgElement(Element ele,BeanDefinition bd)
// 提取index属性
// 提取type属性
// 提取name属性
// 解析ele对应的属性元素
// 不允许重复指定相同参数
// 没有index属性则忽略去属性,自动寻找

上述过程中,首先是提取constructor-arg上的必要属性(index,type,name)。
如果配置了index属性,那么操作步骤如下:

  1. 解析constructor-arg子元素;
  2. 使用ConstructorArgumentValues.ValueHolder类型封装解析出来的元素;
  3. 将type、name、index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的indexedArgumentValues属性中;

如果没有指定index属性,那么操作步骤如下:

  1. 解析constructor-arg子元素;
  2. 使用ConstructorArgumentValues.ValueHolder类型封装解析出来的元素;
  3. 将type、name、index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的genericArgumentValues属性中;

可以看出,对于是否定制index属性来讲,Spring的处理流程是不同的,关键在于属性信息的保存位置。
了解了整个流程后,进一步了解解析构造函数配置中子元素的过程,进入parsePropertyValue:

public Object parsePropertyValue(Element ele,BeanDefinition bd,String propertyName)
// 一个属性只能对应一种类型:ref、value、list等
// 解析constructor-arg上的ref属性
// 解析constructor-arg上的value属性
// ref属性的处理,使用RuntimeBeanReference封装对应的ref名称
// value属性的处理,使用TypedStringValue封装
// 解析子元素
// 既没有ref也没有value也没有子元素时抛出异常

从代码上看,对构造函数中属性元素的解析,经历了一下几个过程:

  1. 略过description或者meta;
  2. 提取constructor-arg上的ref和value属性,以便于根据规则验证正确性,其规则为在constructor-arg上不存在以下情况。
    2.1 同时既有ref属性又有value属性
    2.2 存在ref属性或者value属性且又有子元素
  3. ref属性的处理,使用RuntimeBeanReference封装对应的ref名称;
  4. value属性的处理,使用TypedStringValue封装;
  5. 子元素的处理。

对于子元素的处理,parsePropertySubElement中实现了各种元素的分类处理:

public Object parsePropertySubElement(Element ele,BeanDefinition bd,String defaultValueType)
// 解析local
// 解析parent
// 对ideref元素的解析
// 对value子元素的解析
// 对null子元素的解析
// 对array子元素的解析
// 对list子元素的解析
// 对set子元素的解析
// 对map子元素的解析
对props子元素的解析

可以看出,上面的函数实现了所有可支持的子类的分类处理。

7. 解析子元素property

parsePropertyElement函数完成了对property属性的提取,具体的解析过程如下:

public void parsePropertyElement(Element ele,BeanDefinition bd)
// 获取配置元素中的name的值
// 不允许多次对同一属性配置

可以看出上面的函数与构造函数注入的方式不同的是将方绘制使用PropertyValue进行封装,并记录在BeanDefinition中的propertyValues属性中

8. 解析子元素qualifier

对于qualifier元素的获取,接触更多的是注解的形式,在使用Spring框架中进行自动注入时,Spring容器中匹配的候选Bean数目必须有且仅有一个,当找找不到一个匹配的Bean时,Spring容器将会抛出BeanCreationException异常,并指出必须至少拥有一个匹配的Bean。

Spring允许通过Qualifier指定注入Bean的名称,这样就消除歧义了。

3.1.2 AbstractBeanDefinition属性

至此便完成了对XML文档到GenericBeanDefinition的转换,也就是到这里,XML中所有配置都可以在GenericBeanDefinition的实例中找到对应的配置。

GenericBeanDefinition只是子类实现,而大部分通用属性都保存在了AbstractBeanDefinition中。

public abstract class AbstractDeanDefinition extends BeanMetadateAttributeAccessor implements BeanDefinition,Cloneable{
// bean的作用范围,对应bean属性的scope
// 是否是单例,来自bean属性scope
// 是否是原型,来自bean属性scope
// 是否是抽象,来自bean属性abstract
// 是否延迟加载,来自lazy-init
// 自动注入模式,对应bean属性autowire
// 依赖检查,Spring 3.0后弃用这个属性
// 用来表示一个bean的实例化靠另一个bean先实例化,对应bean属性depend-on
// autowire-candidate属性设置为false,这样容器在查找自动装配对象时,将不考虑该bean,即它不会被考虑作为其他bean自动装配的候选者,但是该bean还是可以使用自动装配来注入其他bean的。对应bean属性autowire-candidate
// 自动装配时当出现多个bean候选者时,将作为首选者,对应bean属性primary
// 用于记录Qualifier,对应子元素Qualifier
// 允许访问非公开的构造器和方法,程序设置
// 是否以一种宽松的模式解析构造函数
// 记录构造函数注入属性,对应bean属性constructor-arg
// 普通属性集合
// 方法重写的持有者,记录lookup-method、replace-method元素
// 对应bean属性factory-bean
// 对应bean属性factory-method
// 初始化方法,对应bean属性init-mothod方法
// 销毁方法,对应bean属性destory-method方法
// 是否执行init-mothod方法,程序设置
// 是否执行destory-method方法,程序设置
// 是否是用户定义的而不是应用程序本身定义的,创建AOP时候为true,程序设置
// 定义bean的应用,APPLICATION:用户;INFRASRUCTRUE:完全内部使用,与用户无关;SUPPORT:某些复杂配置的一部分,程序设置
// bean的描述信息
// 这个bean定义的资源
}

3.1.3 解析默认标签中的自定义标签元素

我们已经用了大量的篇幅分析了BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele)这句代码,接下来,要进行bdHolder = delegate.decorateBeanDefinitionIfRequired(ele,bdHolder)代码的分析。

当Spring中的bean使用的是默认的标签配置,但是子元素却使用了自定义的配置时,这句代码便会起作用。

public BeanDefinitionHolder decorateBeanDefinitionIfRequire(Element ele,BeanDefinitionHolder definitionHolder,null)

这里将函数的第三个参数设置为空,那么第三个参数是做什么用的呢?什么情况下不为空呢?其实第三个参数是父类bean,当某个嵌套配置进行分析时,这里需要传递父类beanDefinition。分析源码的遏制这里传递参数其实是为了使用父类的scope属性,以备子类若没有设置scope是默认使用父类的属性,这里分析的是顶层配置,所以传递null,将第三个函数设置为空后进一步跟踪函数:

public BeanDefinitionHolder decorateBeanDefinitionIfRequire(Element ele,BeanDefinitionHolder definitionHolder,null)
// 遍历所有的属性,看看是否有适用于修饰的属性
// 遍历所有的子节点,看看是否有适用于修饰的子元素

上面的代码,可以看到函数分别对元素的所有属性以及子元素进行了decorateIfReuired函数的调用

private BeanDefinitionHolder decorateIfRequired(Node node,BeanDefinitionHolder originalDef,BeanDefinition containingBd)
// 获取自定义标签的命名空间
// 对于非默认标签进行修饰
// 根据命名空间找到对应的处理器
// 进行修饰

程序走到这里,调理其实非常请处理了,首先获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所有对应的NamespaceHandler进行进一步解析。

总结一下decorateBeanDefinitionIfRequired方法的作用,在decorateBeanDefinitionIfDefinitionIfRequired中可以看出对程序默认标签的处理其实是直接略过的,因为默认的标签到这里已经处理完了,这里只对自定义标签或者bean的自定义属性感兴趣。在方法中实现了寻找自定义标签并根据自定义标签寻找命名空间处理器,并进一步解析。

3.1.4 注册解析的BeanDefinition

对于配置文件,解析是解析完了,装饰也装饰完了,对于得到的beanDefinition已经可以满足后续的使用要求了,为剩下的工作就是注册了,也就是processBeanDefinition函数中的BeanDefinitionReaderUtils.registryBeanDefinition(bdHolder,getReaderContext().getRegistry())代码的解析了。

public static void registryBeanDefinition(BeanDefinitionHolder definitionHolder,BeanDefinitionRegistry registry)
// 使用beanName做唯一标识注册
// 注册所有的别名

从上面代码可以看出,解析的beanDefinition都会被注册到BeanDefinitionRegistry类型的实例registry中,而对于beanDefinition的注册分为两部分,通过beanName的注册以及通过别名的注册。

1. 通过beanName注册BeanDefinition

对于beanName的注册,或许许多人认为的方式就是将beanDefinition直接放入map中就好了,使用beanName作为key,确实,Spring就是这么做的,只不过除此之外,它还做了点别的事。

public void registryBeanDefinition(String beanName,Definition beanDefinition) throws BeanDefinitionStoreException
// 注册前的最后一次校验,这里的校验不同于之前的XML文件校验,主要是对于AbstractBeanDefinition属性中的methodOverrides校验,校验methodOverrides是否与工厂方法并存或者methodOverrides对应方法根本不存在
// 因为beanDefinitionMap是全局变量,这里肯定会存在并发访问的情况
// 处理注册已经注册的beanName情况
// 如果对应的BeanName已经注册且在配置中配置了bean不允许被覆盖,则抛出异常
// 记录beanName
// 注册beanDefinition
// 重置所有beanName对应的缓存

上面的代码可以看出,对于bean的注册处理方法上,主要进行了几个步骤:

  1. 对AbstractBeanDefinition的校验。在解析XML文件的时候,是针对XML格式的校验,而此时的校验时是对于AbstractBeanDefinition的methodOverrides属性的;
  2. 对beanName已经注册情况的处理。如果没有设置不允许bean的覆盖,则需要抛出异常。
  3. 加入map缓存
  4. 清除解析之前留下的对应beanName的缓存
2. 通过别名注册BeanDefinition

在理解了注册bean的原理之后,理解别名注册的原理就容易多了

public void registryAlias(String name,String alisa)
// 如果beanName与alisa相同的话不记录alisa,并删除对应的alisa
// 如果alisa不允许被覆盖则抛异常

由以上代码中可以得知注册alisa的步骤如下:

  1. alisa与beanName相同情况处理,若alisa与beanName并名称相同则不要处理并删除原有alisa;
  2. alisa覆盖处理,若alisaName已经使用并已经指向了另一beanName则需要用户的设置进行处理;
  3. alisa循环检查,单A->B存在时,若再次出现A->C->B的时候则会抛出异常;
  4. 注册alisa。
3.1.5 通知监听器解析及注册完成

通过代码getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此项工作,这里的实现只为扩展,当程序开发人员需要注册BeanDefinition时间进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中,目前Spring中并没有对此事件做任何逻辑处理。

3.2 alisa标签的解析

通过上面较长的篇幅终于分析完了默认标签中对bean标签的处理,那么之前提到过的,对配置文件的解析保罗import标签、alisa标签、bean标签、beans标签的处理,现在已经完成了bean标签的解析,其他的步骤也都是围绕着第3个解析进行的。在分析了第3个解析步骤后,在回过头来看看对alisa标签的解析。

在对bean进行定义时,除了使用id属性来指定名称之外,为了提供对个名称,可以使用alisa标签来指定,而所有的这些名称都指向同一个bean,在某些情况下提供别名非常有用,比如为了让应用的每一个组件能更容易的对公共组件进行引用。

然而,在定义bean是就指定所有的别名并不是总是恰当的。又是期望能在当前位置为那些在别处定义的bean引入别名。在XML配置文件中,可用单独的< alisa/>元素来完成bean别名的定义。

在之前的章节已经讲过对于bean中name元素的解析,那么再来深入分析下alisa标签的解析过程

processAlisaRegistation(Elemet ele)
// 获取beanName
// 获取alisa
// 注册alisa
// 别名注册后通知监听器做相应处理

3.3 import标签的解析

对于Spring配置文件的编写,分模块是大多数人能想到的方法,使用import是个好办法,applicationContext.xml文件中使用import的方法导入有模块配置文件,以后如果有新的模块的加入,那就可以简单修改这个文件了,这样可以大大简化了配置后期维护的复杂度,并使配置模块化,易于管理,import标签的解析方法:

protected void importBeanDefinitionResource(Element ele)
// 获取resource属性
// 如果不存在resource属性在不做处理
// 解析系统属性,格式如:“${user.dir}”
// 判定location是绝对URI还是先对URI
// 如果是绝对URI则直接根据地址加载对应的配置文件
// 如果是相对地址则根据相对地址计算出绝对地址
// Resource存在多个子类实现,而每个resource的createRelative方法都不一样,所以先使用子类的方法进行解析
// 如果解析不成功,则使用默认解析器ResourcePatternResolver进行解析
解析后进行监听器激活处理

上面的代码不能,大致流程如下:

  1. 获取resource属性所表示的路径;
  2. 解析路径中的系统属性;
  3. 判定location是绝对路径还是相对路径;
  4. 如果是绝对路径则递归调用bean的解析过程,进行另一次解析;
  5. 如果是相对路径则计算绝对路径并进行解析;
  6. 通知监听器,解析完成。

3.4 嵌入式beans标签的解析

对于嵌入式beans标签,非常类似于import标签所提供的功能,并没有太多可讲,与单独配置文件并没有太大的差别,无法是递归调用beans的解析过程。

Spring源码深度解析笔记(10)——默认标签的解析相关推荐

  1. spring源码深度解析— IOC 之 默认标签解析(下)

    默认标签中的自定义标签解析 注册解析的BeanDefinition 通过beanName注册BeanDefinition 通过别名注册BeanDefinition alias标签的解析 import标 ...

  2. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  3. 《Spring源码深度解析 郝佳 第2版》ApplicationContext

    往期博客: <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度 ...

  4. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理实现(八)

    继上一篇博客,我们继续来分析下面示例的 Spring 静态代理源码实现. 静态 AOP使用示例 加载时织入(Load -Time WEaving,LTW) 指的是在虚拟机载入字节码时动态织入 Aspe ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  6. 《Spring源码深度解析 郝佳 第2版》bean的加载、循环依赖的解决

    往期博客: <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 往期博客完成了xml文件加载 ...

  7. Spring源码深度解析:三、容器的刷新 - refresh()

    一.前言 文章目录:Spring源码深度解析:文章目录 我们先通过Spring源码的整体流程,来了解Spring的工作流程是什么,接着根据这个工作流程一步一步的阅读源码 二.Spring容器的启动 p ...

  8. Spring源码深度解析(郝佳)-学习-Spring消息-整合RabbitMQ及源码解析

      我们经常在Spring项目中或者Spring Boot项目中使用RabbitMQ,一般使用的时候,己经由前人将配置配置好了,我们只需要写一个注解或者调用一个消息发送或者接收消息的监听器即可,但是底 ...

  9. 《Spring源码深度解析 郝佳 第2版》AOP

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

  10. 《Spring源码深度解析 郝佳 第2版》SpringBoot体系分析、Starter的原理

    往期博客 <Spring源码深度解析 郝佳 第2版>容器的基本实现与XML文件的加载 <Spring源码深度解析 郝佳 第2版>XML标签的解析 <Spring源码深度解 ...

最新文章

  1. 我当了二十几年总经理,总结出这10条经验,看懂了你将少走些弯路
  2. 算法------------完全平方数(Java版本)
  3. 《ASP.NET Core 微服务实战》-- 读书笔记(第7章)
  4. 利用Helm简化Kubernetes应用部署(2)
  5. [vue] 怎么缓存当前打开的路由组件,缓存后想更新当前组件怎么办呢?
  6. 基于Passthru的NDIS开发的个人理解
  7. linux系统中安装python_2. Linux 下安装python
  8. 【今日CS 视觉论文速览】Thu, 13 Dec 2018
  9. jQuery源码研究学习笔记(二)
  10. 制度化规范化标准化精细化_制度化、标准化、流程化,走向精细化管理的蜕变...
  11. 代替嵌套循环java_蓝石榴_个人博客_Java中for循环嵌套的替换优化
  12. VS(Visual Studio)自动创建的文件格式
  13. 金融反欺诈(项目练习)
  14. [转]NHibernate中DateTime,int,bool空值的处理方法
  15. [bzoj 5064]B-number
  16. SLAM14讲学习笔记(一) 李群李代数基础
  17. Android 系统默认铃声修改 添加删除铃声
  18. stm32开发之使用Keil MDK以及标准外设库创建STM32工程
  19. 网络攻防技术——黑客攻防
  20. 【MM小贴士】SAP工序外协初探

热门文章

  1. (转载)高光谱数据读取by multibandread函数
  2. 为RecyclerView打造通用Adapter
  3. JZOJ 4.2 C组 打鼹鼠
  4. bzoj3224 普通平衡树(splay 模板)
  5. hadoop2.6---常用命令
  6. linux学习笔记1(第一本笔记)
  7. asp.net使用Mysql乱码处理
  8. 0可用,0已用 U盘问题 重解~
  9. Hough检测直线原理及c++代码
  10. 【持续更新】java 指令释疑