赞
踩
GeoServer源码解读 - 解析kvp和xml格式参数。(GeoServer版本 2.21)
最近遇到一些需求,所以读了下GeoServer源码,因为源码相关体系比较庞大,所以想着就一些细节进行归纳,整理下体系脉络,以备不时之需。
不出意外的话,这将会一个系列文章,主题是围绕GeoServer后端服务的主体处理逻辑进行源码研读。
正如标题所言,本文将主要关注GeoServer在主体处理流程中,对于接收参数的处理。(至于整体处理逻辑快速浏览,推荐看一下这篇2011年的博客GeoServer源码解析和扩展 (一)基础篇)
作为GeoServer主体逻辑流程核心类的Dispatcher
,其中与parse相关的方法如下:
再结合GeoServer对于参数的接收是基于HTTP协议这一条脉络,因此其参数接收分为如下两类:
HTTP-METHOD | HTTP-CONTENT TYPE | 入参数据格式 | 关键处理类 | 隶属 |
---|---|---|---|---|
GET | x-www-form-urlencoded | kvp | KvpParser / KvpRequestReader | GeoServer自建 |
POST | XML request body | xml | XmlRequestReader | 基于GeoTools |
流程图版本参见: GeoServer2.21 - 入参处理
在GeoServer业务逻辑处理的主体链条Dispatcher.handleRequest(request, response)
中,处理kvp格式数据的关键方法大约有如下几个:
KvpUtils.parse(Map<String, Object> kvp)
。涉及关键接口KvpParser
。Dispatcher.parseRequestKVP(Class<?> type, Request request)
。涉及关键接口KvpRequestReader
。KvpParser
KvpParser
实际表现是一个抽象类,用于封装一些公用逻辑。按照官方解释,该接口主要负责将 key-value pair 转换为 key-object pair 。
Object parse(String value)
。子类需要实现各自对应的自定义解析逻辑。FilterKvpParser
所声明的"filter",第二个参数指示的则是调用核心方法为parse(String value)
之后的返回值类型。关于这一点典型例子可以参见FilterKvpParser
,其所对应的入参为FILTER=(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="states.3"/></Filter>)
。KvpParser
实例中挑出与之最为匹配的,决定条件之一是前端传递来的key-value pair中key的值。这一点和下面马上要讲到的KvpRequestReader
是完全不同的。该接口拥有着数量众多的子类:
以上截图所展示的只是KvpParser
子类中的一部分。GeoServer在接收到前端传递来的kvp格式参数后,将从注册到Spring容器中的诸多KvpParser
实例中挑出与之最为匹配的,相关挑选规则在KvpUtils.findParser(final String key, final String service, final String request, final String version, Collection<KvpParser> parsers)
中可以找到:
public static KvpParser findParser( final String key, final String service, final String request, final String version, Collection<KvpParser> parsers) { // find the parser for this key value pair KvpParser parser = null; final Iterator<KvpParser> pitr = parsers.iterator(); // 本次循环中没有break关键字,所以肯定是整体迭代一次, 以期找出最合适的Parser while (pitr.hasNext()) { KvpParser candidate = pitr.next(); // 1. 比较key // key: 前端传参时的key-value pair中的key // candidate.getKey() : 构建KvpParser实例的构造函数参数key // 只有以上两者向匹配, 即key相同, 才会进入备选列表 if (key.equalsIgnoreCase(candidate.getKey())) { if (parser == null) { parser = candidate; } else { // 2. 比较service // if target service matches, it is a closer match String trgService = candidate.getService(); if (trgService != null && trgService.equalsIgnoreCase(service)) { // determine if this parser more closely matches the request String curService = parser.getService(); if (curService == null) { parser = candidate; } else { // 3. 比较version // both match, filter by version Version curVersion = parser.getVersion(); Version trgVersion = candidate.getVersion(); if (trgVersion != null) { if (curVersion == null && trgVersion.toString().equals(version)) { parser = candidate; } } else { if (curVersion == null) { // ambiguous, unable to match throw new IllegalStateException( "Multiple kvp parsers: " + parser + "," + candidate); } } } } } } } return parser; }
所以只有 key, service, version全部匹配,才是真正处理当前key-value Item的。
入参在经过一组对应的KvpParser
处理之后,将成为相应的Map<String, Object>
实例,传递给之后的逻辑。所以KvpParser
只是参数处理的第一阶段。
KvpRequestReader
KvpRequestReader
实际表现是一个抽象类,封装一些公用逻辑。按照官方解释,该接口主要负责将 a kvp set 转换为 a request bean。
很明显,该接口的执行是在前面的KvpParser
生效之后。
核心方法read(Object request, Map<String, Object> kvp, Map<String, Object> rawKvp)
,其中第二个方法参数Map<String, Object> kvp
正是KvpParser
处理后的结果。
关于GeoServer从注册到Spring容器中的诸多KvpRequestReader
实例中挑出与之最为匹配的,完全取决于后端方法定义中的方法参数声明,典型例子是DefaultWebFeatureService20.getCapabilities(GetCapabilitiesType request)
。(注意这一点是和上面的KvpParser
挑选逻辑是不一样的)。
// Dispatcher.java public static KvpRequestReader findKvpRequestReader(Class<?> type) { // 从Spring容器中抓取注册过的KvpRequestReader实现类 Collection<KvpRequestReader> kvpReaders = loadKvpRequestReaders(); List<KvpRequestReader> matches = new ArrayList<>(); for (KvpRequestReader kvpReader : kvpReaders) { // 这个 kvpReader.getRequestBean() 返回的值, 是由构造函数参数参入的, 这样保证了约束性, 范例参见 org.geoserver.wfs.kvp.GetCapabilitiesKvpRequestReader if (kvpReader.getRequestBean().isAssignableFrom(type)) { matches.add(kvpReader); } } if (matches.isEmpty()) { return null; } if (matches.size() > 1) { // sort by class hierarchy Comparator<KvpRequestReader> comparator = (kvp1, kvp2) -> { if (kvp2.getRequestBean().isAssignableFrom(kvp1.getRequestBean())) { return -1; } return 1; }; Collections.sort(matches, comparator); } // 择优录取 return matches.get(0); }
以下范例参数来自GeoServer官方Demo。
// ============ 入参
{GeoServer-RootUrl}?request=GetFeature&version=1.1.0&typeName=topp:states,tiger:tiger_roads&outputFormat=GML2&FILTER=(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="states.3"/></Filter>)(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="tiger_roads.3"/></Filter>)'
// ============ typeName=topp:states,tiger:tiger_roads
1. 负责处理这段的是 TypeNameKvpParser 类。
2. 转换出来的对象类型: QName 。
// ============ FILTER=(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="states.3"/></Filter>)(<Filter xmlns="http://www.opengis.net/ogc"><FeatureId fid="tiger_roads.3"/></Filter>)
1. 负责处理这段的是 Filter_1_1_0_KvpParser 类。(其中对于parse方法的实现借用了下面的xml解析)
2. 转换出来的对象类型为: FeatureIdImpl + FidFilterImpl 。
处理xml格式数据的关键方法大约有如下几个:
Dispatcher.readOpPost(Request req)
。解析必要的基础参数,例如xml相关的namespace,通用参数service,version,outputFormat等等。Dispatcher.parseRequestXML(Object requestBean, BufferedReader input, Request request)
。进行实际的 xml 到 request bean的转换。XmlRequestReader
XmlRequestReader
实际表现是一个抽象类,用于封装一些公用逻辑。按照官方解释,该接口主要负责将 xml 转换为a request bean 。
read(Object request, Reader reader, Map kvp)
。子类需要实现各自对应的自定义解析逻辑。XmlRequestReader
实例中挑出与之最为匹配的,只有 namespace(例如"http://www.opengis.net/wfs"), element(例如"GetFeature"), serviceId(例如"WFS"),version(例如"1.1.0")全部匹配,然后在此基础上进行择优录取。相关实现源码参见:Dispatcher.findXmlReader(String namespace, String element, String serviceId, String ver)
以下范例参数来自GeoServer官方Demo。
<!--对应:net.opengis.wfs.impl.GetFeatureTypeImpl 类,, org.geoserver.wfs.xml.v2_0.GetFeatureTypeBinding 类来处理 ...--> <wfs:GetFeature service="WFS" version="1.1.0" > <!--对应:net.opengis.wfs.impl.QueryTypeImpl类,, 使用 QueryTypeBinding.parse(ElementInstance instance, Node node, Object value) 进行解析的...--> <wfs:Query typeName="topp:states"> <wfs:PropertyName>topp:STATE_NAME</wfs:PropertyName> <!-- org.geotools.filter.AttributeExpressionImpl , 使用 org.geoserver.wfs.xml.filter.v1_1.PropertyNameTypeBinding 进行解析 --> <wfs:PropertyName>topp:LAND_KM</wfs:PropertyName> <wfs:PropertyName>topp:the_geom</wfs:PropertyName> <!-- 处理堆栈图见下方 ; -对应:org.geotools.filter.IsBetweenImpl本身就是继承自Filter接口 ,, 使用 org.geoserver.wfs.xml.filter.v1_1.FilterTypeBinding.parse(ElementInstance instance, Node node, Object value) 进行解析的 --> <ogc:Filter> <!-- org.geotools.filter.IsBetweenImpl , 使用 OGCPropertyIsBetweenTypeBinding.parse(ElementInstance instance, Node node, Object value) 进行解析 --> <ogc:PropertyIsBetween> <ogc:PropertyName>topp:LAND_KM</ogc:PropertyName> <!-- 对应的binding为: OGCLowerBoundaryTypeBinding, 解析出来的其实还是里面的 org.geotools.filter.LiteralExpressionImpl --> <ogc:LowerBoundary> <!-- 匹配: org.geotools.filter.LiteralExpressionImpl --> <ogc:Literal>100000</ogc:Literal> </ogc:LowerBoundary> <!-- 对应的binding为: OGCUpperBoundaryTypeBinding --> <ogc:UpperBoundary> <ogc:Literal>150000</ogc:Literal> </ogc:UpperBoundary> </ogc:PropertyIsBetween> </ogc:Filter> </wfs:Query> </wfs:GetFeature>
相较于kvp,GeoServer中针对xml处理其实是在GeoTools基础上进行的些许扩展,主体逻辑和思路其实是在GeoTools中完成的,限于篇幅以及本文中心,这里就不再累述,感兴趣的读者可以通过本地底部链接查看下GeoTools对XML处理的相关考量和相应解决方案。笔者之后也会整理一份相关的博客。
DispatcherCallback
接口这个接口并不完全属于本小节内容,但因为我们可以借助它实现某些参数解析方面的扩展,因此这里作一个关联性介绍。
GeoServer在启动时会加载注册到Spring容器中的DispatcherCallback
接口实现类,然后在实际进行请求处理时候,在不同的阶段回调不同的DispatcherCallback
接口方法。详细的对应关系参见: GeoServer2.21 - DispatcherCallback接口回调契机
补充:
AbstractDispatcherCallback
抽象类。KvpParser
和KvpRequestReader
进行读取转换。XmlRequestReader
进行读取转换。这里需要注意的是可以看出GeoServer在命名上进行了非常刻意地设计,例如KvpRequestReader
和XmlRequestReader
接口命名,以及Dispatcher
类中的parseRequestKVP
和parseRequestXML
方法命名,都属于一眼就能看出其作用,并且能够猜出对应另外一个方法名。KvpReader
KvpParser
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。