博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring源码学习笔记(5)
阅读量:4325 次
发布时间:2019-06-06

本文共 49094 字,大约阅读时间需要 163 分钟。

 


Spring源码学习笔记(五)

  前言--

    最近花了些时间看了《Spring源码深度解析》这本书,算是入门了Spring的源码吧。打算写下系列文章,回忆一下书的内容,总结代码的运行流程。推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的《Spring源码深度解析》这本书,会是个很好的入门


 

    写下一句话,开篇不尴尬  ----  上篇文章中梳理到 Spring 加载资源文件后开始解析 Bean, 现在我们从两个解析函数 parseDefaultElement() parseCustomElement() 开始继续回顾。

 


 

解析默认标签  parseDefaultElement()

    先来看看 parseDefaultElement() 实现逻辑:

1     private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { 2         if(delegate.nodeNameEquals(ele, "import")) { 3             //第一步:  处理 import 标签 4             this.importBeanDefinitionResource(ele); 5         } else if(delegate.nodeNameEquals(ele, "alias")) { 6             //第二步:  处理 alias 标签 7             this.processAliasRegistration(ele); 8         } else if(delegate.nodeNameEquals(ele, "bean")) { 9             //第三步:  处理 bean 标签  =============== 重点10             this.processBeanDefinition(ele, delegate);11         } else if(delegate.nodeNameEquals(ele, "beans")) {12             //第四步:  处理 beans 标签13             this.doRegisterBeanDefinitions(ele);14         }15 16     }

 

    呵呵哈, 什么都没写, 接着往下看, 一个个分析四中标签的解析。


 

一:bean 标签

    processBeanDefinition() 方法的逻辑:

1     protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { 2         //第一步:  BeanDefinitionHolder  类的封装 3         BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 4         if(bdHolder != null) { 5             //第二步:  自定义属性的处理 6             bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); 7  8             try { 9               //第三步;  注册 bean10                 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());11             } catch (BeanDefinitionStoreException var5) {12                 this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);13             }14             //第四步:  发布事件15             this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));16         }17 18     }

 

    首先看第一步中,  BeanDefinitionParserDelegate 类的 parseBeanDefinitionElement() 方法:

1     public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {2         return this.parseBeanDefinitionElement(ele, (BeanDefinition)null);3     }

 

1     public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { 2         //第一步:  解析 id 和 name 属性 3         String id = ele.getAttribute("id"); 4         String nameAttr = ele.getAttribute("name"); 5         List
aliases = new ArrayList(); 6 if(StringUtils.hasLength(nameAttr)) { 7 //第二步: 分割了 name 属性 8 String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; "); 9 aliases.addAll(Arrays.asList(nameArr));10 }11 12 String beanName = id;13 if(!StringUtils.hasText(id) && !aliases.isEmpty()) {14 beanName = (String)aliases.remove(0);15 16 }17 18 if(containingBean == null) {19 this.checkNameUniqueness(beanName, aliases, ele);20 }21 //第三步: 解析属性, 封装到 GenericBeanDefinition 类中22 AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);23 if(beanDefinition != null) {24 //第四步: 没有指定 beanName , 生成 beanName25 if(!StringUtils.hasText(beanName)) {26 try {27 if(containingBean != null) {28 beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);29 } else {30 beanName = this.readerContext.generateBeanName(beanDefinition);31 String beanClassName = beanDefinition.getBeanClassName();32 if(beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {33 aliases.add(beanClassName);34 }35 }36 } catch (Exception var9) {37 this.error(var9.getMessage(), ele);38 return null;39 }40 }41 42 String[] aliasesArray = StringUtils.toStringArray(aliases);43 //第五步: beanDefinition 封装到 BeanDefinitionHolder中44 return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);45 } else {46 return null;47 }48 }

 

    在 parseBeanDefinitionElement() 方法中, 主要的是第二步解析属性 parseBeanDefinitionElement() 方法:
1     public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) { 2         this.parseState.push(new BeanEntry(beanName)); 3         String className = null; 4         if(ele.hasAttribute("class")) { 5              //第一步:  解析 class 属性 6             className = ele.getAttribute("class").trim(); 7         } 8  9         try {10             String parent = null;11             if(ele.hasAttribute("parent")) {12                 parent = ele.getAttribute("parent");13             }14             //第二步:  封装 AbstractBeanDefinition 的 GenericBeanDefinition15             AbstractBeanDefinition bd = this.createBeanDefinition(className, parent);16             this.parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);   /** 解析属性 */17            //第三步:  设置 description 属性 bd.setDescription(DomUtils.getChildElementValueByTagName(ele, "description"));18             //第四步:  解析 元数据19             this.parseMetaElements(ele, bd);20             //第五步:  解析 lookup-method 属性21             this.parseLookupOverrideSubElements(ele, bd.getMethodOverrides());22             //第六步:  解析 replaced-method 属性23             this.parseReplacedMethodSubElements(ele, bd.getMethodOverrides());24             //第七步:  解析 构造函数 参数25             this.parseConstructorArgElements(ele, bd);26             //第八步:  解析 property 元素27             this.parsePropertyElements(ele, bd);28             //第九步:  解析 qualifier 元素29             this.parseQualifierElements(ele, bd);30             bd.setResource(this.readerContext.getResource());31             bd.setSource(this.extractSource(ele));32             AbstractBeanDefinition var7 = bd;33             return var7;34         }35         /** 省略了 catch 语句 */ 36         finally {37             this.parseState.pop();38         }39 40         return null;41     }

    接下来详细说明  parseBeanDefinitionElement() 方法的步骤, 因为很多, 开一个新行,虽然很多, 但是总体的思路还是很清晰的, 就一类元素有对应的解析的方法, 不要乱了阵脚, 战略上藐视一下。  o(* ̄︶ ̄*)o


parseBeanDefinitionElement() 中的方法 : 

  (一)  createBeanDefinition()

    首先了解一下各种 BeanDefinition 之间的关系:

    

    XML 文件当中的 <bean> 解析之后, 封装成 BeanDefinition 对象, 并注册到 BeanDefinitionRegistry 类中, 主要以 map 的方式保存, 并为后续的操作所使用。

    然后在来看看 createBeanDefinition() 方法的实现逻辑:

1     protected AbstractBeanDefinition createBeanDefinition(String className, String parentName) throws ClassNotFoundException {2         return BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, this.readerContext.getBeanClassLoader());3     }

 

1     public static AbstractBeanDefinition createBeanDefinition(String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException { 2         //第一步: 封装的 GenericBeanDefinition  实例 3         GenericBeanDefinition bd = new GenericBeanDefinition(); 4         bd.setParentName(parentName); 5         if(className != null) { 6             if(classLoader != null) { 7                 //第二步:  存在 classLoader ,则反射创建实例 8                 bd.setBeanClass(ClassUtils.forName(className, classLoader)); 9             } else {10                 //第三步:  不存在 clasLoader ,只能记录一下 name 了11                 bd.setBeanClassName(className);12             }13         }14 15         return bd;16     }

 

  (二) parseBeanDefinitionAttributes()

     对 element 的属性进行解析的实现逻辑:

1     public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, BeanDefinition containingBean, AbstractBeanDefinition bd) { 2         //第一步:  解析 scope 属性 3         if(ele.hasAttribute("scope")) { 4             bd.setScope(ele.getAttribute("scope")); 5         } else if(containingBean != null) { 6             bd.setScope(containingBean.getScope()); 7         } 8         //第二步:  解析 abstract 属性 9         if(ele.hasAttribute("abstract")) {10             bd.setAbstract("true".equals(ele.getAttribute("abstract")));11         }12         //第三步:  解析 lazy-init 属性13         String lazyInit = ele.getAttribute("lazy-init");14         if("default".equals(lazyInit)) {15             lazyInit = this.defaults.getLazyInit();16         }17 18         bd.setLazyInit("true".equals(lazyInit));19         //第四步:  解析 autowire 属性20         String autowire = ele.getAttribute("autowire");21         bd.setAutowireMode(this.getAutowireMode(autowire));22         //第五步:  解析 dependency-check 属性23         String dependencyCheck = ele.getAttribute("dependency-check");24         bd.setDependencyCheck(this.getDependencyCheck(dependencyCheck));25         //第六步:  解析 depends-on 属性26         String autowireCandidate;27         if(ele.hasAttribute("depends-on")) {28             autowireCandidate = ele.getAttribute("depends-on");29             bd.setDependsOn(StringUtils.tokenizeToStringArray(autowireCandidate, ",; "));30         }31         //第七步:  解析 autowire-candidate 属性32         autowireCandidate = ele.getAttribute("autowire-candidate");33         String destroyMethodName;34         if(!"".equals(autowireCandidate) && !"default".equals(autowireCandidate)) {35             bd.setAutowireCandidate("true".equals(autowireCandidate));36         } else {37             destroyMethodName = this.defaults.getAutowireCandidates();38             if(destroyMethodName != null) {39                 String[] patterns = StringUtils.commaDelimitedListToStringArray(destroyMethodName);40                 bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));41             }42         }43         //第八步:  解析 primary 属性44         if(ele.hasAttribute("primary")) {45             bd.setPrimary("true".equals(ele.getAttribute("primary")));46         }47         //第九步:  解析 init-method 属性48         if(ele.hasAttribute("init-method")) {49             destroyMethodName = ele.getAttribute("init-method");50             if(!"".equals(destroyMethodName)) {51                 bd.setInitMethodName(destroyMethodName);52             }53         } else if(this.defaults.getInitMethod() != null) {54             bd.setInitMethodName(this.defaults.getInitMethod());55             bd.setEnforceInitMethod(false);56         }57         //第十步:  解析 destroy-method 属性58         if(ele.hasAttribute("destroy-method")) {59             destroyMethodName = ele.getAttribute("destroy-method");60             if(!"".equals(destroyMethodName)) {61                 bd.setDestroyMethodName(destroyMethodName);62             }63         } else if(this.defaults.getDestroyMethod() != null) {64             bd.setDestroyMethodName(this.defaults.getDestroyMethod());65             bd.setEnforceDestroyMethod(false);66         }67         //第十一步:  解析 destroy-method 属性68         if(ele.hasAttribute("factory-method")) {69             bd.setFactoryMethodName(ele.getAttribute("factory-method"));70         }71         //第十二步:  解析 factory-bean 属性72         if(ele.hasAttribute("factory-bean")) {73             bd.setFactoryBeanName(ele.getAttribute("factory-bean"));74         }75 76         return bd;77     }

 

    怎么说呢, 天啦噜,一大坨的代码, 然而只是解析了各种各样的属性, 并设置到第一步中创建的 AbstractBeanDefinition 当中。

  (三) parseMetaElements() 

1     public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { 2         NodeList nl = ele.getChildNodes(); 3         //第一步:  遍历所有的子元素 4         for(int i = 0; i < nl.getLength(); ++i) { 5             Node node = nl.item(i); 6             //第二步:  判断元素的类型 7             if(this.isCandidateElement(node) && this.nodeNameEquals(node, "meta")) { 8                 Element metaElement = (Element)node; 9                 String key = metaElement.getAttribute("key");10                 String value = metaElement.getAttribute("value");11                 //第三步:  构造 BeanMetadataAttribute  类12                 BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);13                 attribute.setSource(this.extractSource(metaElement));14                 attributeAccessor.addMetadataAttribute(attribute);15             }16         }17 18     }

 

    (四) parseLookupOverrideSubElements() ,  parseReplacedMethodSubElements()

  lookup-method 以及 replaced-method 属性的使用请有事找度娘!!!o(^▽^)o

1     public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { 2         NodeList nl = beanEle.getChildNodes(); 3         //第一步:  遍历子元素 4         for(int i = 0; i < nl.getLength(); ++i) { 5             Node node = nl.item(i); 6             //第二步:  判断元素的类型 7             if(this.isCandidateElement(node) && this.nodeNameEquals(node, "lookup-method")) { 8                 Element ele = (Element)node; 9                 //第三步:  获取重写的方法10                 String methodName = ele.getAttribute("name");11                 String beanRef = ele.getAttribute("bean");12                 //第四步:  封装为 LookupOverride 类13                 LookupOverride override = new LookupOverride(methodName, beanRef);14                 override.setSource(this.extractSource(ele));15                 overrides.addOverride(override);16             }17         }18 19     }
1     public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { 2         NodeList nl = beanEle.getChildNodes(); 3         //第一步:  遍历子元素 4         for(int i = 0; i < nl.getLength(); ++i) { 5             Node node = nl.item(i); 6             //第二步:  判断夙愿类型 7             if(this.isCandidateElement(node) && this.nodeNameEquals(node, "replaced-method")) { 8                 //第三步: 获取 要替换的方法 和 替换的方法 9                 Element replacedMethodEle = (Element)node;10                 String name = replacedMethodEle.getAttribute("name");11                 String callback = replacedMethodEle.getAttribute("replacer");12                 //第四步:  封装成 ReplaceOverride 类13                 ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);14                 //第五步:  方法的参数15                 List
argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, "arg-type");16 Iterator var11 = argTypeEles.iterator();17 18 while(var11.hasNext()) {19 Element argTypeEle = (Element)var11.next();20 String match = argTypeEle.getAttribute("match");21 match = StringUtils.hasText(match)?match:DomUtils.getTextValue(argTypeEle);22 if(StringUtils.hasText(match)) {23 replaceOverride.addTypeIdentifier(match);24 }25 }26 27 replaceOverride.setSource(this.extractSource(replacedMethodEle));28 overrides.addOverride(replaceOverride);29 }30 }31 32 }

 

   (五) parseConstructorArgElements()

    解析构造函数参数的实现逻辑:

1     public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) { 2         NodeList nl = beanEle.getChildNodes(); 3         //第一步:  遍历所有元素 4         for(int i = 0; i < nl.getLength(); ++i) { 5             Node node = nl.item(i); 6             //第二步:  判断元素类型 7             if(this.isCandidateElement(node) && this.nodeNameEquals(node, "constructor-arg")) { 8                 //第三步:  解析构造函数参数 9                 this.parseConstructorArgElement((Element)node, bd);10             }11         }12 13     }

 

     前方高能~~~~~ 一大波代码正在来袭!!!

1     public void parseConstructorArgElement(Element ele, BeanDefinition bd) { 2         //第一步:  提取 index, type, name 属性 3         String indexAttr = ele.getAttribute("index"); 4         String typeAttr = ele.getAttribute("type"); 5         String nameAttr = ele.getAttribute("name"); 6         if(StringUtils.hasLength(indexAttr)) { 7             try { 8                 int index = Integer.parseInt(indexAttr); 9                 if(index < 0) {10                     this.error("'index' cannot be lower than 0", ele);11                 } else {12                     try {13                         this.parseState.push(new ConstructorArgumentEntry(index));14                         //第二步:  解析 ele 对应的属性元素15                         Object value = this.parsePropertyValue(ele, bd, (String)null);16                         //第四步:  封装到 ValueHolder 类中17                         ValueHolder valueHolder = new ValueHolder(value);18                         if(StringUtils.hasLength(typeAttr)) {19                             valueHolder.setType(typeAttr);20                         }21 22                         if(StringUtils.hasLength(nameAttr)) {23                             valueHolder.setName(nameAttr);24                         }25 26                         valueHolder.setSource(this.extractSource(ele));27                         //第五步:  相同参数重复指定的情况处理28                         if(bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {29                             this.error("Ambiguous constructor-arg entries for index " + index, ele);30                         } else {31                         //第六步:  存在 index 属性的情况时, 封装到 BeanDefinition 的 constructorArgumentValues 的 indexedArgumentValue 中32                             bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);33                         }34                     } finally {35                         this.parseState.pop();36                     }37                 }38             } catch (NumberFormatException var19) {39                 this.error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);40             }41         } else {42             try {43                 this.parseState.push(new ConstructorArgumentEntry());44                 Object value = this.parsePropertyValue(ele, bd, (String)null);45                 ValueHolder valueHolder = new ValueHolder(value);46                 if(StringUtils.hasLength(typeAttr)) {47                     valueHolder.setType(typeAttr);48                 }49 50                 if(StringUtils.hasLength(nameAttr)) {51                     valueHolder.setName(nameAttr);52                 }53 54                 valueHolder.setSource(this.extractSource(ele));55                 //第七步: 不存在 index 属性时, 封装到 BeanDefinition 的 constructorArgumentValues 的 genericArgumentValue 中56 57           bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);58             } finally {59                 this.parseState.pop();60             }61         }62 63     }

 

  (六) parsePropertyValue()

    解析构造函数配置中子元素的实现逻辑,  感觉就是上一步代码没做完的事堆给下一个方法收拾了(*/ω╲*)

 

1     public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { 2         String elementName = propertyName != null?"
element for property '" + propertyName + "'":"
element"; 3 NodeList nl = ele.getChildNodes(); 4 Element subElement = null; 5    //第一步: 遍历所有的子元素 6 for(int i = 0; i < nl.getLength(); ++i) { 7 Node node = nl.item(i); 8 //第二步: 略过 description 和 meta 9 if(node instanceof Element && !this.nodeNameEquals(node, "description") && !this.nodeNameEquals(node, "meta")) {10 if(subElement != null) {11 this.error(elementName + " must not contain more than one sub-element", ele);12 } else {13 subElement = (Element)node;14 }15 }16 }17 //第三步: 解析 constructor-arg 上的 ref 和 value 属性 (!! 注意: 不存在 1,既有 ref 又有 vlaue 属性 2,存在 ref 或 value 且还有子元素)18 boolean hasRefAttribute = ele.hasAttribute("ref");19 boolean hasValueAttribute = ele.hasAttribute("value");20 if(hasRefAttribute && hasValueAttribute || (hasRefAttribute || hasValueAttribute) && subElement != null) {21 this.error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);22 }23 24 if(hasRefAttribute) {25 String refName = ele.getAttribute("ref");26 if(!StringUtils.hasText(refName)) {27 this.error(elementName + " contains empty 'ref' attribute", ele);28 }29 //第四步: ref 属性使用 RuntimeBeanReference 处理30 RuntimeBeanReference ref = new RuntimeBeanReference(refName);31 ref.setSource(this.extractSource(ele));32 return ref;33 } else if(hasValueAttribute) {34 //第五步: value 属性使用 TypedStringValue 处理35 TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute("value"));36 valueHolder.setSource(this.extractSource(ele));37 return valueHolder;38 } else if(subElement != null) {39 //第六步: 解析子元素40 return this.parsePropertySubElement(subElement, bd);41 } else {42 this.error(elementName + " must specify a ref or value", ele);43 return null;44 }45 }

 

 

 

    在 parsePropertyValue() 中, 第六步解析子元素 parsePropertySubElement() 方法的实现:

 

1     public Object parsePropertySubElement(Element ele, BeanDefinition bd) {2         return this.parsePropertySubElement(ele, bd, (String)null);3     }

 

 

 

 

1     public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) { 2         //第一步:  不是默认命名空间的元素的处理 3         if(!this.isDefaultNamespace((Node)ele)) { 4             return this.parseNestedCustomElement(ele, bd); 5         } else if(this.nodeNameEquals(ele, "bean")) { 6             //第二步: 对 Bean 元素的处理 7             BeanDefinitionHolder nestedBd = this.parseBeanDefinitionElement(ele, bd); 8             if(nestedBd != null) { 9                 nestedBd = this.decorateBeanDefinitionIfRequired(ele, nestedBd, bd);10             }11 12             return nestedBd;13         } else if(this.nodeNameEquals(ele, "ref")) {14             //第三步: 对 ref 属性的处理15             String refName = ele.getAttribute("bean");16             boolean toParent = false;17             if(!StringUtils.hasLength(refName)) {18                 refName = ele.getAttribute("local");19                 if(!StringUtils.hasLength(refName)) {20                     refName = ele.getAttribute("parent");21                     toParent = true;22                     if(!StringUtils.hasLength(refName)) {23                         this.error("'bean', 'local' or 'parent' is required for 
element", ele);24 return null;25 }26 }27 }28 29 if(!StringUtils.hasText(refName)) {30 this.error("
element contains empty target attribute", ele);31 return null;32 } else {33 RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent);34 ref.setSource(this.extractSource(ele));35 return ref;36 }37 } else if(this.nodeNameEquals(ele, "idref")) {38 //第四步: 对 idref 属性的处理39 return this.parseIdRefElement(ele);40 } else if(this.nodeNameEquals(ele, "value")) {41 //第五步: 对 value 属性的处理42 return this.parseValueElement(ele, defaultValueType);43 } else if(this.nodeNameEquals(ele, "null")) {44 //第六步: 对 null 元素的解析45 TypedStringValue nullHolder = new TypedStringValue((String)null);46 nullHolder.setSource(this.extractSource(ele));47 return nullHolder;48 } 49 //第七步: 对各种集合属性的解析50 else if(this.nodeNameEquals(ele, "array")) {51 return this.parseArrayElement(ele, bd);52 } else if(this.nodeNameEquals(ele, "list")) {53 return this.parseListElement(ele, bd);54 } else if(this.nodeNameEquals(ele, "set")) {55 return this.parseSetElement(ele, bd);56 } else if(this.nodeNameEquals(ele, "map")) {57 return this.parseMapElement(ele, bd);58 } else if(this.nodeNameEquals(ele, "props")) {59 return this.parsePropsElement(ele);60 } else {61 this.error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);62 return null;63 }64 }

 

 

 

   (七) parsePropertyElements()

    解析 <property> 元素的实现逻辑:

1     public void parsePropertyElements(Element beanEle, BeanDefinition bd) { 2         NodeList nl = beanEle.getChildNodes(); 3         //第一步:  遍历所有的属性 4         for(int i = 0; i < nl.getLength(); ++i) { 5             Node node = nl.item(i); 6             //第二步: 判断元素的类型 7             if(this.isCandidateElement(node) && this.nodeNameEquals(node, "property")) { 8                 this.parsePropertyElement((Element)node, bd); 9             }10         }11 12     }

 

1    public void parsePropertyElement(Element ele, BeanDefinition bd) { 2         //第一步:  获取 name 属性 3         String propertyName = ele.getAttribute("name"); 4         if(!StringUtils.hasLength(propertyName)) { 5             this.error("Tag 'property' must have a 'name' attribute", ele); 6         } else { 7             this.parseState.push(new PropertyEntry(propertyName)); 8  9             try {10                 //第二步:  处理同一属性多次配置的情况11                 if(bd.getPropertyValues().contains(propertyName)) {12                     this.error("Multiple 'property' definitions for property '" + propertyName + "'", ele);13                     return;14                 }15                 //第三步:  解析元素属性16                 Object val = this.parsePropertyValue(ele, bd, propertyName);17                 PropertyValue pv = new PropertyValue(propertyName, val);18                 this.parseMetaElements(ele, pv);19                 pv.setSource(this.extractSource(ele));20                 //第四步:  添加属性21           bd.getPropertyValues().addPropertyValue(pv);22             } finally {23                 this.parseState.pop();24             }25 26         }27     }

 

   (八) parseQualifierElements()

    解析 qualifier 属性的实现逻辑:

1     public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) { 2         //第一步:  获取 type 属性 3         String typeName = ele.getAttribute("type"); 4         if(!StringUtils.hasLength(typeName)) { 5             this.error("Tag 'qualifier' must have a 'type' attribute", ele); 6         } else { 7             this.parseState.push(new QualifierEntry(typeName)); 8  9             try {10                 //第二步: 封装的 AutowireCandidateQualifier 类11                 AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);12                 qualifier.setSource(this.extractSource(ele));13                 String value = ele.getAttribute("value");14                 if(StringUtils.hasLength(value)) {15                     qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value);16                 }17 18                 NodeList nl = ele.getChildNodes();19                 //第三步:  遍历所有的子元素20                 for(int i = 0; i < nl.getLength(); ++i) {21                     Node node = nl.item(i);22                      //第四步:  判断子元素的类型23                     if(this.isCandidateElement(node) && this.nodeNameEquals(node, "attribute")) {24                         Element attributeEle = (Element)node;25                         String attributeName = attributeEle.getAttribute("key");26                         String attributeValue = attributeEle.getAttribute("value");27                         if(!StringUtils.hasLength(attributeName) || !StringUtils.hasLength(attributeValue)) {28                             this.error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle);29                             return;30                         }31                         //第五步:  封装的 BeanMetadataAttribute 类性32                         BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);33                         attribute.setSource(this.extractSource(attributeEle));34                         //第六步:  qualifier 添加属性35                         qualifier.addMetadataAttribute(attribute);36                     }37                 }38 39                 bd.addQualifier(qualifier);40             } finally {41                 this.parseState.pop();42             }43         }44     }

 

     到此, 我们大致涵盖了 processBeanDefinition() 方法当中的第一步 delegate.parseBeanDefinitionElement() 方法的实现逻辑。 绝望, 这么长的代码总结起来就一句代码而已!! ค(TㅅT)

    在 processBeanDefinition() 方法中, 第二步实现 自定义元素的解析 decorateBeanDefinitionIfRequired() 的逻辑:

1     public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {2         return this.decorateBeanDefinitionIfRequired(ele, definitionHolder, (BeanDefinition)null);3     }

 

1     public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) { 2         BeanDefinitionHolder finalDefinition = definitionHolder; 3         NamedNodeMap attributes = ele.getAttributes(); 4         //第一步:  遍历所有的 子元素 5         for(int i = 0; i < attributes.getLength(); ++i) { 6             Node node = attributes.item(i); 7             finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd); 8         } 9 10         NodeList children = ele.getChildNodes();11         //第二步:  遍历所有的 子节点12         for(int i = 0; i < children.getLength(); ++i) {13             Node node = children.item(i);14             if(node.getNodeType() == 1) {15                 finalDefinition = this.decorateIfRequired(node, finalDefinition, containingBd);16             }17         }18 19         return finalDefinition;20     }
1     public BeanDefinitionHolder decorateIfRequired(Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) { 2         String namespaceUri = this.getNamespaceURI(node); 3         //第一步:  对非默认命名空间标签的处理 4         if(!this.isDefaultNamespace(namespaceUri)) { 5             //第二步:  根据命名空间找到对应的处理器调用方法 6             NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 7             if(handler != null) { 8                 //第三步:  进行修饰 9                 return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));10             }11 12             if(namespaceUri != null && namespaceUri.startsWith("http://www.springframework.org/")) {13                 this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);14             } else if(this.logger.isDebugEnabled()) {15                 this.logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");16             }17         }18 19         return originalDef;20     }

 

  1     public String getNamespaceURI(Node node) {  return node.getNamespaceURI(); }                                                                         

1     public boolean isDefaultNamespace(String namespaceUri) {2         return !StringUtils.hasLength(namespaceUri) || "http://www.springframework.org/schema/beans".equals(namespaceUri);3     }

 

     在 processBeanDefinition() 方法中, 第三步注册 BeanDefinition 的 registerBeanDefinition() 方法的实现逻辑

1     public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { 2          3         String beanName = definitionHolder.getBeanName(); 4         //第一步:  使用 beanName 做标志 5         registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); 6          7         String[] aliases = definitionHolder.getAliases(); 8         if(aliases != null) { 9             String[] var4 = aliases;10             int var5 = aliases.length;11 12             for(int var6 = 0; var6 < var5; ++var6) {13                 String aliase = var4[var6];14                 //第二步:  注册所有别名15                 registry.registerAlias(beanName, aliase);16             }17         }18 19     }

 

     在 registerBeanDefinition() 方法中, 第一步使用 beanName 作为 BeanDefinition 的标志注册, 实现的逻辑:

1     public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { 2          3         if(beanDefinition instanceof AbstractBeanDefinition) { 4             try { 5                 //第一步:  对 AbstractBeanDefinition 的 methodOverrides  的校验 6                 ((AbstractBeanDefinition)beanDefinition).validate(); 7             } catch (BeanDefinitionValidationException var7) { 8                 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", var7); 9             }10         }11 12         Map var3 = this.beanDefinitionMap;13         //第二步:  对全局变量进行同步14         synchronized(this.beanDefinitionMap) {15             //第三步:  缓存中获取 BeanDefinition16             BeanDefinition oldBeanDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);17             if(oldBeanDefinition != null) {18                 if(!this.allowBeanDefinitionOverriding) {19                     throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': There is already [" + oldBeanDefinition + "] bound.");20                 }21 22                 if(oldBeanDefinition.getRole() < beanDefinition.getRole()) {23                     if(this.logger.isWarnEnabled()) {24                         this.logger.warn("Overriding user-defined bean definition for bean '" + beanName + " with a framework-generated bean definition ': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");25                     }26                 } else if(this.logger.isInfoEnabled()) {27                     this.logger.info("Overriding bean definition for bean '" + beanName + "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");28                 }29             } else {30                 //第四步:  记录 beanName31                 this.beanDefinitionNames.add(beanName);32                 this.frozenBeanDefinitionNames = null;33             }34             //第五步:  注册 beanDefinition, 放入到 Map 集合中35             this.beanDefinitionMap.put(beanName, beanDefinition);36         }37         38         this.resetBeanDefinition(beanName);39     }

 

    本来想把 logger 还有 exception 信息都去掉, 但是看了一下, 里面的内容说明了许多问题, 对梳理逻辑很有帮助, 看这段代码的时候应该过一遍。

     在 registerBeanDefinition() 方法中, 第二步使用 别名注册 BeanDefinition 的 registerAlias() 方法的实现逻辑:

1     public void registerAlias(String name, String alias) { 2         //第一步:  beanName 和 alias 相同, 则删除对应的 alias 3         if(alias.equals(name)) { 4             this.aliasMap.remove(alias); 5         } else { 6             if(!this.allowAliasOverriding()) { 7                 String registeredName = (String)this.aliasMap.get(alias); 8                 if(registeredName != null && !registeredName.equals(name)) { 9                     throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");10                 }11             }12 13             this.checkForAliasCircle(name, alias);14             //第二步:  注册 Alias15             this.aliasMap.put(alias, name);16         }17 18     }

 

    在 processBeanDefinition() 方法中,  第四步通知完成注册的方法 fireComponentRegistered() 方法, 留给子类扩展。


 

 

  二 :  alias 标签

    是不是觉得有点奔溃, 到现在为止只是解决了一个 <bean> 标签的处理。  但是, 但是来了,接下来剩下的标签的处理相对于 <bean> 标签的处理要简单得多得多得多。

    在 parseDefaultElement() 方法中, 第二步 processAliasRegistration() 方法的实现逻辑:

1     protected void processAliasRegistration(Element ele) { 2         //第一步:  获取 beanName 和 alias 属性 3         String name = ele.getAttribute("name"); 4         String alias = ele.getAttribute("alias"); 5         boolean valid = true; 6         if(!StringUtils.hasText(name)) { 7             this.getReaderContext().error("Name must not be empty", ele); 8             valid = false; 9         }10 11         if(!StringUtils.hasText(alias)) {12             this.getReaderContext().error("Alias must not be empty", ele);13             valid = false;14         }15 16         if(valid) {17             try {18             //第二步:  注册 alias19                 this.getReaderContext().getRegistry().registerAlias(name, alias);20             } catch (Exception var6) {21                 this.getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, var6);22             }23             //第三步:  注册完成后的通知事件24             this.getReaderContext().fireAliasRegistered(name, alias, this.extractSource(ele));25         }26 27     }

 


 

 

  三  : import 标签

    在 parseDefaultElement() 方法中, 在第一步解析 import 标签的 importBeanDefinitionResource() 的方法的实现:

1     protected void importBeanDefinitionResource(Element ele) { 2         //第一步:  获取 resource 属性 3         String location = ele.getAttribute("resource"); 4         if(!StringUtils.hasText(location)) { 5             this.getReaderContext().error("Resource location must not be empty", ele); 6         } else { 7             //第二步:  处理 placeHolder 的情况 8             location = this.environment.resolveRequiredPlaceholders(location); 9             Set
actualResources = new LinkedHashSet(4);10 //第三步: 判断是相对路径还是绝对路径11 boolean absoluteLocation = false;12 13 try {14 absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();15 } catch (URISyntaxException var11) {16 ;17 }18 19 int importCount;20 if(absoluteLocation) {21 try {22 //第四步: 是绝对路径,直接加载 配置文件23 importCount = this.getReaderContext().getReader().loadBeanDefinitions(location, actualResources);24 } catch (BeanDefinitionStoreException var10) {25 this.getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, var10);26 }27 } else {28 try {29 //第五步: 相对路径计算出绝对路径, 加载配置文件30 Resource relativeResource = this.getReaderContext().getResource().createRelative(location);31 if(relativeResource.exists()) {32 importCount = this.getReaderContext().getReader().loadBeanDefinitions(relativeResource);33 actualResources.add(relativeResource);34 } else {35 String baseLocation = this.getReaderContext().getResource().getURL().toString();36 //第六步: 使用默认 ResourcePatternResolver 进行解析37 importCount = this.getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);38 }39 40 }41 42 Resource[] actResArray = (Resource[])actualResources.toArray(new Resource[actualResources.size()]);43 //第七步: 通知监听器44 this.getReaderContext().fireImportProcessed(location, actResArray, this.extractSource(ele));45 }46 }

 


 

 

  四 : beans 标签

    接触过 Spring 的都熟悉 <beans> 标签了, 在  中解析的 <bean> 标签, 递归调用 bean 标签的解析方法。 轻松略过.  哈哈哈哈哈 ( ^_^ )


 

 

解析自定义标签  parseCustomElement()

    解析自定义标签的方法 parseCustomElement() 方法的实现逻辑:

1     public BeanDefinition parseCustomElement(Element ele) {2         return this.parseCustomElement(ele, (BeanDefinition)null);3     }

 

1     public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { 2         //第一步:  获取对应的命名空间 3         String namespaceUri = this.getNamespaceURI(ele); 4         //第二步:  根据命名空间找到 NamespaceHandler  5         NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 6         if(handler == null) { 7             this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); 8             return null; 9         } else {10             //第三步:  调用自定义的 handler 的方法11             return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));12         }13     }

 

    在 parseCustomElement() 方法中, 第二步 获取 NamespaceHandler 方法中, readerContext 把 namespaceHandlerResolver 初始化为 DefaultNamespaceHandlerResolver :

1     public NamespaceHandler resolve(String namespaceUri) { 2         //第一步:  获取所有配置的 handler  3         Map
handlerMappings = this.getHandlerMappings(); 4 //第二步: 获取类名 5 Object handlerOrClassName = handlerMappings.get(namespaceUri); 6 if(handlerOrClassName == null) { 7 return null; 8 } else if(handlerOrClassName instanceof NamespaceHandler) { 9 //第三步: 已解析的情况, 从缓存中获取10 return (NamespaceHandler)handlerOrClassName;11 } else {12 //第四步: 未解析, 通过类路径, 反射创建类实例13 String className = (String)handlerOrClassName;14 15 try {16 Class
handlerClass = ClassUtils.forName(className, this.classLoader);17 if(!NamespaceHandler.class.isAssignableFrom(handlerClass)) {18 throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");19 } else {20 //第五步: 初始化类21 NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);22 //第六步: 调用 NamespaceHandler 的初始化方法23 namespaceHandler.init();24 //第七步: 记录在缓存当中25 handlerMappings.put(namespaceUri, namespaceHandler);26 return namespaceHandler;27 }28 }29 }30 }

 

    结合注释, 还是挺简单的一段代码来着的, 初始化一个 NamespaceHandler 类, 缓存也只是把类记录在一个 Map 对象中。

    在 resolve() 方法中, 第一步 getHandlerMappings() 方法获取所有配置的 handler 的实现逻辑:

1     private Map
getHandlerMappings() { 2 //第一步: 没有缓存则进行缓存 (缓存就是 handlerMappings 属性) 3 if(this.handlerMappings == null) { 4 synchronized(this) { 5 if(this.handlerMappings == null) { 6 try { 7 //第二步: 加载配置文件 8 Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); 9 if(this.logger.isDebugEnabled()) {10 this.logger.debug("Loaded NamespaceHandler mappings: " + mappings);11 }12 13 Map
handlerMappings = new ConcurrentHashMap(mappings.size());14 //第三步: 将配置文件 Properties 文件合并到 handlerMappings 中15 CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);16 this.handlerMappings = handlerMappings;17 }18 }19 }20 }21 22 return this.handlerMappings;23 }

 

    关于加载的配置文件, 在  DefaultNamespaceHandlerResolver 类的构造函数中, 把 this.handlerMappingsLocation 初始化为 META-INF/spring.handlers:

1     public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"; 2  3  4  5     public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) { 6         this.logger = LogFactory.getLog(this.getClass()); 7         Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null"); 8         this.classLoader = classLoader != null?classLoader:ClassUtils.getDefaultClassLoader(); 9         //第一步:  初始化10         this.handlerMappingsLocation = handlerMappingsLocation;11     }

 

    然后, 我们瞄一眼 spring.handlers 配置文件:

1   http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler2   http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler3   http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

 

    在  parseCustomElement() 方法中, 第三步调用 handler 的 parse() 方法的实现逻辑, 以 NamespaceHandlerSupport 类的实现为例:

1     public BeanDefinition parse(Element element, ParserContext parserContext) {2         return this.findParserForElement(element, parserContext).parse(element, parserContext);3     }

 

1     private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { 2         //第一步:  获取元素的名称 3         String localName = parserContext.getDelegate().getLocalName(element); 4         //第二步:  根据名称获取对应的解析器 5         BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName); 6         if(parser == null) { 7             parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element); 8         } 9 10         return parser;11     }

 

    对于 parse() 方法, 在  AbstractBeanDefinitionParser 中找到了其实现方法:

1     public final BeanDefinition parse(Element element, ParserContext parserContext) { 2         //第一步:  调用自定义的解析函数 3         AbstractBeanDefinition definition = this.parseInternal(element, parserContext); 4         if(definition != null && !parserContext.isNested()) { 5             try { 6                 //第二步:  获取 id 属性 7                 String id = this.resolveId(element, definition, parserContext); 8                 if(!StringUtils.hasText(id)) { 9                     parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element);10                 }11 12                 String[] aliases = new String[0];13                 //第三步:  获取 name 属性14                 String name = element.getAttribute("name");15                 if(StringUtils.hasLength(name)) {16                     aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));17                 } 18                 //第四步: AbstractBeanDefinition 转换为 BeanDefinitionHolder 对象19 20                 BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);21                 this.registerBeanDefinition(holder, parserContext.getRegistry());22                 if(this.shouldFireEvents()) {23                     //第五步:  通知监听器24                     BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);25                     this.postProcessComponentDefinition(componentDefinition);26                     parserContext.registerComponent(componentDefinition);27                 }28             } catch (BeanDefinitionStoreException var9) {29                 parserContext.getReaderContext().error(var9.getMessage(), element);30                 return null;31             }32         }33 34         return definition;35     }

 

    看起来很多, 其实只是 AbstractBeanDefinition 转换为 BeanDefinitionHolder 对象, 在 第一步的调用自定义解析函数的 parseInternal() 的实现逻辑, 在 AbstractSingleBeanDefinitionParser 类中找到了还方法的实现 ( AbstractBeanDefinitionParser 中定义该方法, 子类实现):

1    protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { 2         BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); 3         String parentName = this.getParentName(element); 4         if(parentName != null) { 5             builder.getRawBeanDefinition().setParentName(parentName); 6         } 7         //第一步:  获取 class 属性 8         Class
beanClass = this.getBeanClass(element); 9 if(beanClass != null) {10 builder.getRawBeanDefinition().setBeanClass(beanClass);11 } else {12 String beanClassName = this.getBeanClassName(element);13 if(beanClassName != null) {14 builder.getRawBeanDefinition().setBeanClassName(beanClassName);15 }16 }17 18 builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));19 if(parserContext.isNested()) {20 //第二步: 设置 scope 属性21 builder.setScope(parserContext.getContainingBeanDefinition().getScope());22 }23 //第三步: 设置 lazy-init 属性24 if(parserContext.isDefaultLazyInit()) {25 builder.setLazyInit(true);26 }27 //第四步: 调用自定义的解析函数28 this.doParse(element, parserContext, builder);29 return builder.getBeanDefinition();30 }

 

    在 parseInternal() 方法中, 第四步 doParse() 方法才真正调用了我们自己写的解析方法。


 

    到此,我们已经了解了 Spring 默认标签 以及 自定义标签的处理, 在接下来的内容当中, 将进入 Spring 在加载完 XML 配置文件后, 对 Bean 的初始化的工作。

    ㄟ( ▔, ▔ )ㄏ   来个 Spring 的套路, FIREDAYWORKCOMPLETEDEVENT(new GOODNIGHT("(~﹃~)~zZ"));

 

转载于:https://www.cnblogs.com/forwrader/p/6680676.html

你可能感兴趣的文章
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_3-4.动态Sql语句Mybaties SqlProvider...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_4-1.单机和分布式应用的登录检验讲解...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_4-3.登录检验JWT实战之封装通用方法...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_3-2.使用Mybatis注解开发视频列表增删改查...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-2.微信扫一扫功能开发前期准备...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_3-3.Vidoe相关接口完善和规范协议...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-3.微信Oauth2.0交互流程讲解...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_3-5.PageHelper分页插件使用
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-6.微信扫码登录回调本地域名映射工具Ngrock...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_4-2.微服务下登录检验解决方案 JWT讲解...
查看>>
小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_5-8.用户模块开发之保存微信用户信息...
查看>>
HDU 5353 Average
查看>>
进程和计划管理
查看>>
MQ_ActiveMQ环境部署+C#推送和接收消息
查看>>
Ubuntu16.04上使用Anaconda3的Python3.6的pip安装UWSGI报错解决办法
查看>>
学习笔记11.6
查看>>
高效中的细节注意
查看>>
MySQL 之 库操作
查看>>
Python 最抢手、Java 最流行,前线程序员揭秘 2019 软件开发现状
查看>>
R语言(一)
查看>>