Spring自定义标签解析与实现
在Spring Bean注册解析(一)和Spring Bean注册解析(二)中我们讲到,Spring在解析xml文件中的标签的时候会区分当前的标签是四种基本标签(import、alias、bean和beans)还是自定义标签,如果是自定义标签,则会按照自定义标签的逻辑解析当前的标签。另外,即使是bean标签,其也可以使用自定义的属性或者使用自定义的子标签。本文将对自定义标签和自定义属性的使用方式进行讲解,并且会从源码的角度对自定义标签和自定义属性的实现方式进行讲解。
1. 自定义标签
1.1 使用方式
对于自定义标签,其主要包含两个部分:命名空间和转换逻辑的定义,而对于自定义标签的使用,我们只需要按照自定义的命名空间规则,在Spring的xml文件中定义相关的bean即可。假设我们有一个类Apple,并且我们需要在xml文件使用自定义标签声明该Apple对象,如下是Apple的定义:
public class Apple {
private int price;
private String origin;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}
如下是我们使用自定义标签在Spring的xml文件中为其声明对象的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myapple="http://www.lexueba.com/schema/apple"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.lexueba.com/schema/apple http://www.lexueba.com/schema/apple.xsd">
<myapple:apple id="apple" price="123" origin="Asia"/>
</beans>
我们这里使用了myapple:apple标签声明名为apple的bean,这里myapple就对应了上面的xmlns:myapple,其后指定了一个链接:http://www.lexueba.com/schema/apple,Spring在解析到该链接的时候,会到META-INF文件夹下找Spring.handlers和Spring.schemas文件(这里META-INF文件夹放在maven工程的resources目录下即可),然后读取这两个文件的内容,如下是其定义:
Spring.handlers
http\://www.lexueba.com/schema/apple=chapter4.eg3.MyNameSpaceHandler
Spring.schemas
http\://www.lexueba.com/schema/apple.xsd=META-INF/custom-apple.xsd
可以看到,Spring.handlers指定了当前命名空间的处理逻辑类,而Spring.schemas则指定了一个xsd文件,该文件中则声明了myapple:apple各个属性的定义。我们首先看下自定义标签各属性的定义:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.lexueba.com/schema/apple"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lexueba.com/schema/apple"
elementFormDefault="qualified">
<xsd:complexType name="apple">
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ The unique identifier for a bean. ]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="price" type="xsd:int">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ The price for a bean. ]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="origin" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ The origin of the bean. ]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="apple" type="apple">
<xsd:annotation>
<xsd:documentation><![CDATA[ The service config ]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema>
可以看到,该xsd文件中声明了三个属性:id、price和origin。需要注意的是,这三个属性与我们的Apple对象的属性price和origin没有直接的关系,这里只是一个xsd文件的声明,以表征Spring的applicationContext.xml文件中使用当前命名空间时可以使用的标签属性。接下来我们看一下Spring.handlers中定义的MyNameSpaceHandler声明:
public class MyNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("apple", new AppleBeanDefinitionParser());
}
}
MyNameSpaceHandler只是注册了apple的标签的处理逻辑,真正的转换逻辑在AppleBeanDefinitionParser中。这里注册的apple必须与Spring的applicationContext.xml文件中myapple:apple标签后的apple保持一致,否则将找不到相应的处理逻辑。如下是AppleBeanDefinitionParser的处理逻辑:
public class AppleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return Apple.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String price = element.getAttribute("price");
String origin = element.getAttribute("origin");
if (StringUtils.hasText(price)) {
builder.addPropertyValue("price", Integer.parseInt(price));
}
if (StringUtils.hasText(origin)) {
builder.addPropertyValue("origin", origin);
}
}
}
可以看到,该处理逻辑中主要是获取当前标签中定义的price和origin属性的值,然后将其按照一定的处理逻辑注册到当前的BeanDefinition中。这里还实现了一个getBeanClass()方法,该方法用于表明当前自定义标签对应的BeanDefinition所代表的类的类型。如下是我们的入口程序,用于检查当前的自定义标签是否正常工作的:
public class CustomSchemaApp {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Apple apple = applicationContext.getBean(Apple.class);
System.out.println(apple.getPrice() + ", " + apple.getOrigin());
}
}
运行结果如下:
123, Asia
1.2 实现方式
我们还是从对整个applicationContext.xml文件开始读取的入口方法开始进行讲解,即DefaultBeanDefinitionDocumentReader.parseBeanDefinitions()方法,如下是该方法的源码:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判断根节点使用的标签所对应的命名空间是否为Spring提供的默认命名空间,
// 这里根节点为beans节点,该节点的命名空间通过其xmlns属性进行了定义
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 当前标签使用的是默认的命名空间,如bean标签,
// 则按照默认命名空间的逻辑对其进行处理
parseDefaultElement(ele, delegate);
} else {
// 判断当前标签使用的命名空间是自定义的命名空间,如这里myapple:apple所
// 使用的就是自定义的命名空间,那么就按照定义命名空间逻辑进行处理
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 如果根节点使用的命名空间不是默认的命名空间,则按照自定义的命名空间进行处理
delegate.parseCustomElement(root);
}
}
可以看到,该方法首先会判断当前文件指定的xmlns命名空间是否为默认命名空间,如果是,则按照默认命名空间进行处理,如果不是则直接按照自定义命名空间进行处理。这里需要注意的是,即使在默认的命名空间中,当前标签也可以使用自定义的命名空间,我们定义的myapple:apple就是这种类型,这里myapple就关联了xmlns:myapple后的myapple。如下是自定义命名空间的处理逻辑:
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取当前标签对应的命名空间指定的url
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 获取当前url所对应的NameSpaceHandler处理逻辑,也即我们定义的MyNameSpaceHandler
NamespaceHandler handler = this.readerContext
.getNamespaceHandlerResolver()
.resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" +
namespaceUri + "]", ele);
return null;
}
// 调用当前命名空间处理逻辑的parse()方法,以对当前标签进行转换
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
这里getNamespaceURI()方法的作用是获取当前标签对应的命名空间url。在获取url之后,会调用NamespaceHandlerResolver.resolve(String)方法,该方法会通过当前命名空间的url获取META-INF/Spring.handlers文件内容,并且查找当前命名空间url对应的处理逻辑类。如下是该方法的声明:
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
// 获取handlerMapping对象,其键为当前的命名空间url,
// 值为当前命名空间的处理逻辑类对象,或者为处理逻辑类的包含全路径的类名
Map<String, Object> handlerMappings = getHandlerMappings();
// 查看是否存在当前url的处理类逻辑,没有则返回null
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
} else if (handlerOrClassName instanceof NamespaceHandler) {
// 如果存在当前url对应的处理类对象,则直接返回该处理对象
return (NamespaceHandler) handlerOrClassName;
} else {
// 如果当前url对应的处理逻辑还是一个没初始化的全路径类名,则通过反射对其进行初始化
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
// 判断该全路径类是否为NamespaceHandler接口的实现类
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" +
namespaceUri + "] does not implement the [" +
NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler =
(NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init(); // 调用处理逻辑的初始化方法
handlerMappings.put(namespaceUri, namespaceHandler); //缓存处理逻辑类对象
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class ["
+ className + "] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for"
+ "NamespaceHandler class [" + className + "] for namespace ["
+ namespaceUri + "]", err);
}
}
}
可以看到,在处理命名空间url的时候,首先会判断是否存在当前url的处理逻辑,不存在则直接返回。如果存在,则会判断其为一个NamespaceHandler对象,还是一个全路径的类名,是NamespaceHandler对象则强制类型转换后返回,否则通过反射初始化该类,并调用其初始化方法,然后才返回。
我们继续查看NamespaceHandler.parse()方法,如下是该方法的源码:
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 获取当前标签使用的parser处理类
BeanDefinitionParser parser = findParserForElement(element, parserContext);
// 按照定义的parser处理类对当前标签进行处理,这里的处理类即我们定义的AppleBeanDefinitionParser
return (parser != null ? parser.parse(element, parserContext) : null);
}
这里的parse()方法首先会查找当前标签定义的处理逻辑对象,找到后则调用其parse()方法对其进行处理。这里的parser也即我们定义的AppleBeanDefinitionParser.parse()方法。这里需要注意的是,我们在前面讲过,在MyNameSpaceHandler.init()方法中注册的处理类逻辑的键(即apple)必须与xml文件中myapple:apple后的apple一致,这就是这里findParserForElement()方法查找BeanDefinitionParser处理逻辑的依据。如下是findParserForElement()方法的源码:
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 获取当前标签命名空间后的局部键名,即apple
String localName = parserContext.getDelegate().getLocalName(element);
// 通过使用的命名空间键获取对应的BeanDefinitionParser处理逻辑
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
这里首先获取当前标签的命名空间后的键名,即myapple:apple后的apple,然后在parsers中获取该键对应的BeanDefinitionParser对象。其实在MyNameSpaceHandler.init()方法中进行的注册工作就是将其注册到了parsers对象中。
2. 自定义属性
2.1 使用方式
自定义属性的定义方式和自定义标签非常相似,其主要也是进行命名空间和转换逻辑的定义。假设我们有一个Car对象,我们需要使用自定义标签为其添加一个描述属性。如下是Car对象的定义:
public class Car {
private long id;
private String name;
private String desc;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
如下是在applicationContext.xml中该对象的定义:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:car="http://www.lexueba.com/schema/car-desc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="car" class="chapter4.eg2.Car" car:car-desc="This is test custom attribute">
<property name="id" value="1"/>
<property name="name" value="baoma"/>
</bean>
</beans>
可以看到,car对象的定义使用的就是一般的bean定义,只不过其多了一个属性car:car-desc的使用。这里的car:car-desc对应的命名空间就是上面的http://www.lexueba.com/schema/car-desc。同自定义标签一样,自定义属性也需要在META-INF下的Spring.handlers和Spring.schemas文件中指定当前的处理逻辑和xsd定义,如下是这两个文件的定义:
Spring.handlers
http\://www.lexueba.com/schema/car-desc=chapter4.eg2.MyCustomAttributeHandler
Spring.schemas
http\://www.lexueba.com/schema/car.xsd=META-INF/custom-attribute.xsd
对应的xsd文件的定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.lexueba.com/schema/car-desc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lexueba.com/schema/car-desc"
elementFormDefault="qualified">
<xsd:attribute name="car-desc" type="xsd:string"/>
</xsd:schema>
可以看到,该xsd文件中只定义了一个属性,即car-desc。如下是MyCustomAttributeHandler的声明:
public class MyCustomAttributeHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionDecoratorForAttribute("car-desc",
new CarDescInitializingBeanDefinitionDecorator());
}
}
需要注意的是,和自定义标签不同的是,自定义标签是将处理逻辑注册到parsers对象中,这里自定义属性是将处理逻辑注册到attributeDecorators中。如下CarDescInitializingBeanDefinitionDecorator的逻辑:
public class CarDescInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
String desc = ((Attr) node).getValue();
definition.getBeanDefinition().getPropertyValues().addPropertyValue("desc", desc);
return definition;
}
}
可以看到,对于car-desc的处理逻辑就是获取当前定义的属性的值,由于知道其是当前标签的一个属性,因而可以将其强转为一个Attr类型的对象,并获取其值,然后将其添加到指定的BeandDefinitionHolder中。这里需要注意的是,自定义标签继承的是AbstractSingleBeanDefinitionParser类,实际上是实现的BeanDefinitionParser接口,而自定义属性实现的则是BeanDefinitionDecorator接口。
2.2 实现方式
关于自定义属性的实现方式,需要注意的是,自定义属性只能在bean标签中使用,因而我们可以直接进入对bean标签的处理逻辑中,即DefaultBeanDefinitionDocumentReader.processBeanDefinition()方法,如下是该方法的声明:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 对bean标签的默认属性和子标签进行处理,将其封装为一个BeanDefinition对象,
// 并放入BeanDefinitionHolder中
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
// 进行自定义属性或自定义子标签的装饰
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 注册当前的BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,
getReaderContext().getRegistry());
}catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 调用注册了bean标签解析完成的事件处理逻辑
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
这里我们直接进入BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired()方法中:
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = definitionHolder;
// 处理自定义属性
NamedNodeMap attributes = ele.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
// 处理自定义子标签
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
}
}
return finalDefinition;
}
可以看到,自定义属性和自定义子标签的解析都是通过decorateIfRequired()方法进行的,如下是该方法的定义:
public BeanDefinitionHolder decorateIfRequired(
Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
// 获取当前自定义属性或子标签的命名空间url
String namespaceUri = getNamespaceURI(node);
// 判断其如果为spring默认的命名空间则不对其进行处理
if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
// 获取当前命名空间对应的NamespaceHandler对象
NamespaceHandler handler = this.readerContext
.getNamespaceHandlerResolver()
.resolve(namespaceUri);
if (handler != null) {
// 对当前的BeanDefinitionHolder进行装饰
BeanDefinitionHolder decorated =
handler.decorate(node, originalDef,
new ParserContext(this.readerContext, this, containingBd));
if (decorated != null) {
return decorated;
}
}
else if (namespaceUri.startsWith("http://www.springframework.org/")) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" +
namespaceUri + "]", node);
}
else {
// A custom namespace, not to be handled by Spring - maybe "xml:...".
if (logger.isDebugEnabled()) {
logger.debug("No Spring NamespaceHandler found for XML schema namespace ["
+ namespaceUri + "]");
}
}
}
return originalDef;
}
decorateIfRequired()方法首先会获取当前自定义属性或子标签对应的命名空间url,然后根据该url获取当前命名空间对应的NamespaceHandler处理逻辑,并且调用其decorate()方法进行装饰,如下是该方法的实现:
@Nullable
public BeanDefinitionHolder decorate(
Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
// 获取当前自定义属性或子标签注册的BeanDefinitionDecorator对象
BeanDefinitionDecorator decorator = findDecoratorForNode(node, parserContext);
// 调用自定义的BeanDefinitionDecorator.decorate()方法进行装饰,
// 这里就是我们实现的CarDescInitializingBeanDefinitionDecorator类
return (decorator != null ? decorator.decorate(node, definition, parserContext) : null);
}
和自定义标签不同的是,自定义属性或自定义子标签查找当前Decorator的方法是需要对属性或子标签进行分别判断的,如下是findDecoratorForNode()的实现:
@Nullable
private BeanDefinitionDecorator findDecoratorForNode(Node node,
ParserContext parserContext) {
BeanDefinitionDecorator decorator = null;
// 获取当前标签或属性的局部键名
String localName = parserContext.getDelegate().getLocalName(node);
// 判断当前节点是属性还是子标签,根据情况不同获取不同的Decorator处理逻辑
if (node instanceof Element) {
decorator = this.decorators.get(localName);
} else if (node instanceof Attr) {
decorator = this.attributeDecorators.get(localName);
} else {
parserContext.getReaderContext().fatal(
"Cannot decorate based on Nodes of type [" + node.getClass().getName()
+ "]", node);
}
if (decorator == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionDecorator for " + (node instanceof Element
? "element" : "attribute") + " [" + localName + "]", node);
}
return decorator;
}
对于BeanDefinitionDecorator处理逻辑的查找,可以看到,其会根据节点的类型进行判断,根据不同的情况获取不同的BeanDefinitionDecorator处理对象。
3. 自定义子标签
对于自定义子标签的使用,其与自定义标签的使用非常相似,不过需要注意的是,根据对自定义属性的源码解析,我们知道自定义子标签并不是自定义标签,自定义子标签只是起到对其父标签所定义的bean的一种装饰作用,因而自定义子标签的处理逻辑定义与自定义标签主要有两点不同:①在NamespaceHandler.init()方法中注册自定义子标签的处理逻辑时需要使用registerBeanDefinitionDecorator(String, BeanDefinitionDecorator)方法;②自定义子标签的处理逻辑需要实现的是BeanDefinitionDecorator接口。其余部分的使用都和自定义标签一致。
4. 总结
本文主要对自定义标签,自定义属性和自定义子标签的使用方式和源码实现进行了讲解,有了对自定义标签的理解,我们可以在Spring的xml文件中根据自己的需要实现自己的处理逻辑。另外需要说明的是,Spring源码中也大量使用了自定义标签,比如spring的AOP的定义,其标签为<aspectj-autoproxy />。从另一个角度来看,我们前面两篇文章对Spring的xml文件的解析进行了讲解,可以知道,Spring默认只会处理import、alias、bean和beans四种标签,对于其余的标签,如我们所熟知的事务处理标签,这些都是使用自定义标签实现的。
Spring自定义标签解析与实现的更多相关文章
- Spring 自定义标签配置
前景:经常使用一些依赖于Spring的组件时,发现可以通过自定义配置Spring的标签来实现插件的注入,例如数据库源的配置,Mybatis的配置等.那么这些Spring标签是如何自定义配置的?学习Sp ...
- spring源码深度解析— IOC 之 自定义标签解析
概述 之前我们已经介绍了spring中默认标签的解析,解析来我们将分析自定义标签的解析,我们先回顾下自定义标签解析所使用的方法,如下图所示: 我们看到自定义标签的解析是通过BeanDefinition ...
- 这一次搞懂Spring自定义标签以及注解解析原理
前言 在上一篇文章中分析了Spring是如何解析默认标签的,并封装为BeanDefinition注册到缓存中,这一篇就来看看对于像context这种自定义标签是如何解析的.同时我们常用的注解如:@Se ...
- 自己构建一个Spring自定义标签以及原理讲解
平时不论是在Spring配置文件中引入其他中间件(比如dubbo),还是使用切面时,都会用到自定义标签.那么配置文件中的自定义标签是如何发挥作用的,或者说程序是如何通过你添加的自定义标签实现相应的功能 ...
- spring基础---->spring自定义标签(一)
Spring具有一个基于架构的扩展机制,可以使用xml文件定义和配置bean.本博客将介绍如何编写自定义XML bean的解析器,并用实例来加以说明.其实我一直相信 等你出现的时候我就知道是你. Sp ...
- spring自定义标签之 自我实现
引言: 最近心情比较难以平静,周末的两天就跑出去散心了,西湖边上走走,看日落,还是不错的.回来博客上发现,在自定义标签上,最后一步实现忘记加上了.其实,人生的路程中,我们总是实现着自我的价值,让自己 ...
- Spring自定义标签的实现
首先 简单写下 spring xml解析的过程 通过一段简单的 调用spring代码开始 public static void main(String[] args) { ApplicationCon ...
- angularjs directive (自定义标签解析)
angularjs directive (自定义标签解析) 定义tpl <!-- 注意要有根标签 --> <div class="list list-inset" ...
- spring自定义标签之 规范定义XSD
引言: spring的配置文件中,一切的标签都是spring定义好的.<bean/>等等,有了定义的规范,才能让用户填写的正常可用.想写自定义标签,但首先需要了解XML Schema De ...
随机推荐
- python基础整理5——多进程多线程和协程
进程与线程 1.进程 我们电脑的应用程序,都是进程,假设我们用的电脑是单核的,cpu同时只能执行一个进程.当程序处于I/O阻塞的时候,CPU如果和程序一起等待,那就太浪费了,cpu会去执行其他的程序, ...
- git 源码学习(init-db) 提交版本号 083c516331
写在前面的废话: 学完git之后,还是感觉云里雾里的,于是乎,就想到了通过学习git源码,来加深git的熟练度,同时学习一下c语言编程. git源码学习,逐步分析 这篇帖子是逐步分析git源码的,将g ...
- 20155212 C语言实现linux下pwd命令的两种方法
20155212 C语言实现linux下pwd命令的两种方法 学习pwd命令 通过man pwd命令查看 pwd [OPTION],一般不加参数 -P显示当前目录的物理路径 -L显示当前目录的连接路径 ...
- vab set dim
'问题一'给普通变量赋值使用LET ,只是LET 可以省略.'给对象变量赋值使用SET,SET 不能省略. Sub AA() Dim arr As String arr = "h ...
- 22-[jQuery]-选择器, js jQuery对象转换
1.基础选择器 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- 【BZOJ1070】[SCOI2007]修车
[BZOJ1070][SCOI2007]修车 题面 以后要多写题面flag 题目描述 同一时刻有\(N\)位车主带着他们的爱车来到了汽车维修中心.维修中心共有\(M\)位技术人员,不同的技术人员对不同 ...
- 4539: [Hnoi2016]树
4539: [Hnoi2016]树 链接 分析: 主席树+倍增. 代码: #include<cstdio> #include<algorithm> #include<cs ...
- 洛咕 P4528 [CTSC2008]图腾
洛咕 P4528 [CTSC2008]图腾 神题orz. 先约定abcd表示\(1\leq A<B<C<D\leq n\),而且\(y_a,y_b,y_c,y_d\)的排名正好是\( ...
- springmvc pager-taglib 分页,bootstrap样式
注意: 嵌入到项目中时必须以带参形式访问: http://localhost:8081/DETECT-X/showConnLogsByPager.action?pageSize=5&pager ...
- SQL Server 查询请求
当SQL Server 引擎接收到用户发出的查询请求时,SQL Server执行优化器将查询请求(Request)和Task绑定,并为Task分配一个Workder,SQL Server申请操作系统的 ...