Geoserver源码解读四 REST服务

news2024/11/24 14:50:52

文章目录

文章目录

一、概要

二、前置知识点-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 boot)
UI框架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.数据模型

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);

三、前置知识点-AbstractHttpMessageConverter

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

3.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 类型的数据。

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

3.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参数

四、前置知识点-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 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();
    }
}

4.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去操作父类的一些操作

五、工作空间查询解读

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

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

5.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。

5.2 请求转换器解读

看了前面描述的三、前置知识点-AbstractHttpMessageConverter 可以知道在查询完之后会执行一次查询结果的转换操作

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

    @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");

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1861495.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024最新算法:北极海鹦优化(Arctic puffin optimization,APO)算法求解23个函数,MATLAB代码

一、算法介绍 北极海鹦优化&#xff08;Arctic puffin optimization&#xff0c;APO&#xff09;算法是2024年提出一种智能优化算法。该算法模拟海鹦在空中飞行和水下觅食两个阶段的行为&#xff0c;旨在实现勘探与开发之间更好的平衡。该算法包括几个关键操作&#xff0c;包括…

Nginx开发【Nginx虚拟主机和域名解析】

03 【Nginx虚拟主机和域名解析】 虚拟主机使用特殊的软硬件技术&#xff0c;把一台运行在因特网上的服务器主机分成一台台“虚拟”的主机&#xff0c;每一台虚拟主机都具有独立的域名&#xff0c;具有完整的Internet服务器&#xff08;WWW、FTP、Email等&#xff09;功能&…

FFmpeg+javacpp+javacv使用

FFmpegjavacppjavacv使用 Bytedeco官网案例1、导入opencv、ffmpeg依赖包2、FFmpeg 数据结构2.1 AVFormatContext 格式化I/O上下文2.1.1 metadata2.1.2 Duration、start、bitrate等其他信息2.1.3 dump信息 Bytedeco GitHub&#xff1a;javacpp Bytedeco官网案例 FFmpeg – [示例…

C语言·动态内存管理

1. 为什么要有动态内存管理&#xff1f; 例1&#xff1a; //固定的向内存申请4个字节 int a 10;//申请连续的一块空间 int arr[10]; 这些数据一旦声明定义之后就会在内存中有一块空间&#xff0c;这些空间都是固定的&#xff0c;为了让内存使用更加灵活&#xff0c;这时我们…

【回溯算法题记录】组合总和题汇总

组合总和 39. 组合总和题目描述初始思路后续分析 40. 组合总和 II题目描述思路&#xff08;参考代码随想录&#xff09; 39. 组合总和 题目&#x1f517; 题目描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数…

开源网安参与编制的《代码大模型安全风险防范能力要求及评估方法》正式发布

​代码大模型在代码生成、代码翻译、代码补全、错误定位与修复、自动化测试等方面为研发人员带来了极大便利的同时&#xff0c;也带来了对安全风险防范能力的挑战。基于此&#xff0c;中国信通院依托中国人工智能产业发展联盟&#xff08;AIIA&#xff09;&#xff0c;联合开源…

教你使用 Go 语言访问智谱 AI 大模型!

AI 大模型太火爆了&#xff01;我在工作中经常使用它们&#xff0c;感觉已经离不开了&#xff01; 最近测试了智谱 AI 大模型&#xff0c;实测下来感觉还挺不错的。 官方提供了体验包&#xff0c;注册并实名认证之后有 300 万 token&#xff0c;非常壕&#xff01; 今天的股…

uni-push(2.0)常见问题,Android平台

将常用的网址一定要收藏在标签栏中&#xff0c;方便后期找&#xff0c;不然后期会很生气。 草料二维码&#xff0c;这个在线工具可以将打包生成的apk文件生成二维码&#xff0c;供测试人员测试。生成的apk只有五次下载机会&#xff0c;可点击链接后的一键上传&#xff0c;这样…

站在巨人的肩膀上 C语言理解和简单练习(包含指针前的简单内容)

1.格式化的输入/输出 1.1printf函数 printf函数你需要了解的就是转换说明&#xff0c;转换说明的作用是将内存中的二进制转换成你所需要的格式入%d就是将内存中存储的变量的二进制转化为十进制并打印出来&#xff0c;同时我们可以在%X的转换说明对精度和最小字段宽度的指定&a…

如何在SpringBoot中自定义starter

如何在SpringBoot中自定义starter Spring Boot 提供了一种简便的方法来创建自定义的 starter&#xff0c;从而帮助开发者封装常用的配置和依赖。本文将介绍如何在 Spring Boot 中自定义一个 starter。 1. 创建 Maven 项目 首先&#xff0c;创建一个新的 Maven 项目&#xff…

SpringBoot开启事务日志

一般框架开启日志的方式&#xff1a; 开启某个包下的日志就写该包路径&#xff0c;开启某个类下的日志就写该类路径。

全国首场以AI数字内容风控为主题的大会,开放参会报名中

网易易盾将于2024年7月6日举办一场AI数字内容风控大会&#xff0c;邀请AI产业链的基础层、模型层和应用层的企业代表&#xff0c;科研机构、律所、院校的专家老师&#xff0c;探讨大模型时代下的自由与责任等话题。参会报名链接&#xff1a;https://sourl.cn/vqUU7X&#xff0c…

【计算机毕业设计】基于Springboot的月度员工绩效考核管理系统【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…

HTTP/2 头部压缩 Header Compress(HPACK)详解

文章目录 1. HPACK 的工作原理1.1 静态表1.2 动态表 2. 压缩过程2.1 编码过程2.2 解码过程 3. HPACK 的优势 在HTTP1.0中&#xff0c;我们使用文本的形式传输header&#xff0c;在header中携带cookie的话&#xff0c;每次都需要重复传输几百到几千的字节&#xff0c;这着实是一…

推荐系统-FM模型

参考&#xff1a;推荐系统&#xff08;三&#xff09;Factorization Machines&#xff08;FM&#xff09;_svmmf-CSDN博客 一句话概括下FM&#xff1a;隐式向量特征交叉----类似embedding的思想 LR 如果利用LR做特征的二阶交叉&#xff0c;有&#xff1a; 但这个公式存在显著…

从0开始C++(七):继承

相关文章&#xff1a; 从0开始C&#xff08;一&#xff09;&#xff1a;从C到C 从0开始C&#xff08;二&#xff09;&#xff1a;类、对象、封装 从0开始C&#xff08;三&#xff09;&#xff1a;构造函数与析构函数详解 从0开始C&#xff08;四&#xff09;&#xff1a;作…

香港服务器托管对外贸行业必要性和优势

在当今全球化的经济环境下&#xff0c;外贸企业面临着前所未有的机遇与挑战。其中&#xff0c;服务器托管的选择对于外贸企业的运营效率和市场拓展具有举足轻重的作用。香港服务器&#xff0c;凭借其独特的地理位置、优质的网络环境和卓越的服务性能&#xff0c;一直是外贸企业…

2024年必备的15个免费 SVG 设计资源

在动态设计领域&#xff0c;SVG&#xff08;可缩放矢量图形&#xff09;已成为设计师打造响应迅速、清晰且适应性强的视觉效果的必备工具。 这些设计非常适合幻灯片 PowerPoint 演示文稿、应用程序设计、网站设计、原型设计、社交媒体帖子等。 在这篇文章中&#xff0c;我们将…

这份AI绘画攻略赶紧码住!超适合小白入门的PS AI插件来啦!

有没有小伙伴对AI绘画很感兴趣&#xff0c;但是看到国外的mj和sd总觉得入门困难&#xff01;别担心&#xff0c;米兔挖到一款超级绝的国产PS AI插件&#xff01;适合新手学习&#xff0c;米兔这里还有一份专为小白准备的AI绘画攻略&#xff0c;让你的创意不再受限&#xff01; …

评测|贪吃小猫疯狂长肉,让它停不下嘴的希喂、鲜朗、帕特真实调研

我发现很多铲屎官存在一个误区&#xff0c;认为“进口即是高贵”&#xff0c;过度信赖进口产品。一见到进口宠物粮就冲动购买&#xff0c;甚至对国产品牌持贬低态度&#xff0c;贴上“质量不佳”、“不符合标准”等标签。 为了更深入地了解这一现象&#xff0c;我深入研究了主食…