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