【Spring】Spring中自定义Schema配置Bean

如何在Spring配置dubbo引用和服务

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns="http://www.springframework.org/schema/beans"
	   xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
	
    <dubbo:consumer check="false" />
    <!-- 注入dubbo消费者xxService -->
    <dubbo:reference id="xxService" interface="com.focuse.xx.xxService" timeout="5000"/>
    <!-- 定义bean yyServiceImpl -->
    <bean id="yyServiceImpl" class="com.focuse.yy.yyServiceImpl" />
    <!-- 暴露yyService -->
    <dubbo:service interface="com.focuse.api.yy.yyService"
		ref="yyServiceImpl"  timeout="3000" loadbalance="random" actives="0" />
</beans>

      我们对上面的配置应该都不陌生,配置了一个dubbo引用xxService,使用的标签是<dubbo:reference>;另外还把yyService暴露出去,使用的标签是<dubbo:service>。

      为什么Spring能够识别<dubbo:reference>、<dubbo:service>这样的标签,而不能识别<hello:print>? 我们能不能让Spring识别<hello:print>标签?答案是肯定的。

Spring中Schema扩展机制

    Spring框架是个包容的框架,啥玩意都能集成进来。在Xml配置方面Spring也让开发者有很高的自主权。Spring默认提供2个Bean容器(ApplicationContext)从Xml加载Bean配置:XmlWebApplicationContext以及ClassPathXmlApplicationContext。不管是哪个都是用XmlBeanDefinitionReader来解析xml并往容器中注册Bean。以XmlWebApplicationContext为例:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}

reader.loadBeanDefinitions(configLocation)调用的是AbstractBeanDefinitionReader的loadBeanDefinitions(String),最终会调用XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource)。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
......
}

doLoadBeanDefinitions首先是构造Document对象(xml解析用的是apache的xerces组件),然后调用registerBeanDefinitions注册Bean。继续往下跟,Spring是用DefaultBeanDefinitionDocumentReader来遍历Document的节点Node,DefaultBeanDefinitionDocumentReader.parseBeanDefinitions

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		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)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

这里很关键, 判断是否是默认namespace,如果不是默认namespace则调BeanDefinitionParserDelegate.parseCustomElement解析自定义的节点。

    这里我们联想一下前面dubbo的配置:<dubbo:service>标签的namespace是dubbo(此处xml配置的是http://dubbo.apache.org/schema/dubbo),不是默认的namespace(此处xml配置的是http://www.springframework.org/schema/beans),所以dubbo解析就用调用BeanDefinitionParserDelegate.parseCustomElement。 ok! 我们继续!

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

这里就是获取对应namespace的handler然后调用对应的parse方法解析Bean。

再往下,我们看一下如何获取namespace的对应handler的。我们看一下DefaultNamespaceHandlerResolver的resolve方法(this.readerContext.getNamespaceHandlerResolver获取的对象就是DefaultNamespaceHandlerResolver实例):

public NamespaceHandler resolve(String namespaceUri) {
		Map<String, Object> handlerMappings = getHandlerMappings();
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
			... ...
		}
	}

很明显,关键是在getHanderMappings里面,继续!

private Map<String, Object> getHandlerMappings() {
		if (this.handlerMappings == null) {
			synchronized (this) {
				if (this.handlerMappings == null) {
					try {
						Properties mappings =
								PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
						... ...(省略)
					}
					catch (IOException ex) {
                        ... ...(省略)
					}
				}
			}
		}
		return this.handlerMappings;
	}

getHandlerMappings从相关文件去加载Properties,handlerMappingsLocation默认值是META-INF/spring.handlers

DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"

ok!到这里,Spring的Schema机制基本分析完了,一句话概述就是 Spring在读取xml配置时,如果标签的namespace不是xml默认的namespace,就会调用对应的handler的parse来解析BeanDefinition并注册

既然已经知道Spring的Schema机制,我们来解答第一个问题:为什么Spring能够识别<dubbo:reference>、<dubbo:service>这样的标签。因为dubbo的jar定义了spring.handers,并解析该标签。有兴趣的可以看看DubboNamespaceHandler,继续跟踪Dubbo是如何启动的。

如何自定义Schema

    根据前文自定义Schema需要2个元素:META-INF下创建spring.handers文件、定义NamesapceHandler实现类。其实,在加载xml的时候还需要校验标签,所以还需要2元素:META-INF创建spring.schemas、定义schema文件(.xsd)。以<hello:print>标签为例。

第一步  定义schema文件(hello.xsd),规范xml标签的类型、元素、属性等

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.focuse.com/schema/hello">

    <xsd:element name="print">
        <xsd:complexType>
            <xsd:attribute name="text" type="xsd:string" />
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

这里我们只定义一个元素标签<print>,该元素标签包含一个属性"text"。 这里不赘述schema语法,具体的schema语法参考https://www.w3school.com.cn/schema/index.asp

第二步 META-INF创建2个文件 spring.handlers 和 spring.schemas

spring.handlers定义namespace对应的handler

http\://www.focuse.com/schema/hello=com.focuse.springschema.config.HelloNamespaceHandler

spring.schemas将schema文件URI指向本地文档。这里我们将www.focuse.com/shcema/hello.xsd指向到本地META-INF下的hello.xsd(就是我们第一步定义的schema文件)

http\://www.focuse.com/schema/hello.xsd=META-INF/hello.xsd

第三步 实现NamespaceHandler处理xml元素(element)

Spring为了方便开发者,提供了一个抽象类NamespaceHandlerSupport,开发者需要继承该抽象类。但是,在实现类里,开发者要定义单个标签的解析器(BeanDefinitionParser)。

public class HelloNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("print", new HelloBeanDefinitionParser());
    }
}

因为我们第一步只定义了一个标签<print>,所以这里只注册"print"的解析器HelloBeanDefinitionParser。重点要看HelloBeanDefinitionParser做了什么

public class HelloBeanDefinitionParser implements BeanDefinitionParser {
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String text = element.getAttribute("text");
        GenericBeanDefinition bd = new GenericBeanDefinition();
        bd.setScope("singleton");//单例
        bd.setBeanClassName("com.focuse.springschema.printer.StringPrinter");
        bd.getPropertyValues().add("text", text); //设置属性
        parserContext.getRegistry().registerBeanDefinition("com.focuse.springschema.printer.StringPrinter", bd);
        return bd;
    }
}

HelloBeanDefinitionParser必须实现BeanDefinitionParser,parse方法的参数element就是当前解析的元素,

  • 获取element的"text"属性备用(第一步中我们定义了<print>标签是有"text"属性的)
  • 创建GenericBeanDefinition对象,并设置单例、设置类名(com.focuse.springschema.printer.StringPrinter)、添加text属性
  • 向Bean容器注册BeanDefinition

OK! 至此,我们已经自定义了Schema。目录结构如下:

接下来,我们需要在应用中使用。

com.focuse.springschema.printer.StringPrinter类定义如下:

public class StringPrinter {
    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String print() {
        return text;
    }
}

 

首先,我们定义Bean 配置,配置元素<hello:print>其中hello是namespace(看xml文件头部),并设置元素的属性text="hello sprint shcema"

<?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:hello="http://www.focuse.com/schema/hello"
       xsi:schemaLocation=
               "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.focuse.com/schema/hello http://www.focuse.com/schema/hello.xsd"
>
    <hello:print text="hello spring schema" />
</beans>

然后,我们定义Controller,在controller里面,我们注入StringPrinter的。因为<hello:print>元素就是往容器中添加了StringPrinter的Bean对象,所以这里自动装配能找到。在hello这个方法里面就是返回stringPrinter.getText值

@RestController
@RequestMapping("/focuse")
public class SpringController {
    @Resource
    private StringPrinter stringPrinter;

    @RequestMapping("hello")
    public String hello() {
        return stringPrinter.getText();
    }

}

 最后结果,就是输出"hello sprint schema"

总结

    本文简单介绍了Spring Schema的机制,我们知道Spring是如何识别<dubbo:reference>的,一言以蔽之就是Spring在读取xml配置时,如果标签的namespace不是xml默认的namespace,就会调用对应的handler的parse来解析BeanDefinition并添加到Bean容器中。handler的定义默认配置在META-INF/spring.handlers中。

   本文还介绍了自定义Schema的3个步骤:

  • 定义Schema规范文件;
  • META-INF创建spring.handers和spring.schemas;
  • 实现NamespaceHandler处理xml元素(element)

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring框架,我们可以使用XML配置文件来定义和配置应用程序的组件,包括Service组件。以下是一个示例bean.xml配置文件,用于定义一个名为"userService"的Service组件: ``` <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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-4.3.xsd"> <bean id="userService" class="com.example.UserService"> <property name="userRepository" ref="userRepository"/> </bean> <bean id="userRepository" class="com.example.UserRepository"> <constructor-arg ref="dataSource"/> </bean> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> ``` 在上面的示例,我们定义了一个名为"userService"的Service组件,其类是"com.example.UserService"。该组件需要一个名为"userRepository"的依赖,因此我们还定义了一个名为"userRepository"的组件,其类是"com.example.UserRepository"。"userRepository"组件需要一个名为"dataSource"的依赖,因此我们还定义了一个名为"dataSource"的组件,其类是"org.apache.commons.dbcp2.BasicDataSource"。 我们可以通过在代码载该配置文件来初始化Spring容器,并使用容器的"userService"组件: ``` ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); UserService userService = (UserService) context.getBean("userService"); ``` 这样就可以使用定义好的Service组件了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值