spring自定义标签
初始化项目
项目使用maven构建,项目结构如下
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>consutom-element</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
</dependencies>
</project>
基于spring,实现自定义标签
spring自定义标签的规则如下,必须在META-INF下面存在spring.handlers和spring.schemas两个文件,handlers文件用于通过定义的scheme找到对应解析的handler,schemas文件则是通过scheme找到xsd文件
1.定义custom.xsd文件
这里我们定义自定义的xsd文件,用于定义xml里面我们自定义标签的规范
如下,在META-INF目录下面创建schemas目录,将自定义xml的xsd约束放在这个目录下面
custom.xsd
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://lhstack.com/schema/custom">
<!--targetNamespace用于定义custom的命名空间-->
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="custom-bean">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
custom-bean
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<!--这里通过属性的方式定义custom的class-->
<xsd:attribute name="class" type="xsd:string" />
<!--这里通过属性的方式定义custom的class name-->
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
</xsd:schema>
2.定义spring.schemas文件,用于指向自定义的custom.xsd文件
spring.schemas
http\://lhstack.com/schema/custom.xsd=META-INF/schemas/custom.xsd
3.定义spring.handlers,用于定义解析自定义标签的handler
spring.handlers
http\://lhstack.com/schema/custom=com.lhstack.custom.handler.NamespaceParseHandler
4.创建spring.handlers里面指定的class类
NamespaceParseHandler.java
package com.lhstack.custom.handler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class NamespaceParseHandler extends NamespaceHandlerSupport {
@Override
public void init() {
//解析custom-bean标签
registerBeanDefinitionParser("custom-bean",null);
}
}
5.创建custom-bean解析器,并在NamespaceParseHandler中使用
CustomBeanParseDefinition.java
package com.lhstack.custom.parse;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
public class CustomBeanParseDefinition implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
//获取属性上面的name字段
String name = element.getAttribute("name");
//获取class
String className = element.getAttribute("class");
//获取bean工厂
BeanDefinitionRegistry registry = parserContext.getRegistry();
//创建beanDefinition
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
//设置bean的class
beanDefinition.setBeanClassName(className);
//注册bean到工厂
registry.registerBeanDefinition(name,beanDefinition);
return null;
}
}
6.在resource目录下面创建spring.xml文件,添加custom-bean标签
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:custom="http://lhstack.com/schema/custom"
xsi:schemaLocation="http://lhstack.com/schema/custom http://lhstack.com/schema/custom.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<custom:custom-bean name="test" class="java.util.HashMap" />
</beans>
7.创建启动类,使用spring.xml,自定义标签注册的bean已经到容器里面了
Applicatin.java
package com.lhstack.custom;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Application {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
context.start();
Object test = context.getBean("test");
System.out.println(test.getClass());
}
}
扩展自定义标签,添加对象属性的注入
1.修改custom.xsd定义,添加properties标签
custom.xsd
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://lhstack.com/schema/custom"
targetNamespace="http://lhstack.com/schema/custom">
<!--targetNamespace用于定义custom的命名空间-->
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<!-- 这里我们指向complexType,如果在element里面定义,不知道为什么,会报错 -->
<xsd:element name="custom-bean" type="customBeanType">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
custom-bean
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<!-- 定义entry标签,引用entryType -->
<xsd:element name="entry" type="entryType" />
<!-- 定义entryType的类型,拥有两个属性 -->
<xsd:complexType name="entryType">
<xsd:attribute name="key" type="xsd:string" use="required" />
<xsd:attribute name="value" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:element name="properties" type="propertiesType" />
<!-- 定义propertiesType,添加entry子标签 -->
<xsd:complexType name="propertiesType">
<xsd:sequence maxOccurs="unbounded">
<xsd:element ref="entry" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="customBeanType">
<xsd:sequence minOccurs="0" maxOccurs="1">
<!-- 指向properties标签 -->
<xsd:element ref="properties" maxOccurs="1" />
</xsd:sequence>
<!--这里通过属性的方式定义custom的class-->
<xsd:attribute name="class" type="xsd:string" />
<!--这里通过属性的方式定义custom的class name-->
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:schema>
2.修改之前定义的CustomBeanParseDefinition,添加属性注入的功能
CustomBeanParseDefinition.java
package com.lhstack.custom.parse;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class CustomBeanParseDefinition implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
//获取属性上面的name字段
String name = element.getAttribute("name");
//获取class
String className = element.getAttribute("class");
Map<String,String> attributes = new HashMap<>();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node item = childNodes.item(i);
if(item.getNodeName().endsWith(":properties")){
NodeList entryList = item.getChildNodes();
for (int j = 0; j < entryList.getLength(); j++) {
Node node = entryList.item(j);
NamedNodeMap namedNodeMap = node.getAttributes();
if(Objects.nonNull(namedNodeMap)){
attributes.put(namedNodeMap.getNamedItem("key").getNodeValue(),namedNodeMap.getNamedItem("value").getNodeValue());
}
}
}
}
try{
//这里转换成class
Class<?> clazz = Class.forName(className);
//获取bean工厂
BeanDefinitionRegistry registry = parserContext.getRegistry();
//创建beanDefinition
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
//设置bean的class
beanDefinition.setBeanClass(clazz);
//如果是map,则通过构造方法的方式把参数注入进去
if(Map.class.isAssignableFrom(clazz)){
ConstructorArgumentValues values = new ConstructorArgumentValues();
values.addIndexedArgumentValue(0,attributes);
beanDefinition.setConstructorArgumentValues(values);
}else{
//如果是普通bean,则通过propertyValues的方式注入
beanDefinition.getPropertyValues().addPropertyValues(attributes);
}
//注册bean到工厂
registry.registerBeanDefinition(name,beanDefinition);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
3.使用map方式,测试
<?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:custom="http://lhstack.com/schema/custom"
xsi:schemaLocation="http://lhstack.com/schema/custom http://lhstack.com/schema/custom.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<custom:custom-bean name="test" class="java.util.HashMap" >
<custom:properties>
<custom:entry key="spring.application.name" value="app" />
<custom:entry key="spring.application.test" value="appTest" />
</custom:properties>
</custom:custom-bean>
</beans>
4.创建bean测试
PropertiesBean.java
package com.lhstack.custom;
public class PropertiesBean {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "PropertiesBean{" +
"name='" + name + '\'' +
", value='" + value + '\'' +
'}';
}
}
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:custom="http://lhstack.com/schema/custom"
xsi:schemaLocation="http://lhstack.com/schema/custom http://lhstack.com/schema/custom.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<custom:custom-bean name="test" class="com.lhstack.custom.PropertiesBean" >
<custom:properties>
<custom:entry key="name" value="app" />
<custom:entry key="value" value="appTest" />
</custom:properties>
</custom:custom-bean>
</beans>
自定义属性
1.修改custom.xsd,在最后schema标签上面添加一行属性定义
2.创建属性解析器
package com.lhstack.custom.parse;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Node;
public class CustomAttributeBeanDefinitionDecorator implements BeanDefinitionDecorator {
/**
* 这里创建一个代理工厂,方便打印添加的description属性
*/
private static class CustomFactory implements FactoryBean {
private Class clazz;
private String description;
public CustomFactory(String clazz,String description) throws ClassNotFoundException {
this.clazz = Class.forName(clazz);
this.description = description;
}
@Override
public Object getObject() throws Exception {
//使用cglib增强
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.clazz);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("bean attribute is [" + description + "]");
return proxy.invokeSuper(obj, args);
});
return enhancer.create();
}
@Override
public Class<?> getObjectType() {
return clazz;
}
}
@Override
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClass(CustomFactory.class);
rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanDefinition().getBeanClassName());
rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(node.getNodeValue());
return new BeanDefinitionHolder(rootBeanDefinition,definition.getBeanName());
}
}
3.注册属性解析器,解析刚刚custom.xsd里面定义的description属性
4.在spring.xml里面添加一个bean定义,并添加刚刚定义的属性
5.启动程序,自定义属性注入成功了