自定义标签的使用

主要内容都在书上,p80

关键的部分为

  • 创建XSD文件
  • 创建类 继承AbstractSingleBeanDefinitionParser , 其继承关系最主要是实现了BeanDefinitionParser 接口,顾名思义,可以用来解析bean.
  • 创建Handler文件, 继承自 NamespaceHandlerSupport ,目的是将上面的组件注册到Spring容器内
  • 编写Spring.handlers 和 Spring.schemas文件.

这样,自定义的配置就结束了.

Spring加载自定义bean的流程主要就是遇到自定义标签后就去Spring.handlers和Spring.schemas中取找对应的handler 和 XSD .从而可以拿到parser

而代码里的主要步骤为

  1. 拿到标签对应的命名空间
  2. 根据命名空间找到对应的handler
  3. 调用handler的parse方法

获取标签的命名空间

调用org.w3c.dom.Node中的getNamespaceURI()

提取自定义标签处理器

1
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);

这个resolve方法是接口,由DefaultNamespaceHandlerResolver 默认命名空间解析器来实现,其实现里面的第一步便是

1
Map<String, Object> handlerMappings = this.getHandlerMappings();

原来handlerMappings 是个map,想必是在注册时肯定把解析器添加到这个map里面来,用namespaceUri做key,这样找的时候就很好找了.

取出结果如果是NamespaceHandler 便可以返回,往下读我们发现,之所以这么爽取出来就是handler,是因为已经做过这个解析,把找出的handler塞入了map,相当于是缓存了.

否则就是类名 className, 我们需要把它转为handler

1
2
3
4
5
6
7
8
9
10
11
12
13
//使用反射,将类路径转化为类
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");
} else {
//初始化类
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
//调用类自定义的init()方法,这是所有handler都必须实现的方法
namespaceHandler.init();
//塞入缓存
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}

设计模式–单例模式

this.getHandlerMappings(); 获取handlerMapping处使用了单例模式

声明为volatile

1
private volatile Map<String, Object> handlerMappings;

获取单例时使用双重检查锁定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized(this) {
if (this.handlerMappings == null) {
try {
//读取"META-INF/spring.handlers" 转为map
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}

Map<String, Object> handlerMappings = new ConcurrentHashMap(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
} catch (IOException var5) {
throw new IllegalStateException("Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", var5);
}
}
}
}

return this.handlerMappings;
}

上面的volatile非常关键,如果没有的话,在初始化对象设置handlerMapping指向内存空间 中间可能发生重排序,导致另外的线程拿到了handlerMapping的空间地址,但是其实还没有初始化完成.

标签解析

拿到解析器之后,就调用解析的parse方法,返回BeanDefinition

我们的自定义handler里面无需实现parse方法,在父类NamespaceHandlerSupport 中自有实现,

1
2
3
public BeanDefinition parse(Element element, ParserContext parserContext) {
return this.findParserForElement(element, parserContext).parse(element, parserContext);
}

而parse方法主要步骤就是调用自己实现类的parseInternal 方法,parseInternal 方法除了调用我们实现的doParse方法之外,首先会进行一系列的数据准备,包括对beanClass.scope.lazyInit等属性的准备