8-DefaultNamespaceHandlerResolve
2020-02-18 本文已影响0人
鹏程1995
背景简介
出现的原因
我们上面介绍了NamespaceHandlerResolver
的接口定义,这里重点关注一下它的实现类。当然,只有一个默认实现类。我们正常使用的情况下,基本也就是这个类来为我们服务。
职责
- 在初始化时扫描对应的配置文件,记录解析器和命名空间的对应关系。
- 实现
NamespaceHandlerResolver
定义的功能,提供"根据命名空间获得解析器"的功能。
注意点
注意了解一下看怎么初始化的,在调用时有没有用什么钩子,都怎么搞的。方便后面使用。
源码
变量、常量介绍
/**
* 默认的用来加载 解析器 配置信息的文件地址
*/
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
protected final Log logger = LogFactory.getLog(getClass());
/**
* 用来加载 NameSpaceHandler 解析器的类加载器
*/
@Nullable
private final ClassLoader classLoader;
/**
* 记录配置的用来加载 解析器 配置信息的文件地址【如果没有配置,就默认设置成上面的默认值】
*/
private final String handlerMappingsLocation;
/**
* 保存加载的映射关系
*
* key 为命名空间
*
* value 为从配置文件读出的 String 【NameSpaceHandler的类名】或者通过类加载器加载实例化初始完成的
* NameSpaceHandler的实例
*/
@Nullable
private volatile Map<String, Object> handlerMappings;
加载配置信息
设置配置文件地址
/**
* Create a new {@code DefaultNamespaceHandlerResolver} using the
* default mapping file location.
* <p>This constructor will result in the thread context ClassLoader being used
* to load resources.
*
* @see #DEFAULT_HANDLER_MAPPINGS_LOCATION
*/
public DefaultNamespaceHandlerResolver() {
this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
/**
* Create a new {@code DefaultNamespaceHandlerResolver} using the
* default mapping file location.
*
* @param classLoader the {@link ClassLoader} instance used to load mapping resources
* (may be {@code null}, in which case the thread context ClassLoader will be used)
* @see #DEFAULT_HANDLER_MAPPINGS_LOCATION
*/
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
/**
* Create a new {@code DefaultNamespaceHandlerResolver} using the
* supplied mapping file location.
*
* @param classLoader the {@link ClassLoader} instance used to load mapping resources
* may be {@code null}, in which case the thread context ClassLoader will be used)
* @param handlerMappingsLocation the mapping file 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;
}
很简单,没得说。
加载配置
/**
* 懒加载,使用隐式锁保证不会重复加载
*/
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 {
// 使用 Core 的 util 进行加载
// 加载出的 Properties 数据结构都是线程安全的,没必要
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
// 这里使用线程安全的集合,防止后面将 value 的 String 转成 NameSpaceResolver
// 时出现同步问题
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;
}
根据配置信息返回指定解析器
/**
* @param namespaceUri the relevant namespace URI
* @return the located {@link NamespaceHandler}, or {@code null} if none found
*/
@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);
// 类没实现 NamespaceHandler 直接报错
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 调用对应的 util ,创建实例【就是反射那一套嘛】
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 调用初始化钩子
namespaceHandler.init();
// 各种工作做完了,存到 Map 里【等等,这么玩的话,Map 你用线程安全也没毛用啊】
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);
}
}
}
问题遗留
- 我们发现
NamespaceHandler
貌似没有什么属性注入,直接反射实例化即可,这很棒,大大降低了我们阅读、了解的成本。 -
NamespaceHandler
是有初始化钩子的,后面阅读时注意看一下。
思考
我们在根据命名空间获得解析器时,并没有对关键操作加锁,尤其是涉及类加载、实例化、初始化时。我们上面加载配置时使用的ConcurrentHashMap
完全没有作用,仅仅拖慢了读写效率。
此处可以优化。