Spring实现自定义XML标签详解

前言

学习spring源码已经有一年多了,了解过的朋友肯定都知道spring源码是一块非常难啃的骨头,所以每当找到一丝丝成就感就想拿出来与大家一起分享,这样也能让自己始终保持着对spring源码学习的兴趣。

虽然现在使用xml已经不是主流的方式了,但是一些公共的开源组件都基于自身的功能定制了自定义标签,比如dubbo。

自定义XML关键的几个配置

  • spring.handler
    定义解析xml元素的处理类。
  • spring.schemas
    指定xsd文件的位置。
  • xxx.xsd
    类似于语法规范,约束自定义标签的属性类型等。

具体实现

spring自身中其实也有很多自定义的标签,比如context、aop等,所以我们自己实现的方式很简单,照抄就可以了。

下面我们就按照context标签的实现方式来自己搞一个。

在这里插入图片描述

1、建一个META-INF目录,并创建spring.handler和spring.schemas两个文件。

在这里插入图片描述

2、完成spring.handler文件中的内容

在这里插入图片描述

ContextNamespaceHandler点进去,继承了NamespaceHandlerSupport ,并重写init方法。

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

}

照着实现即可,就定义一个标签user,创建了两个新的类,WylBeanDefinitionParser和User(user是指定标签生成的类)。

在这里插入图片描述

public class MyCustomNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("user", new WylBeanDefinitionParser());
    }
}
public class WylBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected Class<?> getBeanClass(Element element) {
        return User.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {

        String name = element.getAttribute("name");
        if (StringUtils.hasLength(name)) {
            builder.addPropertyValue("name", name);
        }

        String age = element.getAttribute("age");
        if (StringUtils.hasLength(age)) {
            builder.addPropertyValue("age", age);
        }
    }
}
public class User {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

3、处理spring.schemas文件

这个文件很简单定义一下你的xsd文件的位置即可

在这里插入图片描述
在这里插入图片描述

4、处理xsd文件

这个就按照语法规范来实现就可以了

定义了标签名user,与前面MyCustomNamespaceHandler中的保持一致,并且定义了三个属性,id是标识。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.springframework.org/schema/wyl"
            elementFormDefault="qualified">
    <xsd:element name="user">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string">
            </xsd:attribute>
            <xsd:attribute name="name" type="xsd:string">
            </xsd:attribute>
            <xsd:attribute name="age" type="xsd:string">
            </xsd:attribute>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

5、使用

上面4步完成后,自定义标签就已经完成了,接下来只要在你的application.xml文件中添加自己的命名空间和schema位置就可以使用了

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:wyl="http://www.springframework.org/schema/wyl"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/wyl http://www.springframework.org/schema/wyl.xsd ">

    <wyl:user id="myselfTag" name="wangwu" age="18"></wyl:user>

</beans>

6、测试

public class TestSpring {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
        User user = (User) applicationContext.getBean("wyl");
        System.out.println("myselfTag: " + user.getName());
    }
}

在这里插入图片描述


源码分析

既然是学习源码,只照抄方法肯定是不够的,其实这部分逻辑还是比较清晰的,并且从头到尾我们自己重写的方法也就3个,一起来简单分析下吧。

1、MyCustomNamespaceHandler中init方法

public class MyCustomNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("user", new WylBeanDefinitionParser());
    }
}

这个init方法的调用链路比较长,入口肯定是从refresh方法中的obtainFreshBeanFactory()方法开始,最终会执行到如下的方法中,然后先从spring.handles文件中获取com.wyl.learn.config.MyCustomNamespaceHandler值(文件中等号右边定义的内容),再利用反射拿到Class对象,调用init方法即可。

	@Nullable
	public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
		//init方法调用的入口
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		//init方法返回后,接着就处理parse方法调用的入口
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}
	public NamespaceHandler resolve(String namespaceUri) {
		//获取所有的spring.handles文件中的内容,等号左边是key,等号右边的value
		Map<String, Object> handlerMappings = getHandlerMappings();
		//根据key找到对象的value,也就是全限定类名,使用反射就可以得到具体的对象
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
			String className = (String) handlerOrClassName;
			try {
				//反射获得Class对象
				Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
				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);
				//调用自己重写的init方法
				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);
			}
		}
	}

2、 WylBeanDefinitionParser中doParse方法

public class WylBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected Class<?> getBeanClass(Element element) {
        return User.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {

        String name = element.getAttribute("name");
        if (StringUtils.hasLength(name)) {
            builder.addPropertyValue("name", name);
        }

        String age = element.getAttribute("age");
        if (StringUtils.hasLength(age)) {
            builder.addPropertyValue("age", age);
        }
    }
}

这个方法主要就是通过addPropertyValue把属性添加到beanDefinition对象的propertyValueList集合属性中。

	public BeanDefinitionBuilder addPropertyValue(String name, @Nullable Object value) {
		this.beanDefinition.getPropertyValues().add(name, value);
		return this;
	}

add方法

	public MutablePropertyValues add(String propertyName, @Nullable Object propertyValue) {
		addPropertyValue(new PropertyValue(propertyName, propertyValue));
		return this;
	}

addPropertyValue方法

	public MutablePropertyValues addPropertyValue(PropertyValue pv) {
		for (int i = 0; i < this.propertyValueList.size(); i++) {
			PropertyValue currentPv = this.propertyValueList.get(i);
			if (currentPv.getName().equals(pv.getName())) {
				pv = mergeIfRequired(pv, currentPv);
				setPropertyValueAt(pv, i);
				return this;
			}
		}
		//最终添加到propertyValueList集合中
		this.propertyValueList.add(pv);
		return this;
	}

propertyValueList是MutablePropertyValues类的属性,而MutablePropertyValues又是beanDefinition中的一个属性。

在这里插入图片描述

3、 最后还有一个getBeanClass方法

此方法返回的是你自定义标签的BeanClass对象,也就是User,容器在标签解析时就可以通过这个方法得到beanDefinition的class类型,最终通过doParse给属性赋完值以后就可以添加到容器中了,添加到容器中的对象之后就可以通过getBean方法获取了,属性可以从propertyValueList中获取。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Cloud Gateway是一个基于Spring Framework 5、Project Reactor和Spring Boot 2构建的反应式API网关。它提供了一种简单而强大的方式来路由、限流和过滤请求。 下面是Spring Cloud Gateway的全部配置详解: 1. 创建一个Spring Boot应用程序,并添加以下依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> ``` 2. 在application.yml或application.properties文件中配置网关的基本信息,例如端口号和应用名称: ```yaml server: port: 8080 spring: application: name: gateway-service ``` 3. 配置路由规则,定义请求到达网关后的转发规则。可以通过yml或properties文件进行配置: - YML格式: ```yaml spring: cloud: gateway: routes: - id: route1 uri: http://example.com predicates: - Path=/example/** ``` - Properties格式: ```properties spring.cloud.gateway.routes[0].id=route1 spring.cloud.gateway.routes[0].uri=http://example.com spring.cloud.gateway.routes[0].predicates[0]=Path=/example/** ``` 上述配置表示将以/example/**开头的所有请求转发到http://example.com。 4. 配置过滤器,对请求进行过滤和修改。可以使用内置的过滤器,也可以自定义过滤器。 - 内置过滤器使用示例: ```yaml spring: cloud: gateway: routes: - id: route1 uri: http://example.com predicates: - Path=/example/** filters: - AddRequestHeader=X-Request-Foo, Bar - AddResponseHeader=X-Response-Foo, Baz ``` 上述配置表示在转发请求之前,在请求头中添加X-Request-Foo: Bar,在响应头中添加X-Response-Foo: Baz。 - 自定义过滤器使用示例: ```java @Component public class CustomFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 自定义过滤器逻辑 return chain.filter(exchange); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } } ``` 自定义过滤器需要实现GlobalFilter接口,并通过@Component注解将其注册为Spring Bean。可以在filter方法中编写自定义的过滤逻辑。 这些是Spring Cloud Gateway的基本配置,你可以根据实际需求添加更多的路由规则和自定义过滤器。通过灵活配置,你可以实现路由转发、请求限流、负载均衡等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值