spring拓展之如何定义自己的namespace

目录

  • spring拓展 定义自己的namespace
    • 1.查看源码认识spring是怎么加载xml配置的
    • 2.定义自己的namespace
  • spring-namespace实现自定义标签类
    • 1.配置java Bean
    • 2.编写xsd文件
    • 3.编写BeanDefinationParse标签解析类
    • 4.编写调用标签解析类的NamespaceHandler类
    • 5.编写spring.handlers和spring.schemas以供spring读取
    • 6.打包
    • 7.在其他项目中使用

spring拓展 定义自己的namespace
1.查看源码认识spring是怎么加载xml配置的
1.1 spring是怎么创建对象的?
查看spring beanFactory的继承关系
spring拓展之如何定义自己的namespace
文章图片

通过查看源码可以得知,BeanFactory 中的对象创建是实际是根据RootBeanDefinition创建的, 在AbstractAutowireCapableBeanFactory中有具体的实现,包括创建实例,
利用Spring拓展
java的内省实现BeanWrapperImpl,创建对象的包装类,使用反射给对象填充属性,并实现依赖注入DI 。具体可以自行参阅源码。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {.....protected abstract Object createBean(String beanName, RootBeanDefinition mbd, Object[] args)throws BeanCreationException; }

spring拓展之如何定义自己的namespace
文章图片

而RootBeanDefination定义的是什么呢?查看AbstractBeanDefination类。
spring拓展之如何定义自己的namespace
文章图片

可以看到这里就是Spring对对象属性的封装,包括类名,属性,加载策略等等,其实也就是我们在xml里 配置的对象。
1.2 spring是怎么将xml里配置的对象读到BeanFactory中的?
在查看spring容器的源码时,得知spring 是使用 org.springframework.beans.factory.xml.XmlBeanDefinitionReader 进行xml解析的
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {.....protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {//读取xmlDocument doc = doLoadDocument(inputSource, resource); //解析并注册xml中定义的BeanDefinationreturn registerBeanDefinitions(doc, resource); }catch (BeanDefinitionStoreException ex) {xxx}}.....}

接下来查看对dom 解析部分的源码
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {@Overridepublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }protected void doRegisterBeanDefinitions(Element root) {//为了实现进行递归解析BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) {//对profile的支持String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {return; }}}preProcessXml(root); //开始解析dom树parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }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)) {//spring的基础命名元素解析(import、bean、beans、alias)其中在//beans嵌套时会进行递归解析parseDefaultElement(ele, delegate); }else {//拓展元素解析delegate.parseCustomElement(ele); }}}}else {delegate.parseCustomElement(root); }}}

查看spring是怎么实现拓展元素的解析的
public class BeanDefinitionParserDelegate {public BeanDefinition parseCustomElement(Element ele) {return parseCustomElement(ele, null); }public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {//获取当前节点命名空间URIString namespaceUri = getNamespaceURI(ele); //根据命名空间解析到自定义的NamespaceHandlerNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; }//使用拓展的Handler对当前节点进行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }}

其中NamespaceHandlerResolver 会去查找当前项目中classpath 下的META-INF目录下所有文件名为 spring.handlers配置文件,定位到自定义namespace的解析器实现类。
其中在namespace 的处理器中可以通过进行BeanDefination的注册,注册过的BeanDefination会用来给BeanFactory创建对象使用,将解析好的BeanDefination注册到parserContext.getRegistry()中即可。其实DefaultListableBeanFactory 就是一个BeanDefinitionRegistry。
@Overridepublic BeanDefinition parse(Element element, ParserContext parserContext) {....parserContext.getRegistry().registerBeanDefinition(beanName, mbd); }


2.定义自己的namespace
2.1 定义schema约束xsd文件
将自定义的xsd文件放到项目的 META-INF 目录下。

更多xsd写法可以参阅xml的相关资料。
2.2创建自定义namespace的NamespaceHandler
使用NamespaceHandlerSupport来实现我们定义的NamespaceHandler。在init时去提供具体的标签的 解析器。
BeanDefinitionParser
public class MybeanParser implements BeanDefinitionParser {@Overridepublic BeanDefinition parse(Element element, ParserContext parserContext) {RootBeanDefinition mbd =new RootBeanDefinition(); mbd.setBeanClassName(element.getAttribute("class")); String beanName = element.getAttribute("id"); MutablePropertyValues mutablePropertyValues = new MutablePropertyValues(); mutablePropertyValues.add("name", element.getAttribute("name")); mbd.setPropertyValues(mutablePropertyValues); parserContext.getRegistry().registerBeanDefinition(beanName, mbd); return mbd; }}

实现自定义的NamespaceHandler
public class MynsNameSpaceHandler extends NamespaceHandlerSupport{@Overridepublic void init() {registerBeanDefinitionParser("mybean", new MybeanParser()); }}

这里的mybean是myns namespace下的元素标签的具体的解析实现
如:

2.3配置自定义的NamespaceHandler映射
在META-INF下创建文件 spring.handlers
http\://xxx.xxx.com/schema/myns=com.xxx.MynsNameSpaceHandler

2.4使用自定义的Namespace

2.5测试
public class MybeanNamespaceTestCase {@SuppressWarnings("resource")@Testpublic void testGetBean(){ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"myapp.xml"},false); ac.setValidating(false); ac.refresh(); People bean = ac.getBean("mybean123",People.class); System.out.println(bean.getName()); }}

这里设置ac.setValidating(false); 是因为如果开启xml约束检查,需要配置schema的位置,
也是在META-INF 下新建spring.schemas
并加入:
http\://xxx.xxx.com/schema/myns.xsd=META-INF/myns.xsd

这样spring 在xml解析时会调用org.springframework.beans.factory.xml.PluggableSchemaResolver
进行获取schema文件进行约束检查,
这样配置完毕后就可以ac.setValidating(true); 啦,如果文件内容不符合规范,会启动时抛出异常。
此外,如果想要在使用过程开发工具能够像使用spring 自身的一些配置时有提升功能,可以将schema文件
上传到文件服务器上,能够通过http 访问到xsi:schemaLocation的地方,或者配置开发工具中的xml 约束映射,将地址映射到本 地磁盘中,这样就能
spring拓展之如何定义自己的namespace
文章图片

spring拓展之如何定义自己的namespace
文章图片


spring-namespace实现自定义标签类 介绍如何通过spring namespace的方式进行bean的配置
最终要达到的目的如下:

好,下面上货。
1.配置java bean
2.编写xsd文件
3.编写BeanDefinationParse标签解析类
4.编写调用标签解析类的NamespaceHandler类
5.编写spring.handlers和spring.schemas供spring读取
6.在spring中使用
spring拓展之如何定义自己的namespace
文章图片


目录结构

1.配置java Bean
package com.xueyou; public class User {private int userId; private String name; public User() {} public int getUserId() {return userId; } public void setUserId(int userId) {this.userId = userId; } public String getName() {return name; } public void setName(String name) {this.name = name; } @Overridepublic String toString() {return "User{" +"userId='" + userId + '\'' +", name='" + name + '\'' +'}'; }}


2.编写xsd文件


3.编写BeanDefinationParse标签解析类
package com.xueyou.parser; import com.xueyou.User; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; import org.w3c.dom.Element; public class UserDefinationParser extends AbstractSimpleBeanDefinitionParser {@Overrideprotected Class getBeanClass(Element element) {return User.class; } @Overrideprotected void doParse(Element element, BeanDefinitionBuilder builder) {int userId = Integer.valueOf(element.getAttribute("userId"), 0); String name = element.getAttribute("name"); builder.addPropertyValue("userId", userId); builder.addPropertyValue("name", name); }}


4.编写调用标签解析类的NamespaceHandler类
package com.xueyou.handler; import com.xueyou.parser.UserDefinationParser; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class UserNamespaceHandler extends NamespaceHandlerSupport {public void init() {registerBeanDefinitionParser("self-user", new UserDefinationParser()); }}


5.编写spring.handlers和spring.schemas以供spring读取
spring.handlers
http\://www.wuxueyou.cn/schema/user=com.xueyou.handler.UserNamespaceHandler

spring.schemas
http\://www.wuxueyou.cn/schema/user.xsd=namespace/user.xsd


6.打包
首先把刚才的打成一个jar包,需要注意在maven的plugin中添加如下内容, 这个shade插件能够合并指定的内容,比如spring.schema等等
org.apache.maven.pluginsmaven-shade-plugin3.2.0packageshadeMETA-INF/spring.handlersMETA-INF/spring.schemas


7.在其他项目中使用
在一个web项目中使用这个类

在controller中进行测试
package com.example.demo.controller; import com.xueyou.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController@RequestMapping("/namespacetest")public class NamespaceController { @Resource(name = "user2")private User user; @RequestMapping("/user")public User namespacetest() {return user; }}

运行结果:

spring拓展之如何定义自己的namespace
文章图片

运行结果
最终,我们可以使用spring-namespace对bean进行配置了。这样比标签要好的多。
【spring拓展之如何定义自己的namespace】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

    推荐阅读