通常我们在applicationContext.xml文件中使用spring的标签时,会发现spring默认支持的只有5种,如图所示
那么问题来了,spring有那么多的功能,只用这5种标签能够全都实现吗?
答案是否定的,肯定不不能。
于是,自定义标签的功能闪亮登场。
我们以自定义的context标签为例子。大家知道如果直接在配置文件中引入context标签的元素,会在编辑器中有红色警告,如图
这个时候就需要引入命名空间,我们到spring-context-xxx.jar的META-INF目录下找到spring.schemas文件,找到文件中的
http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context.xsd
然后在applicationContext.xml文件中,做如下图片配置,之前增加的context元素红色警告就消失了。
没错,这里给大家演示的是如何引入自定义标签。
问题又来了,我们想引入我们自己定义的标签该怎么玩呢?
定义一个实体User
/** * @author sue * @date 2020/5/31 10:33 */ @AllArgsConstructor @Data public class User { private String id; private String name; private String password; private int age; private byte sex; }
对自定义的标签进行解析
/** * @author sue * @date 2020/5/31 10:27 */ public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override public Class<?> getBeanClass(Element element) { return User.class; } @Override public void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String id = element.getAttribute("id"); String name = element.getAttribute("name"); String password = element.getAttribute("password"); String age = element.getAttribute("age"); String sex = element.getAttribute("sex"); builder.addConstructorArgValue(id); builder.addConstructorArgValue(name); builder.addConstructorArgValue(password); builder.addConstructorArgValue(Integer.parseInt(age)); builder.addConstructorArgValue(Byte.valueOf(sex)); } }
对自定义标签进行初始化,让程序可以识别sue:user标签
/** * @author sue * @date 2020/5/31 10:26 */ public class MyTagHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); } }
在resouces的META-INF目录下,有三个文件mytag.xsd、spring.handlers 和
spring.schemas。
1.mytag.xsd
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.sue.sc.com/schema/mytag" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.sue.sc.com/schema/mytag" elementFormDefault="qualified" attributeFormDefault="unqualified"> <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="password" type="xsd:string"></xsd:attribute> <xsd:attribute name="age" type="xsd:int"></xsd:attribute> <xsd:attribute name="sex" type="xsd:byte"></xsd:attribute> </xsd:complexType> </xsd:element> </xsd:schema>
2.spring.handlers
http\://www.sue.sc.com/schema/mytag=com.sue.jump.tag.MyTagHandler
3.spring.schemas
http\://www.sue.sc.com/schema/mytag.xsd=META-INF/mytag.xsd
在applicationContext.xml 使用自定义的sue:user的标签,给自定义的其他元素赋值。
<?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:sue="http://www.sue.sc.com/schema/mytag" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.sue.sc.com/schema/mytag http://www.sue.sc.com/schema/mytag.xsd"> <sue:user id="sueUser1" name="苏三" password="123456" age="18" sex="1"/> </beans>
测试用例
/** * 用户标签 * * @author sue * @date 2020/5/31 10:07 */ public class MyTagTest { @Test public void testMyTag() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) applicationContext.getBean("sueUser1"); System.out.println("name=>" + user.getName()); System.out.println("password=>" + user.getPassword()); System.out.println("age=>" + user.getAge()); System.out.println("sex=>" + user.getSex()); } }
执行结果:
name=>苏三
password=>123456
age=>18
sex=>1
哈哈哈,能够看到以上的打印,说明我们自定义的sue:开头的标签成功了。
下面我们进一步看看spring自定义标签底层是怎么实现的。
我们先看一下XmlBeanDefinitionReader类的 registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
重点看看这一行代码
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
进入createReaderContext方法
public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); }
再看看getNamespaceHanderResolver方法
public NamespaceHandlerResolver getNamespaceHandlerResolver() { if (this.namespaceHandlerResolver == null) { this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); } return this.namespaceHandlerResolver; }
第一次进来this.namespaceHandlerResolver=null,所以会调用 createDefaultNamespaceHandlerResolver方法
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() { ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader()); return new DefaultNamespaceHandlerResolver(cl); }
看看 构造方法:DefaultNamespaceHandlerResolver
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) { this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION); }
倒数第二步看到一个熟悉的配置文件
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader, String handlerMappingsLocation) { Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null"); this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); this.handlerMappingsLocation = handlerMappingsLocation; }
哈哈哈,看到了spring.handlers文件
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
得出结果:this.handlerMappingsLocation = META-INF/spring.handlers
接着杀个回马枪,回到XmlBeanDefinitionReader类的 registerBeanDefinitions方法,往里面一步步跟到 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); } }
上图中parseDefaultElement主要用于解析默认标签了,也包含用户自定义元素的解析,例如:p:name 等。parseCustomElement主要用于解析用户自定义标签,接下来,我们重点看一下:
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
进入BeanDefinitionParserDelegate类的parseCustomElement方法
@Nullable public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } 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)); }
上面代码 首先获取命名空间,如:http://www.sue.sc.com/schema/mytag,如果命名空间为空,则直接返回,如果不为空,则根据命名空间,获取对应的NamespaceHandler,调用parse方法进行自定义标签的解析。我们重点看看resolve方法:
@Override @Nullable 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 { String className = (String) handlerOrClassName; try { 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); 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); } } }
private Map<String, Object> getHandlerMappings() { Map<String, Object> handlerMappings = this.handlerMappings; if (handlerMappings == null) { synchronized (this) { handlerMappings = this.handlerMappings; if (handlerMappings == null) { if (logger.isDebugEnabled()) { logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]"); } try { Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); if (logger.isDebugEnabled()) { logger.debug("Loaded NamespaceHandler mappings: " + mappings); } handlerMappings = new ConcurrentHashMap<>(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } catch (IOException ex) { throw new IllegalStateException( "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex); } } } } return handlerMappings; }
private volatile Map<String, Object> handlerMappings;
我们可以看到 handlerMappings其实就是一个成员变成,不过用volatile修饰,保证在多个线程直接数据的可见性。根据上面可以知道判断如果handlerMappings不为空,则直接返回handlerMappings。如果为空,则使用双重检测锁,读取META-INF/spring.handlers文件中所有的键值对,将结果放到handlerMappings中。所以,handlerMappings中的key是:http://www.sue.sc.com/schema/mytag,value是:com.sue.jump.tag.MyTagHandler,根据http://www.sue.sc.com/schema/mytag可以非常轻松的找到MyTagHandler类。
再调用MyTagHandler实例的init方法,注册解析器到解析器集合parsers中。
@Override public void init() { registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); }
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) { this.parsers.put(elementName, parser); }
private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
再往回看一下BeanDefinitionParserDelegate类的parseCustomElement方法
@Nullable public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } 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)); }
接下来的重点看:
handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
进入NamespaceHandlerSupport的parse方法,这里别跟丢了,哈哈哈。
@Override @Nullable public BeanDefinition parse(Element element, ParserContext parserContext) { BeanDefinitionParser parser = findParserForElement(element, parserContext); return (parser != null ? parser.parse(element, parserContext) : null); }
@Nullable private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
先根据元素如:user,从parsers 解析器集合中获取解析器UserBeanDefinitionParser
然后调用 AbstractBeanDefinitionParser类的parse方法
@Override @Nullable public final BeanDefinition parse(Element element, ParserContext parserContext) { AbstractBeanDefinition definition = parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { try { String id = resolveId(element, definition, parserContext); if (!StringUtils.hasText(id)) { parserContext.getReaderContext().error( "Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element); } String[] aliases = null; if (shouldParseNameAsAliases()) { String name = element.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(name)) { aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } } BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); registerBeanDefinition(holder, parserContext.getRegistry()); if (shouldFireEvents()) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } catch (BeanDefinitionStoreException ex) { String msg = ex.getMessage(); parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element); return null; } } return definition; }
紧接着,进入AbstractSingleBeanDefinitionParser类的parseInternal方法
@Override protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = getParentName(element); if (parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } Class<?> beanClass = getBeanClass(element); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { String beanClassName = getBeanClassName(element); if (beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); BeanDefinition containingBd = parserContext.getContainingBeanDefinition(); if (containingBd != null) { // Inner bean definition must receive same scope as containing bean. builder.setScope(containingBd.getScope()); } if (parserContext.isDefaultLazyInit()) { // Default-lazy-init applies to custom bean definitions as well. builder.setLazyInit(true); } doParse(element, parserContext, builder); return builder.getBeanDefinition(); }
这个类里面的doParse方法是空方法,等着子类实现的
protected void doParse(Element element, BeanDefinitionBuilder builder) { }
这不我们的UserBeanDefinitionParser类就是它的子类,重写了doParse方法
@Override public void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String id = element.getAttribute("id"); String name = element.getAttribute("name"); String password = element.getAttribute("password"); String age = element.getAttribute("age"); String sex = element.getAttribute("sex"); builder.addConstructorArgValue(id); builder.addConstructorArgValue(name); builder.addConstructorArgValue(password); builder.addConstructorArgValue(Integer.parseInt(age)); builder.addConstructorArgValue(Byte.valueOf(sex)); }
这里会解析自定义标签中的元素,解析成构造方法的参数,传递给BeanDefinitionBuilder对象,然后 BeanDefinitionBuilder对象会拿着这些参数实例化BeanDefinition对象。我们就可以通过
User user = (User) applicationContext.getBean("sueUser1");
获取到指定的实例了。至此,spring自定义标签的底层实现原理终于揭开了。
end,最后把流程总结一下。
1.获取namespaceUri,根据namespaceUri从handlerMappings找对应的NamespaceHandler
2.如果可以找到NamespaceHandler实体,则直接返回实体。如果找不到,读取META-INF/spring.handlers中的配置,然后通过反射实例化对象。调用NamespaceHandler实体的init方法,将元素:user和值:UserBeanDefinitionParser对象放到parsers集合(map)中。然后将namespaceUri和NamespaceHandler实体存到handlerMappings 中
3.调用NamespaceHandler的parse方法
4.通过元素user找到解析器UserBeanDefinitionParser
5.调用UserBeanDefinitionParser的doParse方法完成解析
6.将解析的数据封装到BeanDefinitionBuilder对象中
7.返回BeanDefinition对象
8.调用applicationContext.getBean("sueUser1")时实例化BeanDefinition,得到User对象。