系列文章目录
Geoserver源码解读三 GeoServerBasePage
目录
前言
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)【本质上就是数组或者集合的增删改查】