Geoserver源码解读四 REST服务

系列文章目录

Geoserver源码解读一 环境搭建

Geoserver源码解读二 主入口

Geoserver源码解读三 GeoServerBasePage

Geoserver源码解读四 REST服务

Geoserver源码解读五 Catalog


目录

系列文章目录

一、概要

二、前置知识点

1.模板框架(FreeMarker)

2.处理 HTTP 请求和响应的转换(AbstractHttpMessageConverter)

2.1 描述

2.2 应用

3.装饰者模式(AbstractDecorator)

3.1描述

3.2 应用

4.代理模式(java.lang.reflect.Proxy)

4.1 描述

4.2 应用

三、工作空间查询解读

1 模板在哪

2 从哪个看出来模板FreeMarker用的哪个转换器

四、扩展工作空间查询


一、概要

关于geoserver的rest服务,其实官网有一个简单的描述,此处不多搬运详情可以查看它官网描述(点我),但是需要重点了解的是最新的GeoServer是使用SpringMVC来实现的REST服务,抛弃了Restlet。GeoServer扩展之REST_geoserver过时了-CSDN博客 从GeoServer2.12版(2017)开始采用的SpringMVC, 它的Wiki中也做了个简单描述,但是开发文档没有更新,重要的事情说两遍开发文档没有更新。所以官网描述看看就可以了,不用跟着它的指引做。 本文着重从源码角度梳理整个rest服务的流程,最后举例扩展了下工作空间查询的一个接口。

二、前置知识点

1.模板框架(FreeMarker)

在上一篇文章中看到geoserver的模板框架是FreeMarker

主体框架spring(不是spring boot)
UI框架Wicket(类似jsp)
通信框架(前后台交互)Servlet
地理处理框架GeoTools
模板框架FreeMarker

这个东西在REST请求中的作用主要就是用于格式化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.数据模型

Map<String, 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);

2.处理 HTTP 请求和响应的转换(AbstractHttpMessageConverter)

AbstractHttpMessageConverter 一般与rest 接口联合使用,用于根据前端需求返回不同格式的结果,就比如工作空间的三种请求方式,就分别用了三种不同的转换器

http://localhost:8080/geoserver/rest/workspaces   (浏览器预览居多)
或
http://localhost:8080/geoserver/rest/workspaces.json (作为前端调用的接口居多)
或
http://localhost:8080/geoserver/rest/workspaces.xml  (作为前端调用的接口居多)
2.1 描述

下面是AI(智普清言)生成的,可能细节上有问题,看看即可

AbstractHttpMessageConverter 是 Spring 框架中用于处理 HTTP 请求和响应的转换的一个抽象类。它为具体的 HTTP 消息转换器提供了一种模板方法模式,用于将请求体或响应体转换为 Java 对象,或者将 Java 对象转换为响应体。

如果你需要自定义一个消息转换器,你可以扩展这个类,并实现其中的抽象方法。下面是扩展 AbstractHttpMessageConverter 的基本步骤:

  1. 确定支持的媒体类型:在构造函数中设置你的转换器将支持哪些媒体类型(例如 application/jsontext/xml 等)。

  2. 实现 supports 方法:这个方法需要判断传入的 Java 类型是否为你的转换器所支持的类型。

  3. 实现 read 方法:这个方法负责将请求体转换为 Java 对象。

  4. 实现 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 AbstractHttpMessageConverter<MyObject> {

    public CustomMessageConverter() {
        // 设置支持的媒体类型
        super(new MediaType("application", "custom", Charset.forName("UTF-8")));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        // 判断传入的类型是否为 MyObject 或其子类
        return MyObject.class.isAssignableFrom(clazz);
    }

    @Override
    protected MyObject readInternal(Class<? extends MyObject> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        // 实现从请求体到 MyObject 的转换逻辑
        // ...
        return new MyObject();
    }

    @Override
    protected void writeInternal(MyObject myObject, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        // 实现从 MyObject 到响应体的转换逻辑
        // ...
    }
}

在上述代码中,MyObject 是你希望转换的目标对象类型。你需要实现 readInternal 和 writeInternal 方法来完成具体的转换逻辑。

最后,不要忘记将你的自定义转换器注册到 Spring 的 HttpMessageConverter 列表中,这通常是通过配置一个 WebMvcConfigurer 来实现的:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new CustomMessageConverter());
    }
}

这样,当 Spring MVC 处理请求和响应时,就会使用你的自定义转换器来处理 MyObject 类型的数据。

博客园里面有一篇文章写的也不错可以参考(点我

2.2 应用

在geoserver中,设置转换器的配置代码在RestConfiguration

src/rest/src/main/java/org/geoserver/rest/RestConfiguration.java

在applicationContext.xml中可以看到扫描的是整个包下面的类

<?xml version="1.0" encoding="UTF-8"?>
<beans>
  <!-- <mvc:annotation-driven/> -->
  <context:component-scan base-package="org.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 {

    /** 配置消息转换器 */
    @Override
    protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Catalog catalog = (Catalog) applicationContext.getBean("catalog");

        List<BaseMessageConverter> 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参数

3.装饰者模式(AbstractDecorator)

3.1描述

 org.geotools.util.decorate.AbstractDecorator 是 GeoTools 库中的一个类,它提供了一个基础实现,用于创建装饰者模式(Decorator Pattern)的装饰器。装饰者模式允许你动态地给一个对象添加额外的职责,而不需要修改其原有的代码。通俗来说就是子类定义一个delegate变量,在子类方法中直接调用父类的方法,并且这个变量一般是通过依赖注入的,不用单独的给赋值。

在 GeoTools 中,AbstractDecorator 类是一个抽象类,它实现了 Decorator 接口,并提供了一个构造函数,接受一个要装饰的对象作为参数。这个被装饰的对象通常是一个接口的实现,而 AbstractDecorator 类则负责将所有的调用委派给这个对象。

代码举例应该就能看明白了:

import org.geotools.util.decorate.AbstractDecorator;

public class MyDecorator extends AbstractDecorator<MyInterface> {

    public MyDecorator(MyInterface delegate) {
        super(delegate);
    }

    @Override
    public 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 {
    @Override
    public 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();
    }
}
3.2 应用

AbstractDecorator 的目的主要是为了理解WorkspaceController类

从源码中可以看到

public class WorkspaceController extends AbstractCatalogController {

    private static final Logger LOGGER = Logging.getLogger(WorkspaceController.class);

    @Autowired
    public WorkspaceController(@Qualifier("catalog") Catalog catalog) {
        super(catalog);
    }
}

扩展的说一下@Autowired是个依赖注入,

构造函数中有一个catalog,但是WorkspaceController是个servlet接口,没有实例化的地方,构造函数怎么能够传过来呢,查看applicationContext.xml可以看到

<alias name="localWorkspaceCatalog" alias="catalog"/>     
<bean id="localWorkspaceCatalog" class="org.geoserver.catalog.impl.LocalWorkspaceCatalog">
    <constructor-arg ref="advertisedCatalog" />
</bean>
   
<bean id="advertisedCatalog" class="org.geoserver.catalog.impl.AdvertisedCatalog">
    <constructor-arg ref="secureCatalog" />
    <property name="layerGroupVisibilityPolicy">
       <bean id="org.geoserver.catalog.LayerGroupVisibilityPolicy.HIDE_NEVER" 
        		class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
     </property>
</bean>   

catalog就这通过applicationContext.xml的配置实现初始情况下就可以构造函数注入进去

再继续看LocalWorkspaceCatalog ,跟踪源码可以看到下面的代码

public class LocalWorkspaceCatalog extends AbstractCatalogDecorator implements Catalog {

}

public class AbstractCatalogDecorator extends AbstractDecorator<Catalog> implements Catalog {

    public AbstractCatalogDecorator(Catalog catalog) {
        super(catalog);
    }
}

// 反编译的AbstractDecorator 
public class AbstractDecorator<D> 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去操作父类的一些操作

4.代理模式(java.lang.reflect.Proxy)

4.1 描述

java.lang.reflect.InvocationHandler 是Java反射API的一部分,它用于动态地处理对象上的方法调用。这个接口通常与Proxy类一起使用,以创建动态代理对象。动态代理允许你在运行时创建一个实现了一组给定接口的代理类,并且可以在调用这些接口方法时插入自定义的处理逻辑。

以下是InvocationHandler的基本用法:

  1. 创建一个实现InvocationHandler接口的类:这个类需要重写invoke方法,以提供自定义的处理逻辑。

  2. 使用Proxy类创建一个代理对象:你需要指定代理类实现的接口和InvocationHandler实例。

  3. 通过代理对象调用方法:当你通过代理对象调用一个方法时,实际上会调用InvocationHandler实例的invoke方法。

下面是一个简单的例子:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyInvocationHandler implements InvocationHandler {
    private Object target; // 被代理的对象

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用目标方法之前,可以添加自定义逻辑
        System.out.println("Before method " + method.getName());

        // 调用目标对象的方法
        Object result = method.invoke(target, args);

        // 在调用目标方法之后,可以添加自定义逻辑
        System.out.println("After method " + method.getName());

        return result;
    }
}

// 使用示例
public class ProxyExample {
    public static void main(String[] args) {
        // 创建一个被代理的对象
        SomeInterface someObject = new SomeInterfaceImpl();

        // 创建一个InvocationHandler
        MyInvocationHandler handler = new MyInvocationHandler(someObject);

        // 创建一个代理对象
        SomeInterface proxy = (SomeInterface) Proxy.newProxyInstance(
                someObject.getClass().getClassLoader(),
                someObject.getClass().getInterfaces(),
                handler
        );

        // 通过代理对象调用方法
        proxy.someMethod();
    }
}

// 假设这是一个接口
interface SomeInterface {
    void someMethod();
}

// 接口的实现类
class SomeInterfaceImpl implements SomeInterface {
    @Override
    public void someMethod() {
        System.out.println("Executing someMethod");
    }
}
4.2 应用

geoserver中好多对象都是被代理模式包裹的,以工作空间查询为例跟一下查询工作空间的代码

    @GetMapping
    public RestWrapper workspacesGet() {

        List<WorkspaceInfo> wkspaces = catalog.getWorkspaces();
        return wrapList(wkspaces, WorkspaceInfo.class);
    }

它的catalog跟踪后最终是

src/main/java/org/geoserver/catalog/impl/CatalogImpl.java

    @Override
    public List<WorkspaceInfo> getWorkspaces() {
        return facade.getWorkspaces();
    }
facade是这个

src/main/java/org/geoserver/catalog/impl/DefaultCatalogFacade.java

    @Override
    public List<WorkspaceInfo> getWorkspaces() {
        return ModificationProxy.createList(
                new ArrayList<>(workspaces.values()), WorkspaceInfo.class);
    }
ModificationProxy 是对代理做的一次封装 位置如下,感兴趣可以看一下

src/main/java/org/geoserver/catalog/impl/ModificationProxy.java

三、工作空间查询解读

一般来说工作空间的查询地址是

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

    @GetMapping
    public RestWrapper workspacesGet() {

        List<WorkspaceInfo> wkspaces = catalog.getWorkspaces();
        return wrapList(wkspaces, WorkspaceInfo.class);
    }

@GetMapping 能看出来它是个普通的spring servlet接口,RestWrapper是对返回结果的一个包装器,catalog是针对geoserver文件目录映射出来的一个方法类

查询结果是这样的

如果不用包装器的话返回结果是这样的

    @GetMapping("/details")
    public List<WorkspaceInfo> getAllWorkspacesDetails() {
        List<WorkspaceInfo> workspaces = catalog.getWorkspaces();
        return workspaces;
    }

可以看出来如果不用包装器的话会把查出的数据原封不动的返回出来,而且兼容xml和json,实际不管使用不使用包装器时上面 三、前置知识点-AbstractHttpMessageConverter 讲到Jaxb2RootElementHttpMessageConverter 转换器都会生效,也就是说一直支持xml个json请求,而当使用包装器时就用到了另一个模板框架二、前置知识点-FreeMarker

1 模板在哪

往下看wrapList源码

    protected <T> RestWrapper<T> wrapList(Collection<T> list, Class<T> 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 w>
  <li><a href="${page.pageURI(w.properties.name + '.html')}">${w.properties.name}</a><#if w.properties.isDefault> [default] 哈哈 </#if></li>
</#list>
</ul>
<#include "tail.ftl">

最后那两个“哈哈”是我自己加的,浏览器访问可以看到下面效果

如果你那儿是乱码的可以在头部模板里面加个<meta charset="UTF-8" />

src/restconfig/src/main/java/org/geoserver/rest/catalog/ftl-templates/head.ftl

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>GeoServer Configuration</title>
    <meta charset="UTF-8" />
    <meta name="ROBOTS" content="NOINDEX, 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。

2 从哪个看出来模板FreeMarker用的哪个转换器

看了前面描述的 处理 HTTP 请求和响应的转换  可以知道在查询完之后会执行一次查询结果的转换操作

再次看查询工作空间的代码

    @GetMapping
    public RestWrapper workspacesGet() {

        List<WorkspaceInfo> wkspaces = catalog.getWorkspaces();
        return wrapList(wkspaces, WorkspaceInfo.class);
    }

🔎 下钻查看wrapList的代码如下

    protected <T> RestWrapper<T> wrapList(Collection<T> list, Class<T> 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 XStreamCatalogListConverter
        extends XStreamMessageConverter<RestListWrapper<?>> 

根据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.
 *
 * <p>In the previous rest API this wasn't 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()) {
                    @Override
                    public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
                        return Collection.class.isAssignableFrom(type);
                    }

                    @Override
                    protected void writeCompleteItem(
                            Object item,
                            MarshallingContext context,
                            HierarchicalStreamWriter writer) {

                        writer.startNode(name);
                        context.convertAnother(item);
                        writer.endNode();
                    }
                });
        xstream.registerConverter(
                new Converter() {
                    @Override
                    public boolean canConvert(Class type) {
                        return clazz.isAssignableFrom(type);
                    }

                    @Override
                    public void marshal(
                            Object source,
                            HierarchicalStreamWriter writer,
                            MarshallingContext context) {

                        String ref;
                        // Special case for layer list, to handle the non-workspace-specific
                        // endpoint for layers
                        if (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
                            // case
                            ref = 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);
                    }

                    @Override
                    public Object unmarshal(
                            HierarchicalStreamReader reader, UnmarshallingContext context) {
                        return null;
                    }
                });
    }

我修改了上述代码中的

writer.startNode(wrapper.getItemAttributeName() +"test");

然后再次请求接口就能看到修改后的数据

四、扩展工作空间查询

原来的工作空间查询查询出的接口返回的信息很少,只有名称和详情的访问地址

{
  "workspaces": {
    "workspace": [
      {
        "name": "cite",
        "href": "http://localhost:8080/geoserver/rest/workspaces/cite.json"
      }
    ]
  }
}

而实际上,我们要用到的信息很多,不可能请求国工作空间列表后再一个个单独请求下详情,那样效率太低而且很麻烦,就想一次请求返回所有的工作空间详情,于是乎就有了下面的代码

src/main/java/org/geoserver/rest/catalog/WorkspaceController.java

    @GetMapping("/details")
    public List<WorkspaceInfo> getAllWorkspacesDetails() {
        List<WorkspaceInfo> workspaces = catalog.getWorkspaces();
        return workspaces;
    }

这段代码在上面有讲到,其实也就是当不使用包装器时会返回所有的信息。但仅仅是这样还不行,我还想要工作空间的uri,还有是否是默认工作空间

废话不多直接上代码,先定义一个工作空间详情类

// 定义接口
public interface WorkspaceDetailInfo extends WorkspaceInfo {

    String getUri();

    void setUri(String uri);
}

// 定义类
public class WorkspaceDetailInfoImpl extends WorkspaceInfoImpl implements WorkspaceDetailInfo {

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    protected String uri;
}

然后查询

    @GetMapping("/details")
    public List<WorkspaceDetailInfo> getAllWorkspacesDetails() {
        List<WorkspaceInfo> workspaces = catalog.getWorkspaces();
        List<WorkspaceDetailInfo> workspaceDetailsList = new ArrayList<>();
        WorkspaceInfo def = catalog.getDefaultWorkspace();
        for (WorkspaceInfo workspace : workspaces) {
            if (Proxy.isProxyClass(workspace.getClass())) {
                if(Proxy.getInvocationHandler(workspace) instanceof ModificationProxy ){
                    WorkspaceInfoImpl workspaceInfo = (WorkspaceInfoImpl)ModificationProxy.handler(workspace).getProxyObject();
                    WorkspaceDetailInfoImpl details = new WorkspaceDetailInfoImpl();
                    details.setName(workspaceInfo.getName());
                    details.setIsolated(workspaceInfo.isIsolated());
                    details.setId(workspaceInfo.getId());
                    if (def.equals(workspaceInfo)) {
                        details.setDefault(true);
                    } else {
                        details.setDefault(false);
                    }
                    details.setDateCreated(workspaceInfo.getDateCreated());
                    details.setDateModified(workspaceInfo.getDateModified());
                    NamespaceInfo ns = catalog.getNamespaceByPrefix(workspaceInfo.getName());
                    details.setUri(ns.getURI());
                    workspaceDetailsList.add(details);
                }
            }

        }

        return workspaceDetailsList;
    }

顺便说一下查询的时候用到了代理模式,就是上面前置知识点的那个代理模式,所以解析的时候要做一个解包的操作

if (Proxy.isProxyClass(workspace.getClass())) {
   if(Proxy.getInvocationHandler(workspace) instanceof ModificationProxy ){
                    WorkspaceInfoImpl workspaceInfo = (WorkspaceInfoImpl)ModificationProxy.handler(workspace).getProxyObject();
   }
}

查询出的结果如下,还是比较符合预期的。

写在最后,文章难免有写的不对或者不完善的地方,欢迎提出纠正意见

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值