Spring 源码第三弹!EntityResolver 是个什么鬼?(1)

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

现在大家看到的 Spring 配置头部一般都是这样,这就是 Schema 约束:

<?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:context=“http://www.springframework.org/schema/context”

xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd”>

schema 约束对命名空间有着很好的支持,命名空间可以防止命名冲突,schema 中的名称空间和约束文件都是成对出现的。

有了约束,XML 文件中该写什么不该写什么就固定下来了,这样框架才能成功解析出 XML 文件。

但是大家同时也发现了一个新的问题,无论是 DTD 还是 Schema 约束,给出的约束文件地址都是一个在线地址,这就意味着项目启动时必须能够访问到该在线地址,才能加载到约束文件,如果访问在线约束文件失败,那么项目启动也会失败。

为了解决这个问题,框架一般都是将约束文件放在本地的,在本地哪里呢?实际上就在你下载的 jar 包里。以 spring-beans 为例,在下载的 jar 包里有如下两个文件:

spring.handlers 文件内容如下:

http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler

http://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler

http://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

这其实一个映射配置,每一个名称空间对应的处理类在这里进行配置。

spring.schemas 文件内容如下(部分):

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

http://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

可以看到,各种版本以及没有版本号的约束文件,都对应了同一个文件,就是 org/springframework/beans/factory/xml/spring-beans.xsd,打开这个文件目录,我们就可以看到约束文件:

所以我们虽然在 Spring 的 XML 配置中看到的约束文件是一个在线地址,实际上约束文件是从本地 jar 中读取的。

2.两种解析器


EntityResolver 就是用来处理 XML 验证的。我们先来看下 EntityResolver 接口的定义:

public interface EntityResolver {

public abstract InputSource resolveEntity (String publicId,

String systemId)

throws SAXException, IOException;

}

接口中就只有一个方法,就是加载约束文件。在 Spring 中,EntityResolver 的实现类是 DelegatingEntityResolver:

public class DelegatingEntityResolver implements EntityResolver {

public static final String DTD_SUFFIX = “.dtd”;

public static final String XSD_SUFFIX = “.xsd”;

private final EntityResolver dtdResolver;

private final EntityResolver schemaResolver;

public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {

this.dtdResolver = new BeansDtdResolver();

this.schemaResolver = new PluggableSchemaResolver(classLoader);

}

public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) {

this.dtdResolver = dtdResolver;

this.schemaResolver = schemaResolver;

}

@Override

@Nullable

public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)

throws SAXException, IOException {

if (systemId != null) {

if (systemId.endsWith(DTD_SUFFIX)) {

return this.dtdResolver.resolveEntity(publicId, systemId);

}

else if (systemId.endsWith(XSD_SUFFIX)) {

return this.schemaResolver.resolveEntity(publicId, systemId);

}

}

return null;

}

@Override

public String toString() {

return "EntityResolver delegating " + XSD_SUFFIX + " to " + this.schemaResolver +

" and " + DTD_SUFFIX + " to " + this.dtdResolver;

}

}

在 DelegatingEntityResolver 类中:

  1. 首先通过两种不同的后缀来区分不同的约束。

  2. 然后定义了 dtdResolver 和 schemaResolver 两个不同的变量,对应的类型分别是 BeansDtdResolver 和 PluggableSchemaResolver,也就是 dtd 和 schema 的约束验证分别由这两个类来处理。

  3. 在 resolveEntity 方法中,根据解析出来不同的后缀,分别交由不同的 EntityResolver 来处理。resolveEntity 解析中有两个参数,如果是 dtd 解析的话,publicId 是有值的,如果是 schema 解析,publicId 为 null,而 systemId 则始终指向具体的约束文件。

由于现在大部分都是 schema 约束,所以这里我们就来重点看下 PluggableSchemaResolver 类的实现:

public class PluggableSchemaResolver implements EntityResolver {

public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = “META-INF/spring.schemas”;

private static final Log logger = LogFactory.getLog(PluggableSchemaResolver.class);

@Nullable

private final ClassLoader classLoader;

private final String schemaMappingsLocation;

@Nullable

private volatile Map<String, String> schemaMappings;

public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {

this.classLoader = classLoader;

this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;

}

public PluggableSchemaResolver(@Nullable ClassLoader classLoader, String schemaMappingsLocation) {

Assert.hasText(schemaMappingsLocation, “‘schemaMappingsLocation’ must not be empty”);

this.classLoader = classLoader;

this.schemaMappingsLocation = schemaMappingsLocation;

}

@Override

@Nullable

public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {

if (logger.isTraceEnabled()) {

logger.trace(“Trying to resolve XML entity with public id [” + publicId +

“] and system id [” + systemId + “]”);

}

if (systemId != null) {

String resourceLocation = getSchemaMappings().get(systemId);

if (resourceLocation == null && systemId.startsWith(“https:”)) {

resourceLocation = getSchemaMappings().get(“http:” + systemId.substring(6));

}

if (resourceLocation != null) {

Resource resource = new ClassPathResource(resourceLocation, this.classLoader);

try {

InputSource source = new InputSource(resource.getInputStream());

source.setPublicId(publicId);

source.setSystemId(systemId);

if (logger.isTraceEnabled()) {

logger.trace(“Found XML schema [” + systemId + "] in classpath: " + resourceLocation);

}

return source;

}

catch (FileNotFoundException ex) {

if (logger.isDebugEnabled()) {

logger.debug(“Could not find XML schema [” + systemId + "]: " + resource, ex);

}

}

}

}

return null;

}

private Map<String, String> getSchemaMappings() {

Map<String, String> schemaMappings = this.schemaMappings;

if (schemaMappings == null) {

synchronized (this) {

schemaMappings = this.schemaMappings;

if (schemaMappings == null) {

try {

Properties mappings =

PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);

schemaMappings = new ConcurrentHashMap<>(mappings.size());

CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);

this.schemaMappings = schemaMappings;

}

catch (IOException ex) {

throw new IllegalStateException(

“Unable to load schema mappings from location [” + this.schemaMappingsLocation + “]”, ex);

}

}

}

}

return schemaMappings;

}

@Override

public String toString() {

return "EntityResolver using schema mappings " + getSchemaMappings();

}

}

  1. 在这个类中,一上来先通过 DEFAULT_SCHEMA_MAPPINGS_LOCATION 变量定义了 spring.schemas 文件的位置。

  2. getSchemaMappings 方法则是将 spring.schemas 文件中的内容读取成一个 Map 加载进来。

  3. 在 resolveEntity 方法中,根据 systemId 找到文件路径,systemId 是 http\://www.springframework.org/schema/beans/spring-beans.xsd 格式,文件路径则是 org/springframework/beans/factory/xml/spring-beans.xsd,如果第一次没有加载到,就把用户的 https: 替换成 http: 再去加载。

总结

对于面试,一定要有良好的心态,这位小伙伴面试美团的时候没有被前面阿里的面试影响到,发挥也很正常,也就能顺利拿下美团的offer。
小编还整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家,

在这里插入图片描述

在这里插入图片描述

最后感谢大家的支持,希望小编整理的资料能够帮助到大家!也祝愿大家都能够升职加薪!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
g-beans.xsd,如果第一次没有加载到,就把用户的 https:替换成http:` 再去加载。

总结

对于面试,一定要有良好的心态,这位小伙伴面试美团的时候没有被前面阿里的面试影响到,发挥也很正常,也就能顺利拿下美团的offer。
小编还整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家,

[外链图片转存中…(img-J8eF4GO9-1714752936409)]

[外链图片转存中…(img-kOFRnyiE-1714752936410)]

最后感谢大家的支持,希望小编整理的资料能够帮助到大家!也祝愿大家都能够升职加薪!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值