2019独角兽企业重金招聘Python工程师标准>>>

Spring读取xml配置文件的原理与实现

本篇博文的目录:

一:前言

二:spring的配置文件

三:依赖的第三方库、使用技术、代码布局

四:Document实现

五:获取Element的实现

六:解析Element元素

七:Bean创造器

八:Ioc容器的创建

九:总结

一:前言:

Spring作为Bean的管理容器,在我们的项目构建中发挥了举足轻重的作用,尤其是控制反转(IOC)和依赖(DI)注入的特性,将对象的创建完全交给它来实现,当我们把与其他框架进行整合时,比如与Mybatis整合,可以把sqlMapClientTemplate、数据源等Bean交给它来管理,这样在我们程序需要的时候,只需要调用它的getBean(String id)方法就可以获取它的一个实例。这一点我们都知道它是利用反射的原理,取得class然后获取constructor-arg配置的参数,然后调用newInstance()方法进行实例化的,那么我们在spring配置文件中配置的Bean的属性,比如Lazy-int、AutoWire属性Spring是如何解析的呢?这背后又是怎样的原理呢。这就是本篇博文将要探讨的问题,我们将深入到代码层面,看一看Spring解析Xml的具体原理

二:Spring的配置文件

我们先来看一个简单的Spring配置文件,由配置文件我们看到了Spring配置文件的一系列属性,配置Bean的id,class属性,还有Lazy-int、AutoWire、SingleTon等属性,配置了这些东西,那么Spring就会按照我们配置的东西进行解析,从而得到我们需要的值。

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util" xmlns:jdbc="http://www.springframework.org/schema/jdbc"xmlns:cache="http://www.springframework.org/schema/cache"xsi:schemaLocation="  http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd  http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx.xsd  http://www.springframework.org/schema/jdbc  http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd  http://www.springframework.org/schema/cache  http://www.springframework.org/schema/cache/spring-cache-3.1.xsd  http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop.xsd  http://www.springframework.org/schema/util  http://www.springframework.org/schema/util/spring-util.xsd"><bean id="pad1" class="com.wyq.Bean.Pad" scope="singleton"><constructor-arg><value type="java.lang.double">1999.9</value></constructor-arg></bean><!-- 懒加载 --><bean id="pad2" class="com.wyq.Bean.Pad" lazy-init="true"autowire="no"></bean><bean id="person" class="com.wyq.Bean.Person" autowire="byName"><property name="name" value="Yrion"></property></bean></beans>

三:依赖的第三方库、使用技术、代码布局

spring解析xml配置的第三方库需要的是dom4j,使用的技术是java,代码布局会按照Document、Element、BeanCreator的方式进行,首先定义相关的接口,然后定义子类去实例化,我们来展示一下Spring解析配置文件接口的思维导图,从中可以看出我们定义了一系列的读取属性的接口,比如AutoWire属性,因为有两种情况ByName和no、byType三种情况(这里只为了说明问题,我们只定义两个实现类,正式的话是三个实现类),这里采用的是状态设计模式,设计一个总接口,然后对不同的情况,我们定义相关的实现类,是那种情况,就返回具体的类。如图展示的是接口和具体的实现类,接下来我们将会按照这样的方式去讲解每一个接口对应的实现类。

四:获取Document实现

按照从大到小的思维,我们先来实现DocumenHoler接口,可以看出这个接口我们只定义了一个方法,根据路径返回具体的Document。然后我们来写具体的实现子类,有了这样的类,我们只需要传入一个路径,那么就会返回一个模拟的Document对象

import org.dom4j.Document;public interface DocumentHolder {Document getDocument(String filePath);}

import java.io.File;
import java.util.HashMap;
import java.util.Map;import org.dom4j.Document;
import org.dom4j.io.SAXReader;public class XMLDocumentHolder implements DocumentHolder{//建立一个HashMap用来存放字符串和文档private Map<String, Document> docs = new HashMap<String, Document>();@Overridepublic Document getDocument(String filePath) {Document doc=this.docs.get(filePath);//用HashMap先根据路径获取文档if (doc==null) {this.docs.put(filePath, readDocument(filePath)); //如果为空,把路径和文档放进去}return  this.docs.get(filePath);}/*** 根据路径读Document* @param filePath* @return*/private Document readDocument(String filePath) {Document doc =null;try {SAXReader reader = new SAXReader(true);//借用dom4j的解析器reader.setEntityResolver(new IocEntityResolver());File xmlFile = new File(filePath); //根据路径创建文件doc = reader.read(xmlFile);//用dom4j自带的reader读取去读返回一个Document} catch (Exception e) {e.printStackTrace();}return doc;}}

五:获取Element的接口实现

有了Document,按照从大到小的逻辑,我们就需要解析Element元素了,也就是具体的元素加载器,首先我们先来定义一个接口,然后再定义具体的类去实现这个接口。中间主要定义了三个方法添加元素和获取元素,和获取所有的元素,在子类中定义了一个HashMap用于键用来存储属性名,值用来存储具体的Element元素。

import java.util.Collection;
import org.dom4j.Document;
import org.dom4j.Element;/*** 加载Element元素* @author Yiron**/
public interface ElementLoader {void addElements(Document doc);//添加元素Element getElement(String id);//获取元素Collection<Element> getElements();//获取所有的元素}

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.Element;public class ElementLoaderImpl implements ElementLoader{private Map<String, Element> elements=new HashMap<String, Element>();public void addElements(Document doc) {@SuppressWarnings("unchecked")List<Element> eles = doc.getRootElement().elements();for (Element e : eles) {String id=e.attributeValue("id");elements.put(id, e);}}public Element getElement(String id) {return elements.get(id);}public Collection<Element> getElements() {return this.elements.values();}}

六:解析Element元素

在第五步中,我们主要是获取了Element元素,获取到了之后,我们就要对其进行解析了。我们首先来定义一个解析Element的接口,接口里面的方法主要是对xml文件配置的元素作出解析,比如boolean isLazy(Element element)就是对其是否进行懒加载进行判断,然后实现该接口:

import java.util.List;
import org.dom4j.Element;/*** 解析Element元素* @author Yiron**/
public interface ElementReader {boolean isLazy(Element element);List<Element> getConstructorElements(Element element);String getAttribute(Element element,String name);boolean isSingleTon(Element element);List<Element> getPropertyElements(Element element);AutoWire getAutoWire(Element element);List<DataElement> getConstructorValue(Element element);List<PropertyElement> getPropertyValue(Element element);}

package com.wyq.ResolveElement;import java.util.ArrayList;
import java.util.List;import org.dom4j.Element;
import org.omg.PortableServer.ID_ASSIGNMENT_POLICY_ID;public class ElementReaderImpl implements ElementReader{/*** 判断是否延迟加载*/@Overridepublic boolean isLazy(Element element) {String lazy = getAttribute(element, "lazy-int");//得到是否懒加载这个元素Element parent = element.getParent();Boolean parentLazy = new Boolean(getAttribute(parent, "default-lazy-int"));if (parentLazy) {if ("false".equals(lazy)) return false;return true;}else {if ("true".equals(lazy))  return true;return false;}}/*** 获取constructor-arg节点*/@Overridepublic List<Element> getConstructorElements(Element element) {List<Element> childrens = element.elements();//得到bean节点下的所有节点List<Element> reslut=new ArrayList<Element>();//存放节点的链表for (Element e : childrens) {//遍历if ("constructor-arg".equals(e.getName())) {//如果是constructor-arg节点reslut.add(e);//放入到预设的链表中}}return reslut; //返回这个链表}/*** 根据元素的name获取元素的值*/public String getAttribute(Element element, String name) {String value = element.attributeValue(name);return value;}/*** 判断是不是单例模式*/public boolean isSingleTon(Element element) {Boolean singleTon = new Boolean(getAttribute(element, "singleTon"));return singleTon;}/*** 获得自动注入*/@Overridepublic AutoWire getAutoWire(Element element) {String value = this.getAttribute(element, "autoWire");String parentValue=this.getAttribute(element.getParent(),"default-autowire");if ("no".equals(parentValue)) {if ("byName".equals(parentValue)) {return new ByNameAutoWire(value);}else {return new NoAutoWire(value);}}else if ("byName".equals(parentValue)) {if("no".equals(value)) return new NoAutoWire(value);return new ByNameAutoWire(value);}return new NoAutoWire(value);}@Overridepublic List<DataElement> getConstructorValue(Element element) {List<Element> cons=getConstructorElements(element);List<DataElement> result = new ArrayList<DataElement>();for (Element e : cons) {List<Element> els = e.elements();DataElement dataElement = getDataElement(els.get(0));result.add(dataElement);}return result;}@Overridepublic List<PropertyElement> getPropertyValue(Element element) {List<Element> properties=getPropertyElements(element);List<PropertyElement> reslut=new ArrayList<PropertyElement>();for (Element e : properties) {List<Element> els=e.elements();DataElement dataElement = getDataElement(els.get(0));String value = getAttribute(e, "name");PropertyElement pe = new PropertyElement(value, dataElement);reslut.add(pe);}return reslut;}private DataElement getDataElement(Element element){String name=element.getName();if ("value".equals(name)) {String classTypeName=element.attributeValue("type");String data = element.getText();return new ValueElement(getValue(classTypeName,data));}else if ("ref".equals(name)) {return new RefElement(this.getAttribute(element, "bean"));}return null;}private Object getValue(String className,String data){if (isType(className,"Integer")) {return Integer.parseInt(data);}else {return data;}}private boolean isType(String className, String type) {if (className.indexOf(type)!=-1) {return true;}else {return false;}}@Overridepublic List<Element> getPropertyElements(Element element) {List<Element> elements = element.elements();return elements;}}

6.2:解析Element的时候,我们定义了几个接口,我们来看看自动注入的源代码,自动注入返回三种情况,我们来模拟实现其中两种ByNameAutoWire与NoAutoWire。还有DataElement,其子类分别是:RefElement和ValueElement分别表示引用元素和值元素。

public interface AutoWire {  //自动注入String getValue();}

public class ByNameAutoWire implements AutoWire{private String value;public ByNameAutoWire(String value) {this.value = value;}@Overridepublic String getValue() {return value;}}

public class NoAutoWire implements AutoWire{String value;public NoAutoWire(String value) {this.value = value;}public String getValue() {return value;}}

public interface DataElement {String getType();Object getValue();}

public class RefElement implements DataElement{private Object value;public  RefElement(Object value) {this.value=value;}@Overridepublic String getType() {return "ref";}@Overridepublic Object getValue() {return this.value;}
}

public class ValueElement implements DataElement {private Object value;public ValueElement(Object value) {this.value = value;}@Overridepublic String getType() {return "value";}@Overridepublic Object getValue() {return this.value;}}

七:Bean创造器

主要创造Bean不使用默认构造器和使用定义的construction-arg参数,当我们配置构造参数的时候,就会拿到这些信息,然后进行反射来调用构建对象,其中还包括获取Setter方法。其代码如下:

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;public interface BeanCreator  { Object createBeanUseDefaultConstruct(String className);//使用空构造器Object createBeanUseDefineConstruce(String className,List<Object> args);//使用定义的构造器Map<String, Method> getSetterMethodsMap(Object obj);void executeMethod(Object object,Object argBean,Method method);}

package com.wyq.Bean;import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class BeanCreatorImpl implements BeanCreator{@Overridepublic Object createBeanUseDefaultConstruct(String className) {Object object=null;try {Class clazz = Class.forName(className);object= clazz.newInstance();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return object;}/*** className:类的名字* args:配置的构造参数*/@Overridepublic Object createBeanUseDefineConstruct(String className, List<Object> args) {Class[] argsClass=getArgsClasses(args);try {Class clazz = Class.forName(className);Constructor constructor=findConstructor(clazz,argsClass);} catch (Exception e) {// TODO: handle exception}return null;}/*** 根据类型和参数查找构造器* @param clazz* @param argsClass* @return*/private Constructor findConstructor(Class clazz, Class[] argsClass) throws NoSuchMethodException{Constructor constructor=  getConstructor(clazz,argsClass);if (constructor==null) {Constructor[] constructors = clazz.getConstructors();for (Constructor c : constructors) {Class[] constructorArgsClass = c.getParameterTypes();if (constructorArgsClass.length==argsClass.length) {if (isSameArgs(argsClass,constructorArgsClass)) {return c;}}}}return null;}private boolean isSameArgs(Class[] argsClass, Class[] constructorArgsClass) {for (int i = 0; i < argsClass.length; i++) {try{argsClass[i].asSubclass(constructorArgsClass[i]);if (i==(argsClass.length-1)) {return true;}}catch (Exception e) {e.printStackTrace();break;}}return false;}private Constructor getConstructor(Class clazz, Class[] argsClass) throws SecurityException, NoSuchMethodException {try {Constructor constructor = clazz.getConstructor(argsClass);return constructor;} catch (Exception e) {return null;}}private Class[] getArgsClasses(List<Object> args) {//装有class类的list集合List<Class> reslut =new ArrayList<Class>();for (Object arg : args) {reslut.add(getClass(arg));}Class[] a = new Class[reslut.size()];return reslut.toArray(a);}private Class getClass(Object obj) {if (obj instanceof Integer) {return Integer.TYPE;}else if (obj instanceof Double) {return Double.TYPE;}else if (obj instanceof Long) {return Long.TYPE;}else if (obj instanceof Float) {return Float.TYPE;}else if (obj instanceof Character) {return Character.TYPE;}else if (obj instanceof Byte) {return Byte.TYPE;}return obj.getClass();}@Overridepublic void executeMethod(Object object, Object argBean, Method method) {try {Class[] paramterTypes = method.getParameterTypes();if (paramterTypes.length==1) {if (isMethodArgs(method,paramterTypes[0])) {method.invoke(object, argBean);}}} catch (Exception e) {try {throw new BeanCreateException("autoWire exception"+e.getMessage());} catch (BeanCreateException e1) {e1.printStackTrace();}}}private boolean isMethodArgs(Method method, Class class1) {Class[] c = method.getParameterTypes();if (c.length==1) {try {class1.asSubclass(c[0]);return true;} catch (Exception e) {e.printStackTrace();return false;}}return false;}@Overridepublic Map<String, Method> getSetterMethodsMap(Object obj) {List<Method>    methods=getSetterMethodsList(obj);Map<String, Method> result=new HashMap<String ,Method>();for (Method method : methods) {String propertyName=getMethodNameWithOutSet(method.getName());}return null;}/*** 还原setter方法* @param methodname* @return*/private String getMethodNameWithOutSet(String methodname) {String propertyName=methodname.replace("set", "");String firstWord=propertyName.substring(0,1);String lowerFirstWord = firstWord.toLowerCase();return propertyName.replaceFirst(firstWord, lowerFirstWord);}private List<Method> getSetterMethodsList(Object obj) {Class clazz = obj.getClass();List<Method> result=new ArrayList<Method>();Method[] methods = clazz.getMethods();for (Method method : methods) {if (method.getName().startsWith("Set")) {result.add(method);}}return result;}
}

八:定义自己的IoC容器:

首先我们来定义自己的ApplicationContext接口,其中有getBean(String id)方法,通过这个方法就可以获取具体的对象实例,也是我们使用Spring框架中用的最多的一个方法。然后来定义具体的实现子类

public interface ApplicationContext {Object getBean(String id);boolean containsBean(String id);boolean isSingleton(String id);

package com.wyq.Ioc;import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;import org.dom4j.Document;
import org.dom4j.Element;import com.wyq.Bean.BeanCreateException;
import com.wyq.Bean.BeanCreator;
import com.wyq.Bean.BeanCreatorImpl;
import com.wyq.Element.ElementLoader;
import com.wyq.Element.ElementLoaderImpl;
import com.wyq.ReadXml.DocumentHolder;
import com.wyq.ReadXml.XMLDocumentHolder;
import com.wyq.ResolveElement.AutoWire;
import com.wyq.ResolveElement.ByNameAutoWire;
import com.wyq.ResolveElement.DataElement;
import com.wyq.ResolveElement.ElementReader;
import com.wyq.ResolveElement.ElementReaderImpl;
import com.wyq.ResolveElement.NoAutoWire;
import com.wyq.ResolveElement.PropertyElement;
import com.wyq.ResolveElement.RefElement;
import com.wyq.ResolveElement.ValueElement;
import com.wyq.SetInput.PropertyHandler;
import com.wyq.SetInput.PropertyHandlerImpl;public class AbstractApplicationContext implements ApplicationContext {protected ElementLoader elementLoader = new ElementLoaderImpl();protected DocumentHolder documentHolder = new XMLDocumentHolder();protected Map<String, Object> beans = new HashMap<String, Object>();protected BeanCreator beanCreator = new BeanCreatorImpl();protected ElementReader elementReader = new ElementReaderImpl();protected PropertyHandler propertyHandler = new PropertyHandlerImpl();protected void setUpElements(String[] xmlPaths){URL classPathUrl = AbstractApplicationContext.class.getClassLoader().getResource(".");String classpath;try {classpath = java.net.URLDecoder.decode(classPathUrl.getPath(), "utf-8");for (String path : xmlPaths) {Document doc = documentHolder.getDocument(classpath + path);elementLoader.addElements(doc);}} catch (UnsupportedEncodingException e) {e.printStackTrace();}}@Overridepublic Object getBean(String id) {Object bean = this.beans.get(id);if (bean == null) {bean = handleSingleton(id);}return bean;}private Object handleSingleton(String id) {Object bean = createBean(id);if (isSingleton(id)) {this.beans.put(id, bean);}return bean;}private Object createBean(String id) {Element e = elementLoader.getElement(id);if (e == null) {try {throw new BeanCreateException("element not found" + id);} catch (BeanCreateException e1) {e1.printStackTrace();}}Object result = instance(e);System.out.println("创建bean" + id);System.out.println("该bean的对象是" + result);AutoWire autoWire = elementReader.getAutoWire(e);if (autoWire instanceof ByNameAutoWire) {// 使用名称自动装配autowireByName(result);} else if (autoWire instanceof NoAutoWire) {setterInject(result, e);}return null;}protected void createBeans(){Collection<Element> elements = elementLoader.getElements();for (Element element : elements) {boolean lazy = elementReader.isLazy(element);if (!lazy) {String id = element.attributeValue("id");Object bean = this.getBean(id);if (bean==null) {handleSingleton(id);}}}}private void setterInject(Object obj, Element e) {List<PropertyElement> properties = elementReader.getPropertyValue(e);Map<String, Object> propertiesMap = getPropertyArgs(properties);propertyHandler.setProperties(obj, propertiesMap);}private Map<String, Object> getPropertyArgs(List<PropertyElement> properties) {Map<String, Object> result = new HashMap<String, Object>();for (PropertyElement p : properties) {DataElement de = p.getDataElement();if (de instanceof RefElement) {result.put(p.getName(), this.getBean((String) de.getValue()));} else if (de instanceof ValueElement) {result.put(p.getName(), de.getValue());}}return result;}private void autowireByName(Object obj) {Map<String, Method> methods = propertyHandler.getSetterMethodsMap(obj);for (String s : methods.keySet()) {Element e = elementLoader.getElement(s);if (e == null)continue;Object bean = this.getBean(s);Method method = methods.get(s);propertyHandler.executeMethod(obj, bean, method);}}private Object instance(Element e) {String className = elementReader.getAttribute(e, "class");List<Element> constructorElements = elementReader.getConstructorElements(e);if (constructorElements.size() == 0) {return beanCreator.createBeanUseDefaultConstruct(className);} else {List<Object> args = getConstructArgs(e);return beanCreator.createBeanUseDefineConstruct(className, args);}}private List<Object> getConstructArgs(Element e) {List<DataElement> datas = elementReader.getConstructorValue(e);List<Object> result = new ArrayList<Object>();for (DataElement d : datas) {if (d instanceof ValueElement) {d = (ValueElement) d;result.add(d.getValue());} else if (d instanceof RefElement) {d = (RefElement) d;String refid = (String) d.getValue();result.add(this.getBean(refid));}}return result;}@Overridepublic boolean containsBean(String id) {Element element = elementLoader.getElement(id);return element == null ? false : true;}@Overridepublic boolean isSingleton(String id) {Element e = elementLoader.getElement(id);return elementReader.isSingleTon(e);}}

}

九:总结

我们经过上面的代码就完整实现了一个Ioc容器,其中从Document的创造,再到Element的创建,再到解析Element,然后设置我们的Bean创造器,再实现AppliacionContext,这一过程在编码中是不可逆的,因为只有有了Document,才有Element,再然后才有解析,我们的方法进行下一步的解析都需要上层作为参数,只有这样才能完整解析Spring配置文件。我详细阐述了这一流程,其中参阅了不少资料,耗时一星期,希望大家细细体会其中的方法,尤其是方法之间的串联与解析的思路。从中过我们看出Spring解析的原理,我们进一步深入理解Spring框架起到了很大的的作用。同时本篇博文只是起到一个抛砖引玉的作用,示范了一些特性。按照这样的逻辑,我们可以举一反三,像mybaits、Hibernate的配置文件,我们大概就可以知道是如何解析的了。比如mybatis的配置文件,里面的sql配置,如何去拼接sql,无非就是解析文档,把标签里面的内容拿出来,拼接成String,交给jdbc去运行,这就是编程中的举一反三,所以这篇博文不仅仅局限于Spring,有更多的东西值得我们去思考,加深自己的理解。好了,本次讲解就到这里,我们下篇再见,谢谢。

转载于:https://my.oschina.net/demons99/blog/2050502

Spring读取xml配置文件的原理与实现相关推荐

  1. Flex读取XML配置文件

    在Flex中我们经常使用xml文件,因为Flex支持强大的E4X功能,读取xml相当简洁.总结一下常用的Flex读取XML配置文件的方法: 1.使用Model标签形式 首先声明Model标签, < ...

  2. SSM框架笔记07:初探Spring——采用XML配置文件与注解方式

    初探Spring--采用XML配置文件与注解方式   在上一讲的项目基础上继续.   1.将xmlconfig包里的两个骑士类和两个任务类拷贝到xml_annotation包 2.修改SlayDrag ...

  3. Spring框架XML配置文件使用外部Bean属性注入

    Spring框架XML配置文件使用外部Bean属性注入 (1)创建两个类service类和dao类 (2)在service中调用dao里面的方法 (3)使用Spring框架进行调用 (4)创建测试类 ...

  4. [error] eclipse编写spring等xml配置文件时只有部分提示,tx无提示

    eclipse编写spring等xml配置文件时只有<bean>.<context>等有提示,其他标签都没有提示 这时就需要做以下两步操作(下面以事务管理标签为例) 1,添加命 ...

  5. Spring的xml配置文件中tx命名空间

    Spring的xml配置文件中tx命名空间 一,spring配置文件的tx命名空间 引入tx命名空间 <?xml version="1.0" encoding="U ...

  6. java 读取 xml 配置文件内容

    java  读取 xml 配置文件内容 public static void main(String[] args) throws Exception {//创建读取的核心类SAXReader sax ...

  7. spring读取properties配置文件_Spring-1

    spring共四天 第一天:spring框架的概述以及spring中基于XML的IOC配置 第二天:spring中基于注解的IOC和ioc的案例 第三天:spring中的aop和基于XML以及注解的A ...

  8. Spring 在 xml配置文件 或 annotation 注解中 运用Spring EL表达式

    Spring  EL 一:在Spring xml 配置文件中运用   Spring EL Spring EL 采用 #{Sp Expression  Language} 即 #{spring表达式} ...

  9. Winform中实现读取xml配置文件并动态配置DevExpress的RadioGroup的选项

    场景 Winform中对DevExpress的RadioGroup进行数据源绑定,即通过代码添加选项: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/articl ...

最新文章

  1. C# 对应 Oracle 存储过程 的 SYS_REFCURSOR 应该 传入什么类型的参数?
  2. 机器学习数学原理 霍夫丁不等式
  3. shell   脚本之 continue 与break的用法
  4. Linux内核中影响tcp三次握手的一些协议配置
  5. Python mysql 索引原理与慢查询优化
  6. 安卓抓包工具 linux,Android 下使用tcpdump网络抓包方法
  7. 字符串之数组中两个字符串的最小距离
  8. ueditor如何设置上传图片的高度宽度_怎么设置天猫主图
  9. PHP苹果不给上架,苹果商城上架拒绝
  10. SVM多分类问题例子+matlab代码
  11. Delphi各个版本的官方下载地址,还在等机会
  12. 查看当前python环境_python-环境
  13. 服务器硬件配置及RAID配置操作
  14. 这10个免费学习网站,个个堪称神器,不收后悔!
  15. 记一次git pull 错误
  16. android xposed miui9,vxposed在小米-安卓9上闪退
  17. cad能整体比例缩小吗_cad怎么把原尺寸图缩小几倍
  18. Mysql数据库数据拆分之分库分表总结
  19. [案例3-2]银行存取款程序设计
  20. Python爬虫获取基金持仓股票数据

热门文章

  1. vscode不能跳转_vscode-goto-node-modules 一个快速定位 node 模块的 vscode 插件
  2. 2021上半年测试工作总结:再一次的跳出、新的转变
  3. CSS基础——CSS复合选择器【学习笔记】
  4. git 21天打卡day10-创建昵称分支并切换
  5. 程序老鸟:软件测试的工资高还是开发者工资高?
  6. 自动化测试工具Selenium
  7. python学习基础语法_python学习-基础语法
  8. linux fork脚本,shell调度脚本的三种不同方法(fork, exec, source)
  9. drop out, learning rate in nn
  10. 卷积神经网络处理猫和狗图片(改进网络)