Json 序列化一 —— 基础篇

server/2024/12/21 8:31:56/

1、简介

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它的作用是数据标记,存储,传输,具有读写速度快、解析简单、轻量级、独立于语言和平台、具有自我描述性等特点。

在这里插入图片描述

像 gson、fastjson、jackson 这些都是 JSON 的解析器。而 gson 又称为 Google Gson,是 Google 发布的开源 Java 库,主要用于把 Java 对象序列化为 JSON 字符串,或把 JSON 字符串反序列化为 Java 对象。

2、JSON 语法

JSON 的语法构建于 key-value 键值对和值的有效列表(数组)之上。这两种都是常见的数据结构,大部分计算机语言都以某种形式支持它们,这使得 JSON 数据格式在同样基于这些结构的编程语言之间交换成为可能。

JSON 具有对象(Object)、数组(Array)、值(Value)、字符串(String)、数值(Number)等形式。

2.1 对象 Object

无序的键值对集合,以"{“开始,以”}“结束,在 key 后面跟一个”:“,键值对之间用”,"隔开。

json">    {"name": "英语","score": 78.3}

2.2 值 Value

可以是双引号括起来的字符串、数值、true、false、null、对象或数组,可以嵌套。

在这里插入图片描述

json">{"url":"https://qqe2.com","name": "欢迎使用JSON在线解析编辑器","array": { "JSON校验": "http://jsonlint.qqe2.com/", "Cron生成": "http://cron.qqe2.com/", "JS加密解密": "http://edit.qqe2.com/" },"boolean": true,"null": null, "number": 123, "object": { "a": "b", "c": "d", "e": "f"}
}

2.3 数组 Array

有序的值的集合。以"[“开始,以”]“结束。值之间使用”,"分隔。

json">"courses": [ // 一个对象{}作为一个 value,中间用逗号隔开{"name": "英语", "score": 78.3 },{"name": "数学", "score": 88.3 }
]

2.4 字符串 String

是由双引号包围的任意数量 Unicode 字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。

json">{ "name": "Zero", 
}

2.5 数值 Number

与 C 或者 Java 的数值非常相似。除去未曾使用的八进制与十六进制格式和一些编码细节。

json">{ "age": 28, 
}

3、Gson 基础

3.1 Gson 的基本使用

public class GsonTest {static class GsonBean {private int i;private String string;public GsonBean(int i, String string) {this.i = i;this.string = string;}@Overridepublic String toString() {return "GsonBean{" +"i=" + i +", string='" + string + '\'' +'}';}}public static void main(String[] args) {Gson gson = new Gson();// 基本类型的序列化System.out.println(gson.toJson(1)); // 1System.out.println(gson.toJson("test")); // "test"System.out.println(gson.toJson(new int[]{1, 2, 3})); // [1,2,3]// 反序列化Integer i = gson.fromJson("1", int.class);System.out.println("i=" + i); // i=1// 引用类型的序列化与反序列化GsonBean gsonBean = new GsonBean(2, "test");String json = gson.toJson(gsonBean);System.out.println("json:" + json); // json:{"i":2,"string":"test"}GsonBean gsonBean1 = gson.fromJson(json, GsonBean.class);System.out.println("gsonBean1" + gsonBean1); // gsonBean1GsonBean{i=2, string='test'}}/*** 将 gsonBean 序列化成 JSON 格式并写入 filePath 指定的文件中*/private static void writeJsonToFile(GsonBean gsonBean, String filePath) {try (OutputStream os = new FileOutputStream(filePath);JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {Gson gson = new Gson();gson.toJson(gsonBean, new TypeToken<GsonBean>() {}.getType(), jsonWriter);jsonWriter.flush();} catch (IOException e) {e.printStackTrace();}}private static GsonBean readJsonObjectFromFile(String filePath) {try (JsonReader jsonReader = new JsonReader(new InputStreamReader(new FileInputStream(filePath)))) {Gson gson = new Gson();return gson.fromJson(jsonReader, new TypeToken<GsonBean>() {}.getType());} catch (IOException e) {e.printStackTrace();}return null;}
}

此外,给定一个 JSON 格式,需要熟练的将其转换成 JavaBean,如:

json">// 最外层是一个对象,里面有key、simpleArray、arrays、innerclass四个对象
{// 字符串对象"key": "value",// [] 表示数组,数组内对象只有value没有key就是纯数组"simpleArray": [1,2,3],// 两组[]说明是数组嵌套,内层[]内放了一个对象,该对象对应Java的内部类"arrays": [[{"arrInnerClsKey": "arrInnerClsValue","arrInnerClsKeyNub": 1}]],// 对象嵌套,对应Java的内部类,其对象名为父对象的key,即innerclass"innerclass": {"name": "zero","age": 25,"sex": "男"}
}

转换成 JavaBean 是:

public class GsonBean {private String key;private List<Integer> simpleArray;private List<List<ArraysBean>> arrays;private InnerClassBean innerclass;// getters and setters...public static class ArraysBean {private String arrInnerClsKey;private int arrInnerClsKeyNub;// getters and setters...}public static class InnerClassBean {private String name;private int age;private String sex;// getters and setters...}
}

3.2 Gson 中的注解

Gson 中有一些注解,会给序列化/反序列化工作提供很大的便利。

SerializedName 注解

@SerializedName 表示序列化时,会用给定的名字作为 key:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {/*** @return 在序列化/反序列化时,返回该字段想要得到的名字*/String value();/*** @return 反序列化时,返回该字段可选的名字*/String[] alternate() default {};
}

示例代码:

    public class MyClass {@SerializedName("name")String a;@SerializedName(value = "name1", alternate = {"name2", "name3"})String b;String c;public MyClass(String a, String b, String c) {this.a = a;this.b = b;this.c = c;}}private void test() {MyClass target = new MyClass("v1", "v2", "v3");Gson gson = new Gson();String json = gson.toJson(target);System.out.println(json); // {"name":"v1","name1":"v2","c":"v3"}MyClass target1 = gson.fromJson("{'name1':'v1'}", MyClass.class);assertEquals("v1", target1.b);target1 = gson.fromJson("{'name2':'v2'}", MyClass.class);assertEquals("v2", target1.b);target1 = gson.fromJson("{'name3':'v3'}", MyClass.class);assertEquals("v3", target1.b);}private void assertEquals(String value, String keyValue) {assert value.equals(keyValue);}

MyClass 中,字段 a 在序列化到 Json 中之后的名字为 name,字段 b 序列化后的名字为 name1(如果指定了多个值,会用第一个),而字段 c 则采用默认策略,序列化的名字就为变量名 c。

反序列化时,Json 中的 name1、name2、name3 都是字段 b 所指定的名字,所以它们都会被反序列化到字段 b 上。

@SerializedName 主要用途就是解决后端返回的 Json 字段名字不便建立 JavaBean 字段的情况,比如说后端给的 Json 是这样的:

json">{"Type": 6,"Result": {"State": "1","Msg": "成功"},"Data": {"1": 1000000001,"2": 2800,"3": "UPDATE","4": 201901021130,"5": 2854,"6": 2,"7": 2.58,}
}

Data 中的1、2、3、4、5、6、7根本无法作为 Java 字段声明,这时就可以用 @SerializedName 注解:

public class TestPojo {@SerializedName("1")private int totalAccount;@SerializedName("2")private int numIns;@SerializedName("3")private String type;@SerializedName("4")private long time;
}

Expose 注解

还有一个 @Expose 注解用来标明该字段是否参与序列化/反序列化:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Expose {/*** If {@code true}, the field marked with this annotation is written out in the JSON while* serializing. If {@code false}, the field marked with this annotation is skipped from the* serialized output. Defaults to {@code true}.* @since 1.4*/public boolean serialize() default true;/*** If {@code true}, the field marked with this annotation is deserialized from the JSON.* If {@code false}, the field marked with this annotation is skipped during deserialization. * Defaults to {@code true}.* @since 1.4*/public boolean deserialize() default true;
}

Since 和 Until 注解

此外还有用于版本控制的注解 @Since 和 @Until:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Since {/*** the value indicating a version number since this member* or type has been present.*/double value();
}@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Until {/*** the value indicating a version number until this member* or type should be ignored.*/double value();
}

@Since 表示当被标记字段的当前版本大于等于 value() 指定的值时才参与序列化/反序列化,否则忽略;@Until 表示当前被标记的作用域的当前版本小于(注意没有等于) value() 指定的值时才参与序列化/反序列化,否则忽略。

代码示例:

public class User {private String name;@Since(1.0)@Until(1.3)private String emailAddress;@Since(1.0)@Until(1.2)private String password;// 构造方法和 toString() 省略
}

name 字段会参加所有版本的序列化/反序列化,emailAddress 会参加 [1.0,1.3) 版本之间的序列化/反序列化,password 会参加 [1.0,1.2) 版本之间的序列化/反序列化。注意需要使用 GsonBuilder 指定当前版本后创建 Gson,不能使用 Gson 的空构造方法,否则 User 中使用 @Since、@Until 指定的版本将无效:

    // 使用空参构造方法创建 Gsonprivate void testUser() {Gson gson = new Gson();User user = new User("Test", "xyz@test.com", "123456");String json = gson.toJson(user);System.out.println(json); // {"name":"Test","emailAddress":"xyz@test.com","password":"123456"}User user1 = gson.fromJson(json, User.class);System.out.println(user1); // User{name='Test', emailAddress='xyz@test.com', password='123456'}}// 使用 GsonBuilder 创建 Gson 并设置其版本号private void testUserWithVersion() {Gson gson1 = new GsonBuilder().setVersion(1.1).create();User user = new User("Test", "xyz@test.com", "123456");String json1 = gson1.toJson(user);System.out.println(json1); // {"name":"Test","emailAddress":"xyz@test.com","password":"123456"}User user1 = gson1.fromJson(json1, User.class);System.out.println(user1); // User{name='Test', emailAddress='xyz@test.com', password='123456'}Gson gson2 = new GsonBuilder().setVersion(1.2).create();String json2 = gson2.toJson(user);System.out.println(json2); // {"name":"Test","emailAddress":"xyz@test.com"}User user2 = gson2.fromJson(json2, User.class);System.out.println(user2); // User{name='Test', emailAddress='xyz@test.com', password='null'}}

当指定 gson2 的版本号为 1.2 时,password 字段没有参与序列化/反序列化,说明 @Until 指定的边界不在参与序列化/反序列化的版本之内。

JsonAdapter 注解

Gson 中的解析使用了反射技术,由于反射会使性能受到影响,因此 Gson 提供了 @JsonAdapter 注解用来指定一个具体的 JavaBean 的解析类来代替默认的反射。源码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface JsonAdapter {/** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link JsonDeserializer} or {@link JsonSerializer}. */Class<?> value();/** false, to be able to handle {@code null} values within the adapter, default value is true. */boolean nullSafe() default true;}

@JsonAdapter 既可以作用于类,也可以作用于字段,先看如何在类上使用。JavaBean 类上加注解 @JsonAdapter,值为代替默认的反射方式解析 Json 的类对象:

@JsonAdapter(PersonJsonAdapter.class)
public class Person {public final String firstName, lastName;public Person(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;}// toString()...
}

PersonJsonAdapter 类需要继承 TypeAdapter 并实现 write() 和 read():

public class PersonJsonAdapter extends TypeAdapter<Person> {// implement write: combine firstName and lastName into name@Overridepublic void write(JsonWriter out, Person value) throws IOException {out.beginObject();out.name("name");out.value(value.firstName + " " + value.lastName);out.endObject();}// implement read: split name into firstName and lastName@Overridepublic Person read(JsonReader in) throws IOException {in.beginObject();in.nextName();String[] nameParts = in.nextString().split(" ");in.endObject();return new Person(nameParts[0], nameParts[1]);}
}

write() 其实就是执行序列化过程,把 Person 类写成 Json 字符串,read() 就是反序列化把 Json 字符串转换成 Person 对象。

测试代码:

    private void testPerson() {Person person = new Person("first", "last");Gson gson = new Gson();String json = gson.toJson(person);System.out.println(json); // {"name":"first last"}System.out.println(gson.fromJson(json, Person.class)); // Person{firstName='first', lastName='last'}}

该注解作用于字段:

private static final class Gadget {@JsonAdapter(UserJsonAdapter.class)final User user;Gadget(User user) {this.user = user;}
}

可以在一个字段、该字段的类型以及 GsonBuilder 中(通过 registerTypeAdapter())指定不同的类型适配器(TypeAdapter),三者的优先级由高到低是:作用于字段的注解 -> 在 GsonBuilder 上注册的适配器 -> 作用于类上的注解。

这个注解引用的类必须是 TypeAdapter 或者 TypeAdapterFactory,或者是至少实现了 JsonSerializer 或 JsonDeserializer 接口之一。使用 TypeAdapterFactory 可以使该注解委派给一个封闭的 Gson 实例。

3.3 其它实用方法

主要是 GsonBuilder 中提供的常用方法:

  • setPrettyPrinting():输出 Json 的时候会按照 Json 的格式输出,默认是在一行中输出。
  • registerTypeAdapter():设置自定义的解析器(需要继承 TypeAdapter),是 @JsonAdapter 的等价替换方式。

3.4 对字段空值的处理

实际开发时可能会遇到后端返回的 Json 数据某个字段的值是一个空值,类似于:

json">{"name":"java","authors":""
}

JavaBean 如下:

public class GsonError {private String name;private List<AuthorsBean> authors;public static class AuthorsBean {private String id;private String name;}
}

使用默认的 Gson 进行反序列化:

    public static void test2() {String json = "{\n" +"    \"name\": \"java\",\n" +"    \"authors\": \"\"\n" +"}";Gson gson = new Gson();GsonError gsonError = gson.fromJson(json, GsonError.class);System.out.println(gsonError);}

会抛出异常:

Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 3 column 17 path $.authors

异常原因是 authors 字段的值应该是一个数组,但是 JSON 数据给出的却是一个空的字符串,默认的 Gson 解析器无法处理这种类型不匹配的情况,因此抛出了异常。既然默认的解析器无法处理这种情况,那我们可以自定义反序列化过程对此做专门的处理,有两种方式:

1. 自定义 JsonDeserializer:实现 JsonDeserializer 接口,在处理反序列化的方法 deserialize() 内对可能出现问题的 key 进行特殊处理。
2. 自定义 TypeAdapter:继承 TypeAdapter,重写反序列化方法 read() 时对可能出现问题的 key 进行特殊处理。如果不想在 read()、write() 中手动处理空值,可以调用 TypeAdapter 的 nullSafe() 保证空值安全。

两种方式都需要通过 GsonBuilder 的 registerTypeAdapter() 注册才可生效。

自定义 JsonDeserializer

先来看第一种方式:

    public static class GsonErrorDeserializer implements JsonDeserializer<GsonError> {@Overridepublic GsonError deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {JsonObject jsonObject = json.getAsJsonObject();GsonError gsonError = new GsonError();// 先处理 nameString name = jsonObject.get("name").getAsString();gsonError.setName(name);// 再处理 authorJsonElement authorElement = jsonObject.get("authors");if (!authorElement.isJsonArray()) {// 不是 Json 数组,视为无效值gsonError.setAuthors(null);} else {AuthorsBean[] authors = context.deserialize(authorElement, AuthorsBean[].class);gsonError.setAuthors(Arrays.asList(authors));}return gsonError;}}public static class AuthorDeserializer implements JsonDeserializer<GsonError.AuthorsBean> {@Overridepublic AuthorsBean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {JsonObject jsonObject = json.getAsJsonObject();String id = jsonObject.get("id").getAsString();String name = jsonObject.get("name").getAsString();return new GsonError.AuthorsBean(id, name);}}private static void test2() {String json = "{\n" +"    \"name\": \"java\",\n" +"    \"authors\": \"\"\n" +"}";Gson gson = new GsonBuilder().registerTypeAdapter(GsonError.class, new GsonErrorDeserializer()) // 注册 GsonErrorDeserializer.registerTypeAdapter(GsonError.AuthorsBean.class, new AuthorDeserializer()) // 注册 AuthorDeserializer.create();GsonError gsonError = gson.fromJson(json, GsonError.class);System.out.println(gsonError); // GsonError{name='java', authors=null}}

这里是把两个 JsonDeserializer 的实现类作为 GsonError 的静态内部类了。通过 jsonObject.get(xxx) 得到的是 JsonElement 对象,可以用它的 isJsonArray() 判断当前的 Json 元素是否为一个数组。

自定义 TypeAdapter

再看第二种方式:

    public static void test3() {String json = "{\n" +"    \"name\": \"java\",\n" +"    \"authors\": \"\"\n" +"}";Gson gson = new GsonBuilder().registerTypeAdapter(GsonError.class,new TypeAdapter<GsonError>() {@Overridepublic void write(JsonWriter out, GsonError value) throws IOException {if (value == null) {out.nullValue();return;}// 写 GsonError 对象out.beginObject(); // {out.name("name").value(value.getName());// 写数组out.name("authors");List<GsonError.AuthorsBean> authors = value.getAuthors();if (authors == null) {// 如果数组为空,这里有多种写法out.value("null");//out.nullValue();//out.value("");} else {// 数组不为空,开始写数组out.beginArray(); // [for (int i = 0; i < authors.size(); i++) {out.beginObject(); // {out.name("id");out.value(authors.get(i).getId());out.name("name");out.value(authors.get(i).getName());out.endObject(); // }}out.endArray(); // ]}out.endObject(); // }}@Overridepublic GsonError read(JsonReader in) throws IOException {// 先进行非空判断if (in.peek() == JsonToken.NULL) {// 如果做 doPeek() 操作拿出的 JsonToken 确实为 null,// 就消费掉这个 JsonToken 并断言它是 null。in.nextNull();return null;}GsonError gsonError = new GsonError();in.beginObject();List<GsonError.AuthorsBean> authors = new ArrayList<>();while (in.hasNext()) {// 取下一个 key,并对 key 的值进行判断switch (in.nextName()) {// 如果 key 的值为 name,那么就把对应的 value 取出设置给 GsonError 的 name 字段。case "name":gsonError.setName(in.nextString());break;case "authors":// 先对 authors 的值进行判断,如果不是 [ 开头,说明是一个无效值。if (in.peek() != JsonToken.BEGIN_ARRAY) {authors = null;in.skipValue(); // 跳过这个 valuecontinue;}// 如果 authors 的值是有效的数组值,开始解析in.beginArray();while (in.hasNext()) {String id = in.nextString();String name = in.nextString();authors.add(new GsonError.AuthorsBean(id, name));}in.endArray();break;}}in.endObject();gsonError.setAuthors(authors);return gsonError;}}).setPrettyPrinting().create();GsonError gsonError = gson.fromJson(json, GsonError.class);System.out.println(gsonError); // GsonError{name='java', authors=null}}

操作过程已经在注释中详细标出,其实就是在 read() 读取到 authors 的值时,对值做一个判断,如果是一个有效的数组值,那么它肯定是以 [ 开始的,如果不是,而是诸如 “”、“null”、null 等形式,就直接跳过这个 value 不处理。

另外在 write() 写值的时候,有个细节,就是如果 List<GsonError.AuthorsBean> authors 为空的时候值该怎么写的问题。示例代码中给出了三种写法,得到的序列化结果分别是:

json">out.value(""):
{"name": "java","authors": ""
}out.nullValue():
{"name": "java"
}out.value("null"):
{"name": "java","authors": "null"
}

可以看到当使用 out.nullValue() 时,生成的 Json 中没有对应的 key-value,即 “authors” 及其 value。

TypeAdapter 的 nullSafe()

倘若你在自定义 TypeAdapter 时不想对繁杂的空值进行处理,那么可以使用 TypeAdapter 的包装方法 nullSafe():

  /*** 此包装方法可使类型适配器具有空容忍能力。通常,需要类型适配器来处理写和读方法中的空值。下面* 是通常的做法:* <pre>   {@code** Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,*   new TypeAdapter<Foo>() {*     public Foo read(JsonReader in) throws IOException {*       if (in.peek() == JsonToken.NULL) {*         in.nextNull();*         return null;*       }*       // read a Foo from in and return it*     }*     public void write(JsonWriter out, Foo src) throws IOException {*       if (src == null) {*         out.nullValue();*         return;*       }*       // write src as JSON to out*     }*   }).create();* }</pre>* 使用此方法包装你的类型适配器,可以避免这种对空值的样板处理。以下代码展示了如何* 重写上面的例子。* <pre>   {@code** Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,*   new TypeAdapter<Foo>() {*     public Foo read(JsonReader in) throws IOException {*       // read a Foo from in and return it*     }*     public void write(JsonWriter out, Foo src) throws IOException {*       // write src as JSON to out*     }*   }.nullSafe()).create();* }</pre>* 注意,使用了 nullSafe 之后,就不需要在我们的类型适配器中检查空值了。*/public final TypeAdapter<T> nullSafe() {return new TypeAdapter<T>() {@Override public void write(JsonWriter out, T value) throws IOException {// 如果是空值就不记录这条key-valueif (value == null) {out.nullValue();} else {// 否则就用当前的 TypeAdapter 正常序列化TypeAdapter.this.write(out, value);}}@Override public T read(JsonReader reader) throws IOException {// 如果读到一个空值,就消费掉它  if (reader.peek() == JsonToken.NULL) {reader.nextNull();return null;}// 否则就用当前的 TypeAdapter 正常反序列化return TypeAdapter.this.read(reader);}};}

参考资料:
@SerializedName注解


http://www.ppmy.cn/server/151903.html

相关文章

PT中的report_timing的计算--实例

目录 1.建立时间与保持时间2.输入延迟和输出延时3.时序计算实战3.1.输入延时&#xff1a;最大输入延时3. 2.输入延迟&#xff1a;最小输入延迟3.3.输出延迟&#xff1a;最大输出延迟3.4.输出延迟&#xff1a;最小输出延迟3.5.建立时间/保持时间和输入延时/输出延时之间的关系 1…

【漏洞复现】CVE-2023-29944 Expression Injection

漏洞信息 NVD - cve-2023-29944 Metersphere v1.20.20-lts-79d354a6 is vulnerable to Remote Command Execution. The system command reverse-shell can be executed at the custom code snippet function of the metersphere system workbench. 背景介绍 MeterSphere is…

[Unity Shader] 【图形渲染】Unity Shader的种类1-深入理解表面着色器(Surface Shader)

在 Unity 中,Shader 是图形渲染管线的核心部分,而表面着色器(Surface Shader)是一种高级抽象形式的着色器,使得编写和管理着色器变得更加简便。尽管表面着色器背后实际上是使用顶点/片元着色器(Vertex/Fragment Shader)进行渲染,但表面着色器的编写方式大大减少了开发者…

【Linux】磁盘空间莫名消失,找不到具体原因的思路

磁盘空间莫名消失&#xff0c;找不到具体原因的思路 先说下常见的几种原因&#xff1a; 1、删除的文件未释放空间 2、日志或过期文件未及时清理 3、inode导致 4、隐藏文件夹或者目录 6、磁盘碎片 最后一种单独介绍。 环境&#xff1a;情况是根分区&#xff08;/&#xf…

技术速递|.NET 9 简介

作者&#xff1a;.NET 团队 排版&#xff1a;Alan Wang 今天&#xff0c;我们非常激动地宣布 .NET 9的发布&#xff0c;这是迄今为止最高效、最现代、最安全、最智能、性能最高的 .NET 版本。这是来自世界各地数千名开发人员又一年努力的成果。这个新版本包括数千项性能、安全和…

如何在 Ubuntu 22.04 服务器上安装 Jenkins

简介 Jenkins 是一个非常流行的免费自动化工具&#xff0c;每个人都应该了解它。DevOps 工程师使用它来自动化代码构建、测试和部署。本文将重点介绍如何在新的 Ubuntu LTS 版本&#xff0c;即 Ubuntu 22.04 中安装 Jenkins。 但在此之前&#xff0c;让我们快速讨论一下 Jenk…

Electron -- Electron Fiddle(一)

Electron Fiddle 是一个由 Electron 团队开发的开源工具&#xff0c;它允许开发者快速创建、运行和调试 Electron 应用。这个工具提供了一个简洁的界面&#xff0c;使用户无需配置复杂的开发环境&#xff0c;就能快速体验和学习 Electron。强烈建议将其安装为学习工具。 学习它…

基于 SOME/IP 的动态服务发现与调用:原理、实现与示例全解析

一、 SOME/IP 动态服务发现与调用例子代码 功能描述 服务发布程序: 提供两个远程服务,每个服务用唯一的 Service ID 和 Method ID 标识。根据客户端请求的 Service ID 和 Method ID 调用对应的处理函数。使用 Skeleton 实现服务的注册和响应。服务发现与调用程序: 动态发现…