Geoserver源码解读五 Catalog

系列文章目录

Geoserver源码解读一 环境搭建

Geoserver源码解读二 主入口

Geoserver源码解读三 GeoServerBasePage

Geoserver源码解读四 REST服务

Geoserver源码解读五 Catalog


前言

Catalog数据目录是 Geoserver比较核心的内容,理解Catalog对理解Geoserver的存储机制有很大帮助,夸张点儿说就是,理解了它的逻辑就相当于理解了geoserver的逻辑。本文同样以工作空间为例梳理下它的相关逻辑。


一、定义

GeoServer 的配置形成一个目录,包括工作空间、数据源、图层、样式等。实际上就是一堆文件夹和文本文件(充当的是文件型数据库的作用)。

二、前置知识点

1.Spring 的 Bean 生命周期

Spring 的 Bean 生命周期是指从 Bean 被创建到销毁的整个过程。这个过程包括多个阶段,每个阶段都可以通过不同的方式进行自定义,在 Spring 容器启动时,它会读取配置元数据(如 XML、注解或 Java 配置),解析这些元数据,并将定义的 Bean 注册到容器中。

ApplicationContextAware

Spring 的 ApplicationContextAware 用于标记那些需要知道当前 Spring 应用上下文(ApplicationContext)的 Bean。当一个 Bean 实现 ApplicationContextAware 接口时,它可以在 Bean 的生命周期中访问和操作当前的 ApplicationContext,也就是说它的执行顺序早于Bean 的生命周期

BeanPostProcessor 

Spring 的 BeanPostProcessor 允许你在 Bean 的初始化前后添加自定义逻辑。这个接口定义了两个方法,分别在 Bean 初始化之前和之后被调用。

  • postProcessBeforeInitialization(Object bean, String beanName): 在 Bean 的初始化方法(如 afterPropertiesSet 或自定义的 init-method)之前被调用。
  • postProcessAfterInitialization(Object bean, String beanName): 在 Bean 的初始化方法之后被调用

举个栗子说明

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware,
        InitializingBean, DisposableBean {
    private ApplicationContext applicationContext;
    // 1.这个最先被执行
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        System.out.println("ApplicationContextAware: Application context is set");
    }
    // 2.这个第二被执行
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before initialization of bean: " + beanName);
        return bean; // 可以返回原始的 Bean 或包装后的 Bean
    }

    // 4. 这个第四
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean: Bean properties are set");
    }


    // 3. 这个第三
    public void initMethod() {
        System.out.println("Custom init method: Bean is initializing");
    }

    // 5. 这个第五
    public void destroyMethod() {
        System.out.println("Custom destroy method: Bean is being destroyed");
    }
}
<bean id="myBean" class="com.example.MyBean" init-method="initMethod" destroy-method="destroyMethod">
</bean>

对Spring Bean生命周期的掌握。方便了解分析Geoserver是怎么对资源目录进行解析的

三、常见用法

 获取目录

Catalog catalog = geoServer.getCatalog();

获取所有工作空间

WorkspaceInfo workspace = catalog.getWorkspaces();

获取所有数据存储

List<DataStoreInfo> dataStores = catalog.getStores( DataStoreInfo.class );

获取特定图层

LayerInfo layer = catalog.getLayer( "myLayer" );

四、结构梳理

1.查询工作空间

查询入口

@RestController
@RequestMapping(
        path = RestBaseController.ROOT_PATH + "/workspaces",
        produces = {
            MediaType.APPLICATION_JSON_VALUE,
            MediaType.APPLICATION_XML_VALUE,
            MediaType.TEXT_HTML_VALUE
        })
public class WorkspaceController extends AbstractCatalogController {
    @Autowired
    public WorkspaceController(@Qualifier("catalog") Catalog catalog) {
        super(catalog);
    }

    @GetMapping
    public RestWrapper workspacesGet() {

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

从上面代码可以看出来catalog是依赖注入进来的

    <bean id="localWorkspaceCatalog" class="org.geoserver.catalog.impl.LocalWorkspaceCatalog">
        <constructor-arg ref="advertisedCatalog" />
    </bean>
    <alias name="localWorkspaceCatalog" alias="catalog"/> 

实际用到的Catalog是localWorkspaceCatalog而localWorkspaceCatalog使用了AbstractDecorator技术。这一点上一章的 四、前置知识点-AbstractDecorator 也有讲到,此处不多赘述,本质上就是定义了一个变量去直接操作父类。

public class LocalWorkspaceCatalog extends AbstractCatalogDecorator implements Catalog {

    public LocalWorkspaceCatalog(Catalog delegate) {
        super(delegate);
    }
}

这里同样是个依赖注入,以此类推,下面的三级都是依赖注入

    <bean id="rawCatalog" class="org.geoserver.catalog.impl.CatalogImpl" depends-on="configurationLock">
         <property name="resourceLoader" ref="resourceLoader"/>  
    </bean>
    <bean id="secureCatalog" class="org.geoserver.security.SecureCatalogImpl" depends-on="accessRulesDao,extensions">
        <constructor-arg ref="rawCatalog" /> 
    </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>    
    <bean id="localWorkspaceCatalog" class="org.geoserver.catalog.impl.LocalWorkspaceCatalog">
        <constructor-arg ref="advertisedCatalog" />
    </bean>

根据上面代码可以看到,最终的生效的父类Catalog是 rawCatalog 也就是 org.geoserver.catalog.impl.CatalogImpl,从depends-on看出它的前置条件是

<bean id="configurationLock" class="org.geoserver.GeoServerConfigurationLock"/>
先看下这个前置条件我替大家看过了就是一个线程锁,当两个用户同时尝试更改同一设置时,这个锁可以确保它们不会互相覆盖对方的更改。

然后再看org.geoserver.catalog.impl.CatalogImpl里面是怎么查询工作空间的

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

    @Override
    public WorkspaceInfo getWorkspace(String id) {
        return facade.getWorkspace(id);
    }

可以看出来catalog内部又使用了个facade,这层级套用的是真的深啊,静不下心的话根本就看不下去,再继续往下看这个facade

    /** data access facade */
    protected CatalogFacade facade;

    public CatalogImpl() {
        setFacade(new DefaultCatalogFacade(this));
        dispatcher = new CatalogEventDispatcher();
        resourcePool = ResourcePool.create(this);
    }

在构造函数中的给了它一个 DefaultCatalogFacade

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

继续查看DefaultCatalogFacade可以看到下面的代码

    /** workspaces */
    protected CatalogInfoLookup<WorkspaceInfo> workspaces =
            new CatalogInfoLookup<>(WORKSPACE_NAME_MAPPER);

    @Override
    public List<WorkspaceInfo> getWorkspaces() {
        return ModificationProxy.createList(
                new ArrayList<>(workspaces.values()), WorkspaceInfo.class);
    }

    @Override
    public WorkspaceInfo getWorkspace(String id) {
        WorkspaceInfo ws = workspaces.findById(id, WorkspaceInfo.class);
        return wrapInModificationProxy(ws, WorkspaceInfo.class);
    }

可以看出来DefaultCatalogFacade下面还有一层workspaces也就是CatalogInfoLookup

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

这是一个内部工具类,用于在 GeoServer 的目录(catalog)中查找和存储 CatalogInfo 对象。CatalogInfo 是 GeoServer 目录中的一个接口,它代表目录中的一个信息对象,如工作空间(Workspace)、数据存储(DataStore)、覆盖范围(Coverage)等

class CatalogInfoLookup<T extends CatalogInfo> {
    public T add(T value) {
        if (Proxy.isProxyClass(value.getClass())) {
            ModificationProxy h = (ModificationProxy) Proxy.getInvocationHandler(value);
            value = (T) h.getProxyObject();
        }
        Map<Name, T> nameMap = getMapForValue(nameMultiMap, value);
        Name name = nameMapper.apply(value);
        nameMap.put(name, value);
        Map<String, T> idMap = getMapForValue(idMultiMap, value);
        return idMap.put(value.getId(), value);
    }

    public Collection<T> values() {
        List<T> result = new ArrayList<>();
        for (Map<String, T> v : idMultiMap.values()) {
            result.addAll(v.values());
        }

        return result;
    }
}

从上面代码可以看出来查询出来的实际上是 idMultiMap 它的新增方法是add,到这里整个查询的逻辑基本上已经梳理到头了,但是这个idMultiMap  到底是在什么时候生成的也就是说这个add是在什么时候调用的就又是另外一条业务梳理线了

2.资源读取

上面讲到查询工作目录的时候最终到了CatalogInfoLookup的 idMultiMap ,我在add方法中打了个断点,跟踪了下进程,梳理出了下面的逻辑

首先是入口

src/main/java/applicationContext.xml

当项目起来后读取这个配置文件并初始化

<bean id="geoServerLoader" class="org.geoserver.config.GeoServerLoaderProxy">
      <constructor-arg ref="resourceLoader"/>
    </bean>

读到配置文件后进入Bean生命周期(上面有讲到)

public class GeoServerLoaderProxy
        implements BeanPostProcessor,
                ApplicationListener<ContextClosedEvent>,
                ApplicationContextAware,
                GeoServerReinitializer {

    /** resource loader */
    protected GeoServerResourceLoader resourceLoader;

    /** the actual loader */
    GeoServerLoader loader;

    public GeoServerLoaderProxy(GeoServerResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.loader = lookupGeoServerLoader(applicationContext);
        loader.setApplicationContext(applicationContext);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if (loader != null) {
            return loader.postProcessAfterInitialization(bean, beanName);
        }
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if (loader != null) {
            return loader.postProcessBeforeInitialization(bean, beanName);
        }
        return bean;
    }

    protected GeoServerLoader lookupGeoServerLoader(ApplicationContext appContext) {
        GeoServerLoader loader = GeoServerExtensions.bean(GeoServerLoader.class, appContext);
        if (loader == null) {
            loader = new DefaultGeoServerLoader(resourceLoader);
        }
        return loader;
    }

    @Override
    public void initialize(GeoServer geoServer) throws Exception {
        loader.initializeDefaultStyles(geoServer.getCatalog());
    }
}

然后重点看一下它的postProcessBeforeInitialization方法

    protected GeoServerLoader lookupGeoServerLoader(ApplicationContext appContext) {
        GeoServerLoader loader = GeoServerExtensions.bean(GeoServerLoader.class, appContext);
        if (loader == null) {
            loader = new DefaultGeoServerLoader(resourceLoader);
        }
        return loader;
    }

实际上就是loader(src/main/java/org/geoserver/config/GeoServerLoader.java)的postProcessBeforeInitialization方法

    public final Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if (bean instanceof Catalog) {
            // ensure this is not a wrapper but the real deal
            if (bean instanceof Wrapper && ((Wrapper) bean).isWrapperFor(Catalog.class)) {
                return bean;
            }
            postProcessBeforeInitializationCatalog(bean);
        }

        if (bean instanceof GeoServer) {
            postProcessBeforeInitializationGeoServer(bean);
        }

        return bean;
    }

再继续看postProcessBeforeInitializationCatalog方法

private void postProcessBeforeInitializationCatalog(Object bean) {
        // load
        try {
            // setup ADMIN_ROLE security context to load secured resources
            activateAdminRole();

            Catalog catalog = (Catalog) bean;
            XStreamPersister xp = xpf.createXMLPersister();
            xp.setCatalog(catalog);
            loadCatalog(catalog, xp);

            // initialize styles
            initializeStyles(catalog, xp);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // clear security context
            SecurityContextHolder.clearContext();
        }
    }

重点在loadCatalog,再继续往下看loadCatalog

@Override
    protected void loadCatalog(Catalog catalog, XStreamPersister xp) throws Exception {
        catalog.setResourceLoader(resourceLoader);

        readCatalog(catalog, xp);

        if (!legacy) {
            // add the listener which will persist changes
            catalog.addListener(new GeoServerConfigPersister(resourceLoader, xp));
            catalog.addListener(new GeoServerResourcePersister(catalog));
        }
        executeListener(catalog, xp);
    }

重点在readCatalog,再继续往下看readCatalog

重点来了啊 重点来了

protected void readCatalog(Catalog catalog, XStreamPersister xp) throws Exception {
        // we are going to synch up the catalogs and need to preserve listeners,
        // but these two fellas are attached to the new catalog as well
        catalog.removeListeners(ResourcePool.CacheClearingListener.class);
        catalog.removeListeners(GeoServerConfigPersister.class);
        catalog.removeListeners(GeoServerResourcePersister.class);
        // look for catalog.xml, if it exists assume we are dealing with
        // an old data directory
        Resource f = resourceLoader.get("catalog.xml");
        CatalogImpl catalog2;
        if (!Resources.exists(f)) {
            // assume 2.x style data directory
            Stopwatch sw = Stopwatch.createStarted();
            LOGGER.config("Loading catalog " + resourceLoader.getBaseDirectory());
            catalog2 = (CatalogImpl) readCatalog(xp);
            LOGGER.config("Read catalog in " + sw.stop());
        } else {
            // import old style catalog, register the persister now so that we start
            // with a new version of the catalog
            catalog2 = (CatalogImpl) readLegacyCatalog(f, xp);
        }
        List<CatalogListener> listeners = new ArrayList<>(catalog.getListeners());
        // make to remove the old resource pool catalog listener
        ((CatalogImpl) catalog).sync(catalog2);

        // attach back the old listeners
        for (CatalogListener listener : listeners) {
            catalog.addListener(listener);
        }
    }
    /** Reads the catalog from disk. */
    Catalog readCatalog(XStreamPersister xp) throws Exception {
        CatalogImpl catalog = new CatalogImpl();
        catalog.setResourceLoader(resourceLoader);
        xp.setCatalog(catalog);
        xp.setUnwrapNulls(false);

        // see if we really need to verify stores on startup
        boolean checkStores = checkStoresOnStartup(xp);
        if (!checkStores) {
            catalog.setExtendedValidation(false);
        }

        // global styles
        loadStyles(resourceLoader.get("styles"), catalog, xp);

        // workspaces, stores, and resources
        Resource workspaces = resourceLoader.get("workspaces");
        if (Resources.exists(workspaces)) {
            // do a first quick scan over all workspaces, setting the default
            Resource dws = workspaces.get("default.xml");
            WorkspaceInfo defaultWorkspace = null;
            if (Resources.exists(dws)) {
                try {
                    defaultWorkspace = depersist(xp, dws, WorkspaceInfo.class);
                    if (LOGGER.isLoggable(Level.CONFIG)) {
                        LOGGER.config(
                                "Loaded default workspace '" + defaultWorkspace.getName() + "'");
                    }
                } catch (Exception e) {
                    LOGGER.log(Level.WARNING, "Failed to load default workspace", e);
                }
            } else {
                LOGGER.warning("No default workspace was found.");
            }

            List<Resource> workspaceList =
                    workspaces
                            .list()
                            .parallelStream()
                            .filter(r -> Resources.DirectoryFilter.INSTANCE.accept(r))
                            .collect(Collectors.toList());

            try (AsynchResourceIterator<WorkspaceContents> it =
                    new AsynchResourceIterator<>(
                            workspaces,
                            Resources.DirectoryFilter.INSTANCE,
                            new WorkspaceMapper())) {
                while (it.hasNext()) {
                    WorkspaceContents wc = it.next();
                    WorkspaceInfo ws;
                    final Resource workspaceResource = wc.resource;
                    try {
                        ws = depersist(xp, wc.contents, WorkspaceInfo.class);
                        catalog.add(ws);
                        LOGGER.log(
                                Level.CONFIG,
                                () -> String.format("Loaded workspace '%s'", ws.getName()));
                    } catch (Exception e) {
                        LOGGER.log(
                                Level.WARNING,
                                "Failed to load workspace '" + workspaceResource.name() + "'",
                                e);
                        continue;
                    }

                    // load the namespace
                    NamespaceInfo ns = null;
                    try {
                        ns = depersist(xp, wc.nsContents, NamespaceInfo.class);
                        catalog.add(ns);
                    } catch (Exception e) {
                        LOGGER.log(
                                Level.WARNING,
                                "Failed to load namespace for '" + workspaceResource.name() + "'",
                                e);
                    }

                    // set the default workspace, this value might be null in the case of coming
                    // from a
                    // 2.0.0 data directory. See https://osgeo-org.atlassian.net/browse/GEOS-3440
                    if (defaultWorkspace != null) {
                        if (ws.getName().equals(defaultWorkspace.getName())) {
                            catalog.setDefaultWorkspace(ws);
                            if (ns != null) {
                                catalog.setDefaultNamespace(ns);
                            }
                        }
                    } else {
                        // create the default.xml file
                        defaultWorkspace = catalog.getDefaultWorkspace();
                        if (defaultWorkspace != null) {
                            try {
                                persist(xp, defaultWorkspace, dws);
                            } catch (Exception e) {
                                LOGGER.log(
                                        Level.WARNING,
                                        "Failed to persist default workspace '"
                                                + workspaceResource.name()
                                                + "'",
                                        e);
                            }
                        }
                    }

                    // load the styles for the workspace
                    Resource styles = workspaceResource.get("styles");
                    if (styles != null) {
                        loadStyles(styles, catalog, xp);
                    }
                }
            }

            // maps each store into a SingleResourceContents
            ResourceMapper<SingleResourceContents> storeMapper =
                    sd -> {
                        Resource f = sd.get("datastore.xml");
                        if (Resources.exists(f)) {
                            return new SingleResourceContents(f, f.getContents());
                        }
                        f = sd.get("coveragestore.xml");
                        if (Resources.exists(f)) {
                            return new SingleResourceContents(f, f.getContents());
                        }
                        f = sd.get("wmsstore.xml");
                        if (Resources.exists(f)) {
                            return new SingleResourceContents(f, f.getContents());
                        }
                        f = sd.get("wmtsstore.xml");
                        if (Resources.exists(f)) {
                            return new SingleResourceContents(f, f.getContents());
                        }
                        if (!isConfigDirectory(sd)) {
                            LOGGER.warning("Ignoring store directory '" + sd.name() + "'");
                        }
                        // nothing found
                        return null;
                    };

            for (Resource wsd : workspaceList) {
                // load the stores for this workspace
                try (AsynchResourceIterator<SingleResourceContents> it =
                        new AsynchResourceIterator<>(
                                wsd, Resources.DirectoryFilter.INSTANCE, storeMapper)) {
                    while (it.hasNext()) {
                        SingleResourceContents SingleResourceContents = it.next();
                        final String resourceName = SingleResourceContents.resource.name();
                        if ("datastore.xml".equals(resourceName)) {
                            loadDataStore(SingleResourceContents, catalog, xp, checkStores);
                        } else if ("coveragestore.xml".equals(resourceName)) {
                            loadCoverageStore(SingleResourceContents, catalog, xp);
                        } else if ("wmsstore.xml".equals(resourceName)) {
                            loadWmsStore(SingleResourceContents, catalog, xp);
                        } else if ("wmtsstore.xml".equals(resourceName)) {
                            loadWmtsStore(SingleResourceContents, catalog, xp);
                        } else if (!isConfigDirectory(SingleResourceContents.resource)) {
                            LOGGER.warning(
                                    "Ignoring store directory '"
                                            + SingleResourceContents.resource.name()
                                            + "'");
                            continue;
                        }
                    }
                }

                // load the layer groups for this workspace
                Resource layergroups = wsd.get("layergroups");
                if (layergroups != null) {
                    loadLayerGroups(layergroups, catalog, xp);
                }
            }
        } else {
            LOGGER.warning("No 'workspaces' directory found, unable to load any stores.");
        }

        // layergroups
        Resource layergroups = resourceLoader.get("layergroups");
        if (layergroups != null) {
            loadLayerGroups(layergroups, catalog, xp);
        }
        xp.setUnwrapNulls(true);
        catalog.resolve();
        // re-enable extended validation
        if (!checkStores) {
            catalog.setExtendedValidation(true);
        }
        return catalog;
    }

这就是解析工作空间的核心代码了,并且可以看到它和上一步的关联的地方

catalog.add(ws);

扩展的说一下解析工作空间时用到了XStream技术,这个技术主要用于xml文件和java对象的互相转换

也就是说到此为止,Catalog的业务逻辑算是基本上梳理完了

五、总结

回顾上面的一坨坨代码,总结出来资源目录的处理就两条线

1.资源读取(上述4.2)【本质上就是读取文件】

2.资源的查询过滤(上述4.1)【本质上就是数组或者集合的增删改查】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿儿本无心

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值