Java Review - Spring BeanUtils 踩坑记

文章目录

  • 概述
  • Spring BeanUtils基本使用
  • Code
    • 忽略了属性类型导致拷贝失败
      • 同一字段在不同的类中定义的类型不一致
      • 同一个字段分别使用包装类和基本类型且没有传递实际值
      • 布尔类型的属性分别使用了基本类型和包装类型且属性名使用is开头
    • null值覆盖导致数据异常
    • 内部类数据无法成功拷贝
    • 浅拷贝 vs 深拷贝
    • 引入了错误的包
    • Performance - BeanUtils vs 原生set
  • Apache Commons BeanUtils

在这里插入图片描述


概述

Spring BeanUtils 是 Spring 框架中的一部分,它提供了一套用于简化 Java 对象属性操作的工具类。尽管它的名字暗示了它可能与 Java Bean 相关,但实际上它并不操作 Java Bean 本身,而是操作对象的属性。

BeanUtils 的核心功能是提供属性复制的方法,这在需要将一个对象的属性值复制到另一个对象时非常有用。

Spring BeanUtils 的主要功能如下:

  1. 属性复制copyProperties 方法可以将一个对象的属性值复制到另一个对象中,前提是这两个对象中必须存在相同名称和类型的属性。
  2. 忽略特定属性copyProperties 方法可以指定一个或多个属性不被复制,通过传递一个字符串数组或单个字符串参数来实现。
  3. 类型匹配:Spring BeanUtils 会在复制属性时检查源对象和目标对象的属性类型是否匹配,如果不匹配,则不会复制该属性。
  4. 编辑域限制:可以指定哪些类及其父类中的属性可以被复制,通过传递一个 Class<?> 参数来实现。

使用 Spring BeanUtils 的好处是能够减少样板代码,提高代码的可读性和可维护性。例如,当你需要创建一个新对象并将其设置为与另一个对象相同的状态时,使用 BeanUtils 可以避免手动设置每个属性。

Spring BeanUtils 的使用场景非常广泛,尤其在需要对象间属性同步或数据传输对象(Data Transfer Object, DTO)转换时,它提供了一个简单而有效的解决方案。在 Spring MVC 中,它也常用于将请求参数映射到服务层的对象中。

需要注意的是,Spring BeanUtils 和 Apache Commons BeanUtils 是两个不同的库,虽然它们都提供了类似的功能,但在使用时需要明确区分。Spring 的 BeanUtils 通常被认为在性能上进行了优化,并且与 Spring 框架的其他部分集成得更好。


Spring BeanUtils基本使用

基本使用很简单,这里就不演示了,主要是熟悉下API即可 。
在这里插入图片描述

可以看下面的链接。

Spring - Copying properties using BeanUtils


Code

在这里插入图片描述

请注意看注释


忽略了属性类型导致拷贝失败

同一字段在不同的类中定义的类型不一致

两个Entity

同样为id , 一个是String类型,一个是Long类型 , 此时如果使用BeanUtils.copyProperties进行拷贝,会出现拷贝失败的现象,导致对应的字段为null

package com.artisan.bootbeanutils.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Source {

    // id 类型为 String
    private String id;
    private String username;
}
    
package com.artisan.bootbeanutils.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Target {

    // id 类型为 Long
    private Long id;
    private String username;
}
    

单元测试

package com.artisan.bootbeanutils;

import com.artisan.bootbeanutils.entity.Source;
import com.artisan.bootbeanutils.entity.Target;
import com.artisan.bootbeanutils.entity2.SourceWrappedValue;
import com.artisan.bootbeanutils.entity2.TargetPrimitiveValue;
import com.artisan.bootbeanutils.entity3.SourceBoolean;
import com.artisan.bootbeanutils.entity3.TargetBoolean;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;

/**
 * 属性类型不一致导致拷贝失败
 */
@SpringBootTest
class BootBeanUtilsApplicationTests1 {


    /**
     * 同一属性的类型不同
     * <p>
     * 在开发中,很可能会出现同一字段在不同的类中定义的类型不一致
     * 例如ID,可能在A类中定义的类型为Long,在B类中定义的类型为String,
     * 此时如果使用BeanUtils.copyProperties进行拷贝,会出现拷贝失败的现象,导致对应的字段为null
     */
    @Test
    public void testDiffPropertyType() {
        // Source 和 Target  虽然都有 id属性,但类型却不同 一个为String  一个为Long
        Source source = new Source("1", "artisan");
        Target target = new Target();

        // 通过BeanUtils的copyProperties方法完成对象之间属性的拷贝
        BeanUtils.copyProperties(source, target);

		System.out.println(source);
        System.out.println(target);

        // 输出
        Assert.notNull(target.getUsername(), "copy过来的username属性不应为null, 请检查");
        Assert.notNull(target.getId(), "copy过来的id属性不应为null, 请检查");

    }
}

在这里插入图片描述


同一个字段分别使用包装类和基本类型且没有传递实际值

两个Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SourceWrappedValue {

    // 包装类型
    private Long id;
    private String username;
}
    
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TargetPrimitiveValue {

    // 基本类型
    private long id;
    private String username;
}
    

单元测试

 /**
     * 如果同一个字段分别使用包装类和基本类型,在没有传递实际值的时候,会出现异常
     * <p>
     * 在没有传递实际值的时候,会出现异常
     * 在没有传递实际值的时候,会出现异常
     * 在没有传递实际值的时候,会出现异常
     * </p>
     */
    @Test
    public void testWrappedValue() {
        // 在传递了实际的值的情况下, 不会抛出异常
        // 在传递了实际的值的情况下, 不会抛出异常
        // 在传递了实际的值的情况下, 不会抛出异常
        SourceWrappedValue wrappedValue = new SourceWrappedValue(1L, "artisan");
        TargetPrimitiveValue primitiveValue = new TargetPrimitiveValue();
        // 属性copy
        BeanUtils.copyProperties(wrappedValue, primitiveValue);

        System.out.println(primitiveValue);
        System.out.println(wrappedValue);

        // 输出
        Assert.notNull(primitiveValue.getId(), "copy过来的id属性不应为null, 请检查");
        Assert.notNull(primitiveValue.getUsername(), "copy过来的username属性不应为null, 请检查");

        System.out.println("========================");

        // 在没有传递了实际的值的情况下, 会抛出异常
        // 在没有传递了实际的值的情况下, 会抛出异常
        // 在没有传递了实际的值的情况下, 会抛出异常
        SourceWrappedValue sourceWrappedValue = new SourceWrappedValue();
        sourceWrappedValue.setUsername("artisanTest");
        TargetPrimitiveValue targetPrimitiveValue = new TargetPrimitiveValue();
        // 属性copy (这里就会抛出异常 FatalBeanException: Could not copy property 'id' from source to target)
        BeanUtils.copyProperties(sourceWrappedValue, targetPrimitiveValue);

        System.out.println(sourceWrappedValue);
        System.out.println(targetPrimitiveValue);

        Assert.notNull(targetPrimitiveValue.getId(), "copy过来的id属性不应为null, 请检查");
        Assert.notNull(targetPrimitiveValue.getUsername(), "copy过来的username属性不应为null, 请检查");

    }

在这里插入图片描述


布尔类型的属性分别使用了基本类型和包装类型且属性名使用is开头

两个Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SourceBoolean {
    private Long id;
    private String username;

    // 基本类型,且属性名如果使用is开头
    private boolean isDone;

    // 基本类型,属性名没有使用is开头
    private boolean finished;
}
    
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TargetBoolean {
    private Long id;
    private String username;

    // 包装类型,且属性名如果使用is开头
    private Boolean isDone;

    // 基本类型,属性名没有使用is开头
    private Boolean finished;
}

单元测试

    /**
     * 如果一个布尔类型的属性分别使用了基本类型和包装类型,且属性名如果使用is开头,例如isDone,也会导致拷贝失败
     */
    @Test
    public void testBooleanAndIsXxx() {
        // 在传递了实际的值的情况下, 不会抛出异常
        SourceBoolean sourceBoolean = new SourceBoolean(1L, "artisan", true, false);
        TargetBoolean targetBoolean = new TargetBoolean();
        // 属性copy
        BeanUtils.copyProperties(sourceBoolean, targetBoolean);

        System.out.println(sourceBoolean);
        System.out.println(targetBoolean);
        // 输出
        Assert.notNull(targetBoolean.getIsDone(), "copy过来的isDone属性不应为null, 请检查");
        Assert.notNull(targetBoolean.getFinished(), "copy过来的finished属性不应为null, 请检查");
    }

在这里插入图片描述


null值覆盖导致数据异常

两个Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Source {

    private String id;
    private String username;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Target {
    private String id;
    private String username;
}
    

单元测试


/**
 * null值覆盖导致数据异常
 */
@SpringBootTest
class BootBeanUtilsApplicationTests2 {


    /**
     * 开发过程中,可能会有部分字段拷贝的需求,被拷贝的数据里面如果某些字段有null值存在,
     * 但是对应的需要被拷贝过去的数据的相同字段的值并不为null,
     * 如果直接使用 BeanUtils.copyProperties 进行数据拷贝,就会出现被拷贝数据的null值覆盖拷贝目标数据的字段,导致原有的数据失效
     */
    @Test
    public void testNullCopyToNotNull() {

        // 模拟 username为null
        Source source = new Source();
        source.setId("1");
        System.out.println("original source data: " + source);

        // 模拟 username不为null
        Target target = new Target();
        target.setUsername("artisan");
        System.out.println("original target  data: " + target);

        // 属性copy
        BeanUtils.copyProperties(source, target);

        System.out.println("copied target data: " + target);
        Assert.notNull(target.getUsername(), "username不应为空, 请检查");
    }
}

在这里插入图片描述


内部类数据无法成功拷贝

Entity


@Data
@AllArgsConstructor
@NoArgsConstructor
public class Source {
    private Long id;
    private String username;
    // 内部类
    private InnerClass innerClass;

    @Data
    @AllArgsConstructor
    public static class InnerClass {
        public String innerName;
    }
}
    

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Target {
    private Long id;
    private String username;
    // 内部类
    private InnerClass innerClass;

    @Data
    @AllArgsConstructor
    public static class InnerClass {
        public String innerName;
    }
}
    

单元测试


/**
 * 内部类数据无法成功拷贝
 */
@SpringBootTest
class BootBeanUtilsApplicationTests4 {


    /**
     * 内部类数据无法正常拷贝,即便类型和字段名均相同也无法拷贝成功
     */
    @Test
    public void testInnerClassCopy() {
        //  模拟内部类
        Source source = new Source(1L, "artisan", new Source.InnerClass("artisan-inner"));
        Target target = new Target();

        // 属性copy
        BeanUtils.copyProperties(source, target);

        System.out.println("source data: " + source);
        System.out.println("copied data: " + target);

        Assert.notNull(target.getInnerClass().getInnerName(), "Target#InnerClass#innername不应为空, 请检查");
    }
}

在这里插入图片描述


浅拷贝 vs 深拷贝

Entity

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PojoA {
    private String name;
    private PojoB pojoB;


}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PojoB {
    private String info;

}

单元测试


/**
 * BeanUtils.copyProperties是浅拷贝
 * <p>
 * 一旦在拷贝后修改了原始对象的引用类型的数据,就会导致拷贝数据的值发生异常,这种问题排查起来比较困难
 */
@SpringBootTest
class BootBeanUtilsApplicationTests5 {


    /**
     * 浅拷贝是指创建一个新对象,该对象的属性值与原始对象相同,但对于引用类型的属性,仍然共享相同的引用。
     * 也就是说在浅拷贝下,当原始内容的引用属性值发生变化时,被拷贝对象的引用属性值也会随之发生变化。
     * <p>
     * 深拷贝是指创建一个新对象,该对象的属性值与原始对象相同,包括引用类型的属性。
     * 深拷贝会递归复制引用对象,创建全新的对象,所以深拷贝拷贝后的对象与原始对象完全独立。
     */
    @Test
    public void testShadowCopy() {

        PojoA sourcePojoA = new PojoA("artisan", new PojoB("pojoB"));
        PojoA targetPojoA = new PojoA();

        // 属性复制
        BeanUtils.copyProperties(sourcePojoA, targetPojoA);
        System.out.println(targetPojoA);

        System.out.println("修改源sourcePojoA中对象的属性值,观察targetPojoA中的值是否有变化,用于验证是否是浅复制....");
        // 修改source的属性,观察target属性值的变化
        sourcePojoA.getPojoB().setInfo("测试Modify");
        System.out.println(targetPojoA);

        // 浅拷贝  原始对象值被修改后,目标对象的值也会被修改
        Assert.isTrue("测试Modify".equals(targetPojoA.getPojoB().getInfo()), "浅复制BeanUtils.copyProperties");

    }
}

在这里插入图片描述


引入了错误的包

  <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.4</version>
        </dependency>
package com.artisan.bootbeanutils;

import com.artisan.bootbeanutils.entity5.Source;
import com.artisan.bootbeanutils.entity5.Target;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;

/**
 * 导包错误导致拷贝数据异常
 */
@SpringBootTest
class BootBeanUtilsApplicationTests3 {


    /**
     * 如果项目中同时引入了Spring的beans包和Apache的beanutils包,
     * 在导包的时候,如果导入错误,很可能导致数据拷贝失败,排查起来也不太好发现。
     * <p>
     * 我们通常使用的是Spring包中的拷贝方法
     * <p>
     * //org.springframework.beans.BeanUtils(源对象在左边,目标对象在右边)
     * public static void copyProperties(Object source, Object target) throws BeansException
     * <p>
     * //org.apache.commons.beanutils.BeanUtils(源对象在右边,目标对象在左边)
     * public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException
     */
    @Test
    public void testNullCopyToNotNull() throws InvocationTargetException, IllegalAccessException {

        // 模拟 username为null
        Source source = new Source();
        source.setId("1");

        // 模拟 username不为null
        Target target = new Target();
        target.setUsername("artisan");

        // 属性copy
        BeanUtils.copyProperties(source, target);

        System.out.println("copied data: " + target);
        //Assert.notNull(target.getUsername(), "username不应为空, 请检查");

        System.out.println("============使用Apache Common 的BeanUtils============");

        // Apache的BeanUtils属性copy  --> 源对象在右边,目标对象在左边
        org.apache.commons.beanutils.BeanUtils.copyProperties(target, source);
        System.out.println("copied data: " + target);
        Assert.notNull(target.getUsername(), "username不应为空, 请检查");

    }
}

在这里插入图片描述


Performance - BeanUtils vs 原生set


/**
 * BeanUtils.copyProperties底层是通过反射获取到对象的set和get方法,再通过get、set完成数据的拷贝,整体拷贝效率较低
 */
@SpringBootTest
class BootBeanUtilsApplicationTests6 {


    @Test
    public void testPerformance() {

        PojoA sourcePojoA = new PojoA("artisan", new PojoB("pojoB"));
        PojoA targetPojoA = new PojoA();

        StopWatch stopWatch = new StopWatch("BeanUtils#copyProperties Vs Set");
        stopWatch.start("copyProperties");
        for (int i = 0; i < 50000; i++) {
            BeanUtils.copyProperties(sourcePojoA, targetPojoA);
        }
        stopWatch.stop();


        stopWatch.start("set");
        for (int i = 0; i < 50000; i++) {
            targetPojoA.setPojoB(sourcePojoA.getPojoB());
            targetPojoA.setName(sourcePojoA.getName());
        }
        stopWatch.stop();

        // 打印时间
        System.out.println(stopWatch.prettyPrint());
    }
}

在这里插入图片描述


Apache Commons BeanUtils

Apache Commons BeanUtils 的基本使用
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

根本记不住MySQL进阶查询语句

1 MySQL进阶查询 1.1 MySQL进阶查询的语句 全文以数据库location和Store_Info为实例 ---- SELECT ----显示表格中一个或数个字段的所有数据记录 语法&#xff1a;SELECT "字段" FROM "表名"; select 列名 from 表名 ; ---- DISTINCT ----不显示重复的数…

使用KVM命令集管理虚拟机

14.2.1案例分析 案例环境使用一台物理机器&#xff0c;一台服务器安装CentOS7.3的64位系统&#xff08;即node01&#xff09;&#xff0c;rhel7.1是在宿主机node01中安装的虚拟机。 14.2.2案例实施 1.安装Linux虚拟机 安装过程同上一案例&#xff0c;使用Xshell 远程控制node0…

视频号上怎么开店带货?门槛和注意事项,如下所示

我是王路飞。 视频号上现在也可以开店带货了&#xff08;严格来说从22年就可以了&#xff09;。 我们团队是在22年9月份开始入局视频号电商这个赛道的&#xff0c;在此之前是专注于抖店&#xff0c;目前两个项目都在做。 今天不聊抖店&#xff0c;主要说下视频号上开店带货的…

Win10电脑关闭OneDrive自动同步的方法

在Win10电脑操作过程中&#xff0c;用户想要关闭OneDrive的自动同步功能&#xff0c;但不知道具体要怎么操作&#xff1f;首先用户需要打开OneDrive&#xff0c;然后点击关闭默认情况下将文档保存到OneDrive选项保存&#xff0c;最后关闭在这台电脑上同步设置保存就好了。接下来…

Flink 维表关联方案

Flink 维表关联方案 1、Flink DataStream 关联维表 1&#xff09;概述 1.分类 实时数据库查找关联&#xff08;Per-Record Reference Data Lookup&#xff09; 预加载维表关联&#xff08;Pre-Loading of Reference Data&#xff09; 维表变更日志关联&#xff08;Refere…

微信小程序+前后端开发学习材料

目录结构 全局文件 1.app.json 文件 用来对微信小程序进行全局配置&#xff0c;决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。文件内容为一个 JSON 对象。 1.1 page用于指定小程序由哪些页面组成&#xff0c;每一项都对应一个页面的 路径&#xff08;含文…

【数值分析】Hermite插值

4. Hermite插值 理论和应用中提出的某些插值问题&#xff0c;要求插值函数 p ( x ) {p(x)} p(x) 具有一定的光滑度&#xff0c;即在插值节点处满足一定的导数条件&#xff0c;这类插值问题称为Hermite插值问题。题目大多以三次Hermite插值为主。三次Hermite插值需要四个条件&…

Leetcode的AC指南 —— 字符串/卡码网:55. 右旋字符串

摘要&#xff1a; Leetcode的AC指南 —— 字符串/卡码网&#xff1a;55. 右旋字符串。题目介绍&#xff1a;字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k&#xff0c;请编写一个函数&#xff0c;将字符串中的后面 k 个字…

grep笔记240103

常用选项&#xff1a;&#xff1a; -i&#xff1a;忽略大小写进行匹配。 -v&#xff1a;反向匹配&#xff0c;只打印不匹配的行。 -n&#xff1a;显示匹配行的行号。 -r&#xff1a;递归查找子目录中的文件。 -l&#xff1a;只打印匹配的文件名。 -c&#xff1a;只打印匹配的行…

整理的6个Linux运维脚本

整理的6个Linux运维脚本 1、统计/etc/passwd 中能登录的用户&#xff0c;并将对应在/etc/shadow 中第二列密码提取2、查看当前连接到本机的远程IP地址3、检测本机当前用户是否为超级管理员&#xff08;root&#xff09;4、检查指定目录下是否存在对应文件5、查找 Linux 系统中的…

QT_02 窗口属性、信号槽机制

QT - 窗口属性、信号槽机制 1. 设置窗口属性 窗口设置 1,标题 2,大小 3,固定大小 4,设置图标在 widget.cpp 文件中&#xff1a; //设置窗口大小,此时窗口是可以拉大拉小的 //1参:宽度 //2参:高度 this->resize(800, 600); //设置窗口标题 this->setWindowTitle("…

使用 Palantir 表征单细胞数据中的细胞状态概率

使用 Palantir 表征单细胞数据中的细胞状态概率 写在前面的概览正文内容摘要Introduction结果马尔可夫过程The Palantir algorithm早期人类造血的景观Palantir 概括了预期的造血趋势与轨迹推理算法的比较 讨论 学习文献 写在前面的概览 Palantir算法主要用于模拟细胞分化的轨迹…

Redis缓存穿透,缓存击穿,缓存雪崩

文章目录 Redis缓存穿透&#xff0c;缓存击穿&#xff0c;缓存雪崩1. 缓存穿透1.1 解决方案1&#xff1a;缓存空数据1.2 解决方案2&#xff1a;使用布隆过滤器1.2.1 布隆过滤器介绍 2. 缓存击穿2.1 解决方案1&#xff1a;互斥锁2.2 解决方案2&#xff1a;逻辑过期 3. 缓存雪崩3…

交易逆序对的总数

题目链接 交易逆序对的总数 题目描述 注意点 0 < record.length < 50000 解答思路 本题是归并排序的扩展&#xff0c;可以先进入手撕归并排序了解利用归并排序进行合并时&#xff0c;对于左侧区间当前的首个元素leftNum&#xff0c;不论右侧区间当前的首个元素right…

【智慧地球】星图地球 | 星图地球超算数据工场

当前空天信息处理涉及并发并行的大量计算问题&#xff0c;需要高性能计算、智能计算联合调度&#xff0c;以此来实现多算力融合&#xff1b;而我国算力产业规模快速增长&#xff0c;超算算力资源正需要以任务驱动来统筹。 基于此&#xff0c;中科星图与郑州中心展开紧密合作&a…

使用 Process Explorer 和 Windbg 排查软件线程堵塞案例分享

目录 1、问题说明 2、线程堵塞的可能原因分析 3、使用Windbg和Process Explorer确定线程中发生了死循环 4、根据Windbg中显示的函数调用堆栈去查看源码&#xff0c;找到问题 4.1、在Windbg定位发生死循环的函数的方法 4.2、在Windbg中查看变量的值去辅助分析 4.3、是循环…

抖店申请流程是什么?

我是电商珠珠 想要入驻抖店的人很多&#xff0c;但是知道流程的新手却没有几个。 从开店资料到入驻流程&#xff0c;我来具体的跟大家讲一讲。 第一个&#xff0c;新手开店资质 1、营业执照 营业执照是入驻门槛之一&#xff0c;营业执照类型分为两类&#xff0c;一类为企业…

快速批量运行命令

Ansible 是 redhat 提供的自动化运维工具&#xff0c;它是 Python编写&#xff0c;可以通过 pip 安装。 pip install ansible 它通过任务(task)、角色(role)、剧本(playbook) 组织工作项目&#xff0c;适用于批量化系统配置、软件部署等需要复杂操作的工作。 但对于批量运行命…

进程的程序替换(exec函数)【Linux】

进程的程序替换详解exec函数【Linux】 程序替换的原理exec系列函数函数理解命令理解&#xff08;助记&#xff09; 关于程序替换中环境变量的解释exec函数之间的关系exec函数的使用execlexeclpexecleexecv 程序替换的原理 进程的程序替换就是让子进程执行新程序&#xff0c; 执…

使用华为云鲲鹏弹性云服务器部署Discuz

本实验将在华为云鲲鹏弹性云服务器CentOS系统的实例上&#xff0c;部署Discuz!项目&#xff0c;并进行初步的安装测试。 注意&#xff1a;官网文档有些链接失效&#xff0c;本文在官方文档的基础上作出修改&#xff0c;具体参见Discuz安装这一步 操作前提&#xff1a;登录华为…