centos6.6做网站,网站源码上传教程,安卓app下载官方正式版,莱芜一中贴吧文章目录
文章目录
一、概要
二、前置知识点-FreeMarker
三、前置知识点-AbstractHttpMessageConverter
3.1 描述
3.2 应用
四、前置知识点-AbstractDecorator
4.1描述
4.2 应用
五、工作空间查询解读
5.1 模板解读
5.2 请求转换器解读 一、概要
关于geoserver的r…文章目录
文章目录
一、概要
二、前置知识点-FreeMarker
三、前置知识点-AbstractHttpMessageConverter
3.1 描述
3.2 应用
四、前置知识点-AbstractDecorator
4.1描述
4.2 应用
五、工作空间查询解读
5.1 模板解读
5.2 请求转换器解读 一、概要
关于geoserver的rest服务其实官网有一个简单的描述此处不多搬运详情可以查看它官网描述点我,但是需要重点了解的是最新的GeoServer是使用SpringMVC来实现的REST服务抛弃了Restlet。GeoServer扩展之REST_geoserver过时了-CSDN博客 从GeoServer2.12版2017开始采用的SpringMVC 它的Wiki中也做了个简单描述,但是开发文档没有更新重要的事情说两遍开发文档没有更新。所以官网描述看看就可以了不用跟着它的指引做。 本文着重从源码角度梳理整个rest服务的流程
二、前置知识点-FreeMarker
在上一篇文章中看到geoserver的模板框架是FreeMarker
主体框架spring不是spring bootUI框架Wicket类似jsp通信框架前后台交互Servlet地理处理框架GeoTools模板框架FreeMarker
这个东西主要就是用于格式化REST接口的 用法的话参照下面的代码AI生成的可能细节上有问题看看即可
1.环境配置
Configuration cfg new Configuration(Configuration.VERSION_2_3_31);
cfg.setDirectoryForTemplateLoading(new File(templates)); // 设置模板目录
cfg.setDefaultEncoding(UTF-8); // 设置默认编码
2.加载模板
Template template cfg.getTemplate(example.ftl);
3.数据模型
MapString, Object dataModel new HashMap();
dataModel.put(title, FreeMarker 示例);
dataModel.put(message, 这是一个 FreeMarker 模板);
4.处理模板
StringWriter out new StringWriter();
template.process(dataModel, out);
String result out.toString();
System.out.println(result);
三、前置知识点-AbstractHttpMessageConverter
AbstractHttpMessageConverter 一般与rest 接口联合使用用于根据前端需求返回不同格式的结果就比如工作空间的三种请求方式
3.1 描述
下面是AI智普清言生成的可能细节上有问题看看即可
AbstractHttpMessageConverter 是 Spring 框架中用于处理 HTTP 请求和响应的转换的一个抽象类。它为具体的 HTTP 消息转换器提供了一种模板方法模式用于将请求体或响应体转换为 Java 对象或者将 Java 对象转换为响应体。
如果你需要自定义一个消息转换器你可以扩展这个类并实现其中的抽象方法。下面是扩展 AbstractHttpMessageConverter 的基本步骤 确定支持的媒体类型在构造函数中设置你的转换器将支持哪些媒体类型例如 application/json, text/xml 等。 实现 supports 方法这个方法需要判断传入的 Java 类型是否为你的转换器所支持的类型。 实现 read 方法这个方法负责将请求体转换为 Java 对象。 实现 write 方法这个方法负责将 Java 对象转换为响应体。
以下是一个简单的示例展示了如何创建一个自定义的 AbstractHttpMessageConverter
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;import java.io.IOException;
import java.nio.charset.Charset;public class CustomMessageConverter extends AbstractHttpMessageConverterMyObject {public CustomMessageConverter() {// 设置支持的媒体类型super(new MediaType(application, custom, Charset.forName(UTF-8)));}Overrideprotected boolean supports(Class? clazz) {// 判断传入的类型是否为 MyObject 或其子类return MyObject.class.isAssignableFrom(clazz);}Overrideprotected MyObject readInternal(Class? extends MyObject clazz, HttpInputMessage inputMessage)throws IOException, HttpMessageNotReadableException {// 实现从请求体到 MyObject 的转换逻辑// ...return new MyObject();}Overrideprotected void writeInternal(MyObject myObject, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException {// 实现从 MyObject 到响应体的转换逻辑// ...}
}在上述代码中MyObject 是你希望转换的目标对象类型。你需要实现 readInternal 和 writeInternal 方法来完成具体的转换逻辑。
最后不要忘记将你的自定义转换器注册到 Spring 的 HttpMessageConverter 列表中这通常是通过配置一个 WebMvcConfigurer 来实现的
Configuration
public class WebConfig implements WebMvcConfigurer {Overridepublic void configureMessageConverters(ListHttpMessageConverter? converters) {converters.add(new CustomMessageConverter());}
}这样当 Spring MVC 处理请求和响应时就会使用你的自定义转换器来处理 MyObject 类型的数据。
博客园里面有一篇文章写的也不错可以参考点我
3.2 应用
在geoserver中设置转换器的配置代码在RestConfiguration中 src/rest/src/main/java/org/geoserver/rest/RestConfiguration.java 在applicationContext.xml中可以看到扫描的是整个包下面的类
?xml version1.0 encodingUTF-8?
beans!-- mvc:annotation-driven/ --context:component-scan base-packageorg.geoserver.rest/
/beans
当扫描到RestConfiguration时就会自动注册消息转换器
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/** Configure various aspects of Spring MVC, in particular message converters */
Configuration
public class RestConfiguration extends WebMvcConfigurationSupport {/** 配置消息转换器 */Overrideprotected void configureMessageConverters(ListHttpMessageConverter? converters) {Catalog catalog (Catalog) applicationContext.getBean(catalog);ListBaseMessageConverter gsConverters GeoServerExtensions.extensions(BaseMessageConverter.class);gsConverters.add(new FreemarkerHTMLMessageConverter(UTF-8));gsConverters.add(new XStreamXMLMessageConverter());gsConverters.add(new XStreamJSONMessageConverter());gsConverters.add(new XStreamCatalogListConverter.XMLXStreamListConverter());gsConverters.add(new XStreamCatalogListConverter.JSONXStreamListConverter());gsConverters.add(new InputStreamConverter());EntityResolver entityResolver catalog.getResourcePool().getEntityResolver();for (StyleHandler sh : Styles.handlers()) {for (Version ver : sh.getVersions()) {gsConverters.add(new StyleReaderConverter(sh.mimeType(ver), ver, sh, entityResolver));gsConverters.add(new StyleWriterConverter(sh.mimeType(ver), ver, sh));}}if (applicationContext.containsBean(gwcConverter)) {converters.add((HttpMessageConverter?) applicationContext.getBean(gwcConverter));}gsConverters.sort(Comparator.comparingInt(BaseMessageConverter::getPriority));for (BaseMessageConverter converter : gsConverters) {converters.add(converter);}converters.removeIf(Jaxb2RootElementHttpMessageConverter.class::isInstance);converters.add(0, new Jaxb2RootElementHttpMessageConverter());super.addDefaultHttpMessageConverters(converters);}
}
上面的一对转换器都是针对geoserver一些特定对象的封装像workspace、layer、datastore等最下面那个比较特殊也是比较常见的一个它用于将java对象转换成json或者xml返回给前端
converters.add(0, new Jaxb2RootElementHttpMessageConverter());
比如果当请求工作空间时一般有下面的几种请求
http://localhost:8080/geoserver/rest/workspaces (浏览器预览居多)
或
http://localhost:8080/geoserver/rest/workspaces.json (作为前端调用的接口居多)
或
http://localhost:8080/geoserver/rest/workspaces.xml (作为前端调用的接口居多)
Jaxb2RootElementHttpMessageConverter 转换器会根据前端请求的Accept请求头自动适配出前端需要的格式
其优先级是 格式拼接到请求地址上http://localhost:8080/gisserver/rest/workspaces.json大于 请求地址什么都不加 但是header有Accept参数 四、前置知识点-AbstractDecorator
4.1描述 org.geotools.util.decorate.AbstractDecorator 是 GeoTools 库中的一个类它提供了一个基础实现用于创建装饰者模式Decorator Pattern的装饰器。装饰者模式允许你动态地给一个对象添加额外的职责而不需要修改其原有的代码。通俗来说就是子类定义一个delegate变量在子类方法中直接代用父类的方法并且这个变量一般是通过依赖注入的不用单独的给赋值。
在 GeoTools 中AbstractDecorator 类是一个抽象类它实现了 Decorator 接口并提供了一个构造函数接受一个要装饰的对象作为参数。这个被装饰的对象通常是一个接口的实现而 AbstractDecorator 类则负责将所有的调用委派给这个对象。
举例
import org.geotools.util.decorate.AbstractDecorator;public class MyDecorator extends AbstractDecoratorMyInterface {public MyDecorator(MyInterface delegate) {super(delegate);}Overridepublic void doSomething() {// 在调用原有方法之前可以添加一些额外的逻辑System.out.println(Before doing something);// 调用被装饰对象的方法delegate.doSomething();// 在调用原有方法之后也可以添加一些额外的逻辑System.out.println(After doing something);}
}public interface MyInterface {void doSomething();
}public class MyImplementation implements MyInterface {Overridepublic void doSomething() {System.out.println(Doing something);}
}public class Main {public static void main(String[] args) {MyInterface myImplementation new MyImplementation();MyDecorator myDecorator new MyDecorator(myImplementation);myDecorator.doSomething();}
}4.2 应用
说AbstractDecorator 的目的主要是为了理解WorkspaceController类
从源码中可以看到
public class WorkspaceController extends AbstractCatalogController {private static final Logger LOGGER Logging.getLogger(WorkspaceController.class);Autowiredpublic WorkspaceController(Qualifier(catalog) Catalog catalog) {super(catalog);}
}
扩展的说一下Autowired是个依赖注入
构造函数中有一个catalog但是WorkspaceController是个servlet接口没有实例化的地方构造函数怎么能够传过来呢查看applicationContext.xml可以看到
alias namelocalWorkspaceCatalog aliascatalog/
bean idlocalWorkspaceCatalog classorg.geoserver.catalog.impl.LocalWorkspaceCatalogconstructor-arg refadvertisedCatalog /
/beanbean idadvertisedCatalog classorg.geoserver.catalog.impl.AdvertisedCatalogconstructor-arg refsecureCatalog /property namelayerGroupVisibilityPolicybean idorg.geoserver.catalog.LayerGroupVisibilityPolicy.HIDE_NEVER classorg.springframework.beans.factory.config.FieldRetrievingFactoryBean//property
/bean
catalog就这通过applicationContext.xml的配置实现初始情况下就可以构造函数注入进去
再继续看LocalWorkspaceCatalog 跟踪源码可以看到下面的代码
public class LocalWorkspaceCatalog extends AbstractCatalogDecorator implements Catalog {}public class AbstractCatalogDecorator extends AbstractDecoratorCatalog implements Catalog {public AbstractCatalogDecorator(Catalog catalog) {super(catalog);}
}// 反编译的AbstractDecorator
public class AbstractDecoratorD implements Wrapper, Serializable {protected D delegate;public AbstractDecorator(D delegate) {if (delegate null) {throw new NullPointerException(Cannot delegate to a null object);} else {this.delegate delegate;}}
}
通过一步步的查看父对象可以看到最终继承自 org.geotools.util.decorate.AbstractDecorator 也就是说可以直接用delegate去操作父类的一些操作
五、工作空间查询解读
一般来说工作空间的查询地址是
http://localhost:8080/geoserver/rest/workspaces (浏览器预览居多)
或
http://localhost:8080/geoserver/rest/workspaces.json (作为前端调用的接口居多)
或
http://localhost:8080/geoserver/rest/workspaces.xml (作为前端调用的接口居多)
当浏览器访问http://localhost:8080/geoserver/rest/workspaces的servlet代码位置在如下位置✈ 引申的说一下geoserver的rest代码大多在 gs-restconfig 包下面 src/restconfig/src/main/java/org/geoserver/rest/catalog/WorkspaceController.java GetMappingpublic RestWrapper workspacesGet() {ListWorkspaceInfo wkspaces catalog.getWorkspaces();return wrapList(wkspaces, WorkspaceInfo.class);}
从GetMapping 能看出来它是个普通的spring servlet接口RestWrapper是对返回结果的一个包装器catalog是针对geoserver文件目录映射出来的一个方法类
查询结果是这样的 如果不用包装器的话返回结果是这样的 GetMapping(/details)public ListWorkspaceInfo getAllWorkspacesDetails() {ListWorkspaceInfo workspaces catalog.getWorkspaces();return workspaces;} 可以看出来如果不用包装器的话会把查出的数据原封不动的返回出来而且兼容xml和json实际不管使用不使用包装器时上面 三、前置知识点-AbstractHttpMessageConverter 讲到Jaxb2RootElementHttpMessageConverter 转换器都会生效也就是说一直支持xml个json请求而当使用包装器时就用到了另一个模板框架二、前置知识点-FreeMarker
5.1 模板解读
往下看wrapList源码 protected T RestWrapperT wrapList(CollectionT list, ClassT clazz) {return new RestListWrapper(list, clazz, this, getTemplate(list, clazz));}
这里面终于找到了一个跟模板相关的东西getTemplate(list, clazz)
在WorkspaceController的基类RestBaseController中找到下面获取模板的代码 protected Template getTemplate(Object o, Class? clazz) {Template template null;Configuration configuration createConfiguration(clazz);。。。。。。此处省略n行代码return tryLoadTemplate(configuration, templateName);}
里面的代码看着没啥营养我替你们看过了跟着代码就能找到模板的位置也就是这个地方
src/restconfig/src/main/java/org/geoserver/rest/catalog/ftl-templates/workspaces.ftl
#include head.ftl
Workspaces
ul
#list values as wlia href${page.pageURI(w.properties.name .html)}${w.properties.name}/a#if w.properties.isDefault [default] 哈哈 /#if/li
/#list
/ul
#include tail.ftl
最后那两个“哈哈”是我自己加的浏览器访问可以看到下面效果 如果你那儿是乱码的可以在头部模板里面加个meta charsetUTF-8 / src/restconfig/src/main/java/org/geoserver/rest/catalog/ftl-templates/head.ftl !DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//ENhttp://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
html xmlnshttp://www.w3.org/1999/xhtml xml:langen langen
headtitleGeoServer Configuration/titlemeta charsetUTF-8 /meta nameROBOTS contentNOINDEX, NOFOLLOW/
/head
body#setting number_format#0.0#
但是访问的时候我还有一个疑问直接访问是用的模板但当我访问json的接口时儿返回结果貌似没有走这个模板 这是因为什么呢 再返回去查getTemplate方法原因是默认是根据模板名查询模板的也就是说根据workspace就能查到模板换成workspace.json 就不行如果想要用json类型的模板就得再定义个workspace.json.flt文件
if (template null) template tryLoadTemplate(configuration, templateName .ftl);
总的来说如果不加干预的话直接请求
http://localhost:8080/geoserver/rest/workspaces
就会使用FreeMarker模板然后经过转换器此处是Jaxb2RootElementHttpMessageConverter 、FreemarkerHTMLMessageConverter、XStreamXMLMessageConverter、XStreamJSONMessageConverter。。。传给前端如果是访问
http://localhost:8080/geoserver/rest/workspaces.json
的话则会跳过模板直接经过转换器此处是Jaxb2RootElementHttpMessageConverter 然后传给前端
到这里FreeMarker的框架算是基本上梳理完了 感觉就像是个放大版的StringBuilder。
5.2 请求转换器解读
看了前面描述的三、前置知识点-AbstractHttpMessageConverter 可以知道在查询完之后会执行一次查询结果的转换操作
再次看查询工作空间的代码 GetMappingpublic RestWrapper workspacesGet() {ListWorkspaceInfo wkspaces catalog.getWorkspaces();return wrapList(wkspaces, WorkspaceInfo.class);} 下钻查看wrapList的代码如下 protected T RestWrapperT wrapList(CollectionT list, ClassT clazz) {return new RestListWrapper(list, clazz, this, getTemplate(list, clazz));} 继续下钻查看RestListWrapper以及它的基类RestWrapperAdapter public void configurePersister(XStreamPersister persister, XStreamMessageConverter converter) {controller.configurePersister(persister, converter);}
从这里能看出来包装器有个关于转换器的配置的方法而且类型是XStreamMessageConverter converter继续跟踪代码查找下它是在哪里被调用的 这里看到有几个继承类,但是只有里面的类型和RestListWrapper是一样的
public abstract class XStreamCatalogListConverterextends XStreamMessageConverterRestListWrapper?
根据spring mvc的自动根据参数类型适配的原则它用的转换器就是XStreamCatalogListConverter而且从注释中也能看出来
/*** A wrapper for all Collection type responses using the {link XStreamCatalogListConverter} (XML* and JSON output). Also supports Collection type responses using the {link* FreemarkerHTMLMessageConverter}, but is not required for such responses.** pIn the previous rest API this wasnt needed because in each individual rest request the* Collections were aliased to*/
在XStreamCatalogListConverter.java中能够看到具体的转换方法
protected void configureXStream(XStream xstream, Class? clazz, RestListWrapper? wrapper) {XStreamPersister xp xpf.createXMLPersister();wrapper.configurePersister(xp, this);final String name getItemName(xp, clazz);xstream.alias(name, clazz);xstream.registerConverter(new CollectionConverter(xstream.getMapper()) {Overridepublic boolean canConvert(SuppressWarnings(rawtypes) Class type) {return Collection.class.isAssignableFrom(type);}Overrideprotected void writeCompleteItem(Object item,MarshallingContext context,HierarchicalStreamWriter writer) {writer.startNode(name);context.convertAnother(item);writer.endNode();}});xstream.registerConverter(new Converter() {Overridepublic boolean canConvert(Class type) {return clazz.isAssignableFrom(type);}Overridepublic void marshal(Object source,HierarchicalStreamWriter writer,MarshallingContext context) {String ref;// Special case for layer list, to handle the non-workspace-specific// endpoint for layersif (clazz.equals(LayerInfo.class) OwsUtils.getter(clazz, prefixedName, String.class) ! null RequestInfo.get() ! null !RequestInfo.get().getPagePath().contains(/workspaces/)) {ref (String) OwsUtils.get(source, prefixedName);} else if (OwsUtils.getter(clazz, name, String.class) ! null) {ref (String) OwsUtils.get(source, name);} else if (OwsUtils.getter(clazz, id, String.class) ! null) {ref (String) OwsUtils.get(source, id);} else if (OwsUtils.getter(clazz, id, Long.class) ! null) {// For some reason Importer objects have Long ids so this catches that// caseref OwsUtils.get(source, id).toString();} else {throw new RuntimeException(Could not determine identifier for: clazz.getName());}writer.startNode(wrapper.getItemAttributeName());writer.setValue(ref);writer.endNode();encodeLink(encode(ref), writer);}Overridepublic Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {return null;}});}
我修改了上述代码中的
writer.startNode(wrapper.getItemAttributeName() test); 然后再次请求接口就能看到修改后的数据 写在最后文章难免有写的不对或者不完善的地方欢迎提出纠正意见