spring的自定义标签都不会?你可能只学到了spring的皮毛

时间:2022-07-26
本文章向大家介绍spring的自定义标签都不会?你可能只学到了spring的皮毛,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

通常我们在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对象。