原型模式(Prototype Pattern)详解

文章目录

    • 1. 什么是原型模式?
    • 2. 为什么需要原型模式?
    • 3. 原型模式的结构
    • 4. 原型模式的基本实现
      • 4.1 基础示例:简单的原型模式
      • 4.2 使用Java的Cloneable接口
    • 5. 深拷贝与浅拷贝
      • 5.1 浅拷贝(Shallow Copy)
      • 5.2 深拷贝(Deep Copy)
        • 5.2.1 通过递归复制实现深拷贝
        • 5.2.2 通过序列化实现深拷贝
    • 6. 原型模式的实际应用场景
      • 6.1 数据对象的复制
      • 6.2 对象的缓存
      • 6.3 Java中的实际应用
    • 7. 原型模式与其他设计模式的区别
      • 7.1 原型模式 vs 工厂模式
      • 7.2 原型模式 vs 建造者模式
    • 8. 原型模式的优缺点
      • 8.1 优点
      • 8.2 缺点
    • 9. 何时使用原型模式?
    • 10. 常见问题及解决方案
      • 10.1 问题:如何处理深拷贝中的循环引用?
      • 10.2 问题:如何克隆不可变对象?
      • 10.3 问题:如何保证克隆对象和原型对象的一致性?
    • 11. 总结

1. 什么是原型模式?

原型模式是一种创建型设计模式,它允许我们通过复制(克隆)现有对象来创建新对象,而不是通过使用构造函数创建。原型模式的核心思想是基于现有对象创建新的对象,而不是从零开始创建

这种模式特别适用于创建对象成本较高的场景,或者需要创建大量相似对象的情况。通过克隆现有对象,可以避免重新执行初始化过程,从而提高性能。

2. 为什么需要原型模式?

在以下情况下,原型模式特别有用:

  1. 当创建对象的过程很昂贵或复杂时(如需要进行数据库操作或文件I/O)
  2. 当需要创建的对象与现有对象差别不大时
  3. 当需要避免使用构造函数创建对象的限制时
  4. 当需要保存对象状态并在需要时恢复时
  5. 当系统需要独立于对象的创建方式时

3. 原型模式的结构

原型模式通常包含以下角色:

  1. 原型接口(Prototype):声明克隆自身的方法
  2. 具体原型(Concrete Prototype):实现克隆方法的类
  3. 客户端(Client):使用原型实例创建新对象的类

4. 原型模式的基本实现

4.1 基础示例:简单的原型模式

首先,我们定义一个原型接口:

// 原型接口
public interface Prototype {Prototype clone();
}

然后,实现具体原型类:

// 具体原型类
public class ConcretePrototype implements Prototype {private String field;public ConcretePrototype(String field) {this.field = field;}// 用于测试的getter和setterpublic String getField() {return field;}public void setField(String field) {this.field = field;}@Overridepublic Prototype clone() {return new ConcretePrototype(this.field);}@Overridepublic String toString() {return "ConcretePrototype [field=" + field + "]";}
}

最后,客户端代码:

// 客户端代码
public class Client {public static void main(String[] args) {// 创建原型对象ConcretePrototype prototype = new ConcretePrototype("原始值");System.out.println("原型对象: " + prototype);// 克隆原型对象ConcretePrototype clone = (ConcretePrototype) prototype.clone();System.out.println("克隆对象: " + clone);// 修改克隆对象的属性clone.setField("修改后的值");System.out.println("修改后的克隆对象: " + clone);System.out.println("原型对象: " + prototype); // 原型对象不受影响}
}

输出结果:

原型对象: ConcretePrototype [field=原始值]
克隆对象: ConcretePrototype [field=原始值]
修改后的克隆对象: ConcretePrototype [field=修改后的值]
原型对象: ConcretePrototype [field=原始值]

4.2 使用Java的Cloneable接口

Java提供了Cloneable接口和Object.clone()方法来支持原型模式。下面是使用Java内置机制的例子:

// 使用Java的Cloneable接口实现原型模式
public class Person implements Cloneable {private String name;private int age;private String address;public Person(String name, int age, String address) {this.name = name;this.age = age;this.address = address;}// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }public String getAddress() { return address; }public void setAddress(String address) { this.address = address; }@Overridepublic Person clone() {try {// 调用Object的clone方法return (Person) super.clone();} catch (CloneNotSupportedException e) {// 实现了Cloneable接口的类不应该抛出这个异常throw new AssertionError("这不应该发生", e);}}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";}
}

使用示例:

public class CloneableDemo {public static void main(String[] args) {// 创建原型对象Person original = new Person("张三", 30, "北京市海淀区");System.out.println("原始对象: " + original);// 克隆对象Person clone = original.clone();System.out.println("克隆对象: " + clone);// 修改克隆对象clone.setName("李四");clone.setAge(25);clone.setAddress("上海市浦东新区");// 查看修改后的结果System.out.println("修改后的克隆对象: " + clone);System.out.println("原始对象: " + original); // 原始对象不变}
}

输出结果:

原始对象: Person [name=张三, age=30, address=北京市海淀区]
克隆对象: Person [name=张三, age=30, address=北京市海淀区]
修改后的克隆对象: Person [name=李四, age=25, address=上海市浦东新区]
原始对象: Person [name=张三, age=30, address=北京市海淀区]

5. 深拷贝与浅拷贝

在原型模式中,有两种类型的复制:

5.1 浅拷贝(Shallow Copy)

浅拷贝只复制对象的基本属性,对于引用类型,只复制引用而不复制引用指向的对象。Java的Object.clone()方法默认执行浅拷贝。

// 包含引用类型的类
public class Employee implements Cloneable {private String name;private Department department; // 引用类型public Employee(String name, Department department) {this.name = name;this.department = department;}// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public Department getDepartment() { return department; }public void setDepartment(Department department) { this.department = department; }// 浅拷贝@Overridepublic Employee clone() {try {return (Employee) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}@Overridepublic String toString() {return "Employee [name=" + name + ", department=" + department + "]";}
}// 引用类型
public class Department {private String name;public Department(String name) {this.name = name;}public String getName() { return name; }public void setName(String name) { this.name = name; }@Overridepublic String toString() {return "Department [name=" + name + "]";}
}

浅拷贝示例:

public class ShallowCopyDemo {public static void main(String[] args) {// 创建部门Department hr = new Department("人力资源部");// 创建员工Employee original = new Employee("张三", hr);System.out.println("原始员工: " + original);// 浅拷贝Employee clone = original.clone();System.out.println("克隆员工: " + clone);// 修改克隆对象的引用类型属性clone.getDepartment().setName("财务部");// 查看修改后的结果System.out.println("修改后的克隆员工: " + clone);System.out.println("原始员工: " + original); // 原始对象的引用类型也被修改了}
}

输出结果:

原始员工: Employee [name=张三, department=Department [name=人力资源部]]
克隆员工: Employee [name=张三, department=Department [name=人力资源部]]
修改后的克隆员工: Employee [name=张三, department=Department [name=财务部]]
原始员工: Employee [name=张三, department=Department [name=财务部]]

注意:修改克隆对象的引用类型属性会影响原始对象。

5.2 深拷贝(Deep Copy)

深拷贝不仅复制对象本身,还复制对象包含的所有引用类型的属性。有两种常见的实现方式:

5.2.1 通过递归复制实现深拷贝
// 实现Cloneable接口的引用类型
public class Department implements Cloneable {private String name;public Department(String name) {this.name = name;}public String getName() { return name; }public void setName(String name) { this.name = name; }@Overrideprotected Department clone() {try {return (Department) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}@Overridepublic String toString() {return "Department [name=" + name + "]";}
}// 深拷贝实现
public class Employee implements Cloneable {private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public Department getDepartment() { return department; }public void setDepartment(Department department) { this.department = department; }// 深拷贝@Overridepublic Employee clone() {try {Employee cloned = (Employee) super.clone();// 对引用类型也进行克隆cloned.department = this.department.clone();return cloned;} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}@Overridepublic String toString() {return "Employee [name=" + name + ", department=" + department + "]";}
}
5.2.2 通过序列化实现深拷贝

使用序列化和反序列化实现深拷贝是一种更通用的方法,特别适用于对象层次较深的情况:

import java.io.*;// 必须实现Serializable接口
public class Department implements Serializable {private static final long serialVersionUID = 1L;private String name;public Department(String name) {this.name = name;}public String getName() { return name; }public void setName(String name) { this.name = name; }@Overridepublic String toString() {return "Department [name=" + name + "]";}
}// 实现Serializable接口
public class Employee implements Serializable {private static final long serialVersionUID = 1L;private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}// Getters and Setterspublic String getName() { return name; }public void setName(String name) { this.name = name; }public Department getDepartment() { return department; }public void setDepartment(Department department) { this.department = department; }// 通过序列化实现深拷贝public Employee deepCopy() {try {// 写入字节流ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);// 从字节流读取ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (Employee) ois.readObject();} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic String toString() {return "Employee [name=" + name + ", department=" + department + "]";}
}

使用序列化深拷贝的示例:

public class DeepCopySerializationDemo {public static void main(String[] args) {// 创建部门Department hr = new Department("人力资源部");// 创建员工Employee original = new Employee("张三", hr);System.out.println("原始员工: " + original);// 深拷贝Employee clone = original.deepCopy();System.out.println("克隆员工: " + clone);// 修改克隆对象的引用类型属性clone.getDepartment().setName("财务部");// 查看修改后的结果System.out.println("修改后的克隆员工: " + clone);System.out.println("原始员工: " + original); // 原始对象不受影响}
}

输出结果:

原始员工: Employee [name=张三, department=Department [name=人力资源部]]
克隆员工: Employee [name=张三, department=Department [name=人力资源部]]
修改后的克隆员工: Employee [name=张三, department=Department [name=财务部]]
原始员工: Employee [name=张三, department=Department [name=人力资源部]]

6. 原型模式的实际应用场景

6.1 数据对象的复制

当需要创建大量相似但略有差异的数据对象时,原型模式非常有用:

public class DataObject implements Cloneable {private String id;private String name;private String description;private Date creationDate;// 构造函数和其他初始化代码...@Overrideprotected DataObject clone() {try {DataObject clone = (DataObject) super.clone();// 对日期进行深拷贝clone.creationDate = (Date) this.creationDate.clone();return clone;} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}// 创建一个带有不同ID的新实例public DataObject cloneWithNewId(String newId) {DataObject clone = this.clone();clone.id = newId;return clone;}
}

6.2 对象的缓存

原型模式可以用于实现对象缓存,避免重复创建开销大的对象:

import java.util.HashMap;
import java.util.Map;// 原型管理器
public class PrototypeManager {private static Map<String, Prototype> prototypes = new HashMap<>();// 注册原型public static void register(String key, Prototype prototype) {prototypes.put(key, prototype);}// 获取原型的克隆public static Prototype getPrototype(String key) {Prototype prototype = prototypes.get(key);if (prototype != null) {return prototype.clone();}return null;}
}// 原型接口
public interface Prototype {Prototype clone();
}// 具体原型
public class Document implements Prototype {private String content;private String format;public Document(String content, String format) {this.content = content;this.format = format;// 假设初始化过程非常耗时try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic Document clone() {return new Document(this.content, this.format);}// 其他方法...
}

使用示例:

public class PrototypeManagerDemo {public static void main(String[] args) {// 初始化原型Document wordDoc = new Document("示例内容", "Word");Document pdfDoc = new Document("示例内容", "PDF");// 注册原型PrototypeManager.register("word", wordDoc);PrototypeManager.register("pdf", pdfDoc);// 使用原型创建对象long start = System.currentTimeMillis();Document doc1 = (Document) PrototypeManager.getPrototype("word");Document doc2 = (Document) PrototypeManager.getPrototype("pdf");long end = System.currentTimeMillis();System.out.println("克隆两个文档耗时: " + (end - start) + "毫秒"); // 几乎不耗时// 对比直接创建start = System.currentTimeMillis();Document doc3 = new Document("示例内容", "Word");Document doc4 = new Document("示例内容", "PDF");end = System.currentTimeMillis();System.out.println("直接创建两个文档耗时: " + (end - start) + "毫秒"); // 约2000毫秒}
}

6.3 Java中的实际应用

Java标准库中也有一些类使用了原型模式:

  1. Java集合框架中的clone()方法
  2. Object类提供的clone()方法

7. 原型模式与其他设计模式的区别

7.1 原型模式 vs 工厂模式

原型模式工厂模式
通过复制现有对象创建新对象通过工厂类创建对象
不需要关心类的具体实现细节需要知道具体的产品类
适合创建成本高的对象适合创建多种不同类型的对象

7.2 原型模式 vs 建造者模式

原型模式建造者模式
通过克隆创建对象分步骤创建复杂对象
基于现有对象创建从零开始构建对象
适合创建相似对象适合构建复杂对象

8. 原型模式的优缺点

8.1 优点

  1. 减少对象创建的成本,特别是创建过程复杂或耗时的情况
  2. 隐藏对象创建的细节
  3. 允许在运行时添加和删除产品
  4. 提供了一种快速创建复杂对象的方法
  5. 可以避免构造函数的限制,如创建不可变对象的多个变体

8.2 缺点

  1. 对象包含循环引用时的克隆可能很复杂
  2. 深拷贝实现复杂,特别是对象结构较深时
  3. 克隆包含引用类型的对象时需要格外小心(浅拷贝问题)
  4. 对于一些有状态的对象,可能需要重置状态

9. 何时使用原型模式?

以下情况适合使用原型模式:

  1. 当创建对象的代价较大,且创建的对象之间差异较小时
  2. 当需要避免构建与产品类层次平行的工厂类层次时
  3. 当对象的类在运行时才确定时
  4. 当系统需要独立于产品如何创建、组合和表示时
  5. 当需要保存对象的状态,并在将来需要恢复到这个状态时

10. 常见问题及解决方案

10.1 问题:如何处理深拷贝中的循环引用?

解决方案:使用哈希表记录已经克隆过的对象,避免重复克隆。

import java.util.HashMap;
import java.util.Map;public class DeepCopyUtil {private static Map<Object, Object> clonedObjects = new HashMap<>();public static <T> T deepCopy(T object) {// 如果对象已经被克隆,直接返回克隆的对象if (clonedObjects.containsKey(object)) {return (T) clonedObjects.get(object);}// 这里实现深拷贝逻辑// ...// 将克隆对象添加到哈希表中clonedObjects.put(object, clonedObject);return clonedObject;}
}

10.2 问题:如何克隆不可变对象?

解决方案:对于不可变对象,可以直接返回对象本身,不需要克隆。

public class ImmutableObject implements Cloneable {private final String data;public ImmutableObject(String data) {this.data = data;}public String getData() {return data;}@Overridepublic ImmutableObject clone() {// 对于不可变对象,可以直接返回对象本身return this;}
}

10.3 问题:如何保证克隆对象和原型对象的一致性?

解决方案:在克隆过程中添加验证逻辑,确保克隆对象符合要求。

@Override
public Object clone() {try {Object clone = super.clone();// 添加验证逻辑validate(clone);return clone;} catch (CloneNotSupportedException e) {throw new AssertionError(e);}
}private void validate(Object clone) {// 验证逻辑// ...if (!isValid(clone)) {throw new IllegalStateException("克隆对象验证失败");}
}

11. 总结

原型模式是一种强大的创建型设计模式,它允许通过克隆现有对象来创建新对象,避免了昂贵的对象创建过程。在Java中,我们可以通过实现Cloneable接口和重写clone()方法来实现原型模式。

原型模式的关键点在于理解浅拷贝和深拷贝的区别,以及如何根据具体需求选择合适的克隆方式。对于包含引用类型的复杂对象,通常需要实现深拷贝以避免潜在的问题。

通过本文的多个示例,希望初学者能够对原型模式有一个全面深入的理解,并能在日常编程中灵活运用这一模式。

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

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

相关文章

掉馅饼,八分之一到二分之一:《分析模式》漫谈59

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第6章“存货和会计”原文&#xff1a; The transactions creation would then be the only place that could create entries. ... Providing only the trans…

使用Python和Pandas实现的Amazon Redshift权限检查与SQL生成用于IT审计

import pandas as pd import psycopg2 from psycopg2 import sql# 连接Redshift conn psycopg2.connect(hostyour-cluster.endpoint.redshift.amazonaws.com,port5439,dbnamedev,useradmin,passwordyour-password )# 权限检查函数 def check_redshift_permissions(conn):"…

Cribl 数据脱敏 更多方法 MASK (三)

我做过好几个cribl 数据脱敏的实验: Cribl 脱敏mask-CSDN博客

Android Studio下载安装教程

## 什么是Android Studio Android Studio是Google官方推出的Android应用开发集成环境(IDE)&#xff0c;基于IntelliJ IDEA开发&#xff0c;专门用于Android应用开发。它包含了代码编辑器、可视化布局编辑器、应用性能分析工具、模拟器等功能&#xff0c;为开发者提供了一站式的…

如何测试登录模块?全面测试思路解析

思路如下: 面试官问"如何测试一个登录模块?"时,考察的是你的测试思维是否全面,能否覆盖功能、安全、性能、兼容性等多个维度。下面我会从不同角度详细展开,确保回答既系统又深入。 1. 功能测试(Functional Testing) 1.1 正常流程测试 ✅ 正确的用户名+密码:…

MySQL基础篇 | 数据库概述及在TencentOS中安装MySQL8.0.42版本

MySQL基础篇 | 在TencentOS中安装MySQL8.0.42版本 1. 数据库概述2. 部署前准备工作2.1. 安装依赖包2.2. GCC版本升级3. MySQL服务部署3.1. 编译部署MySQL3.2. 初始化数据库3.3. 启动数据库4. 数据库配置4.1 配置环境变量4.2. 首次登录设置1. 数据库概述 SQL Server:SQL Server…

Angular教程前言:历史、安装与用途

Angular 是一个强大且流行的开源前端 Web 应用程序框架&#xff0c;由 Google 开发并维护 1。它在现代 Web 开发中占据着重要的地位&#xff0c;尤其在构建动态、高效且可扩展的 Web 应用程序方面表现出色&#xff0c;特别适用于单页应用程序 (SPA) 和复杂的用户界面 1。本教程…

systemd和OpenSSH

1 systemd 1.1 配置文件 /etc/systemd/system /lib/systemd/system /run/systemd/system /usr/lib/systemd/user 1.2 commands systemctl list-unit-files | grep enable systemctl cat dlt-daemon.service systemctl cat dlt-system.service systemctl show dlt-daemon.ser…

如何实现一个可视化的文字编辑器(C语言版)?

一、软件安装 Visual Studio 2022 Visual Studio 2022 是微软提供的强大集成开发环境&#xff08;IDE&#xff09;&#xff0c;广泛用于C/C、C#、Python等多种编程语言的开发。它提供了许多强大的工具&#xff0c;帮助开发者编写、调试和优化代码。 1.下载 Visual Studio 202…

ArrayList的特点及应用场景

ArrayList的特点及应用场景 一、ArrayList核心特点 基于动态数组实现 底层使用Object[]数组存储元素 默认初始容量为10 扩容机制&#xff1a;每次扩容为原来的1.5倍&#xff08;int newCapacity oldCapacity (oldCapacity >> 1)&#xff09; 快速随机访问 实现了R…

深挖Java基础之:变量与类型

今天我要介绍的是在Java中对变量和类型的一些相关知识点的介绍&#xff0c;包括对基本数据类型&#xff0c;引用类型&#xff0c;变量命名规则和类型转换以及其注意事项的解明。 java变量与类型&#xff1a;Java 是静态类型语言&#xff0c;变量必须先声明类型后使用。变量是存…

数据结构与算法学习笔记(Acwing提高课)----动态规划·背包模型(一)

数据结构与算法学习笔记----动态规划背包模型(一) author: 明月清了个风 first publish time: 2025.5.1 ps⭐️背包模型是动态规划中的重要模型&#xff0c;基础课中已对背包模型的几种模版题有了讲解&#xff0c;[链接在这](数据结构与算法学习笔记----背包问题_有 n 件物品…

Java关键字解析

Java关键字是编程语言中具有特殊含义的保留字&#xff0c;不能用作标识符&#xff08;如变量名、类名等&#xff09;。Java共有50多个关键字&#xff08;不同版本略有差异&#xff09;&#xff0c;下面我将分类详细介绍这些关键字及其使用方式。 一、数据类型相关关键字 1. 基…

vue自定义表头内容excel表格导出

1、安装 npm install xlsx file-saver 2、使用 import * as XLSX from xlsx import { saveAs } from file-saverconst exportAccounts (data) > {// 将对象数组转换为 worksheetconst worksheet XLSX.utils.json_to_sheet(data)// 创建 workbook 并附加 sheetconst wor…

鸿蒙NEXT开发组件截图和窗口截图工具类SnapshotUtil(ArkTs)

import { image } from kit.ImageKit; import { componentSnapshot, window } from kit.ArkUI; import { AppUtil } from ./AppUtil; import { ArrayUtil } from ./ArrayUtil;/*** 组件截图和窗口截图工具类* author 鸿蒙布道师* since 2025/04/28*/ export class SnapshotUtil…

C#与SVN的深度集成:实现版本控制自动化管理​

目录 1. 环境准备 2. 创建 C# 工程 3. 引用 SharpSvn 库 4. 编写代码 1. 环境准备 2. 创建 C# 工程 3. 引用 SharpSvn 库 4. 编写代码 5. 代码说明 6. 注意事项 1. 环境准备 首先&#xff0c;需要安装 SharpSvn 库。可以从 SharpSvn 官方网站 下载适合 .NET Framewor…

本文不定期更新,用于收录各种怪异的python脚本

1.计算自然数对数底 a b 1 for n in range(1, 1001):a a * n 1b b * n t a % br . for i in range(1, 1001):t 10if t < b:r 0else:r str(t // b)t % bprint(str(a//b) r) 得到 2.7182818284590452353602874713526624977572470936999595749669676277240766303…

日志之ClickHouse部署及替换ELK中的Elasticsearch

文章目录 1 ELK替换1.1 Elasticsearch vs ClickHouse1.2 环境部署1.2.1 zookeeper 集群部署1.2.2 Kafka 集群部署1.2.3 FileBeat 部署1.2.4 clickhouse 部署1.2.4.1 准备步骤1.2.4.2 添加官方存储库1.2.4.3 部署&启动&连接1.2.4.5 基本配置服务1.2.4.6 测试创建数据库和…

2025年大一ACM训练-搜索

2025年大一ACM训练-搜索 前期知识&#xff1a;DFS&#xff0c;本文搜索题解法以深度优先搜索为主 1.1 DFS 的定义 深度优先搜索&#xff08;Depth-First Search&#xff09;是一种用于遍历树或图的算法。核心思想是尽可能“深入”访问图的每个节点&#xff0c;直到无法继续前进…

Nginx核心功能02

目录 一&#xff0c;正向代理 1&#xff0c;编译安装Nginx &#xff08;1&#xff09;安装支持软件 &#xff08;2&#xff09;创建运行用户&#xff0c;组和日志目录 &#xff08;3&#xff09;编译安装Nginx &#xff08;4&#xff09;添加Nginx系统服务 2&#xff0c…