Java字节码技术javassist

article/2025/9/15 5:53:17

一、Javassist入门

(一)Javassist是什么

Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。Javassist使用户不必关心字节码相关的规范也是可以编辑类文件的。

(二)Javassist核心API

在Javassist中每个需要编辑的class都对应一个CtCLass实例,CtClass的含义是编译时的类(compile time class),这些类会存储在Class Pool中(Class poll是一个存储CtClass对象的容器)。
CtClass中的CtField和CtMethod分别对应Java中的字段和方法。通过CtClass对象即可对类新增字段和修改方法等操作了。

(三)简单示例

为了减少演示的复杂度,示例以及之后的操作,都在Maven项目下进行,因为我们可以直接引入依赖就可以达到我们导包的目的,很方便,不用再去下载jar包,然后自己手动导入了。

1、创建一个maven项目

如果你使用的是IDEA,可以像我一样;如果是其他工具,可以自行百度,或者按照自己的经验来创建即可。

2、创建一个测试类,代码如下:

package com.ssdmbbl.javassist;import javassist.*;import java.io.IOException;
/*** @author zhenghui* @description: Javassist使用演示测试* @date 2021/4/6 6:38 下午*/
public class JavassistTest {public static void main(String[] args) throws CannotCompileException, IOException {ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.makeClass("com.ssdmbbl.javassist.Hello");ctClass.writeFile("./");}
}

当运行这个代码的时候,可以看到已经在项目的根目录下创建了一个“com.ssdmbbl.javassist”包,在这个包下创建了“Hello.java”的java文件。

内容如下:

package com.ssdmbbl.javassist;public class Hello {public Hello() {}
}

二、Javassist操作字节码示例

回想一下,咱们如果对一个Java正常操作的话,大概存在哪些操作呢?

  • 1、咱们会对一个类添加字段;
  • 2、咱们会对一个类添加方法;

好像没其他的了吧。其余的就是在方法里写代码了呗。

(一)新增一个方法

咱们继续套用上面简单示例的代码,在此基础之上进行新增一个方法。

新增方法的名字为"hello1",传递两个参数分别为int和double类型,并且没有返回值。

package com.ssdmbbl.javassist;import javassist.*;import java.io.IOException;
import java.net.URL;/*** @author zhenghui* @description: Javassist使用演示测试* @date 2021/4/6 6:38 下午*/
public class JavassistTest2 {public static void main(String[] args) throws CannotCompileException, IOException {//找到本文件的路径,与之保存在一起URL resource = JavassistTest2.class.getClassLoader().getResource("");String file = resource.getFile();System.out.println("文件存储路径:"+file);ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.makeClass("com.ssdmbbl.javassist.Hello");//创建一个类名为"hello",传递参数的顺序为(int,double),没有返回值的类/*CtMethod(...)源代码:public CtMethod(CtClass returnType,//这个方法的返回值类型,String mname, //(method name)方法的名字是什么CtClass[] parameters, //方法传入的参数类型是什么CtClass declaring //添加到哪个类中) {....}*/CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);//设置hello方法的权限为publicctMethod.setModifiers(Modifier.PUBLIC);//向ctClass中添加这个方法ctClass.addMethod(ctMethod);//写入本地ctClass.writeFile(file);}
}

执行后,就可以查看生成的代码了:

可以看到,我们并没有指定参数的名字,也会给生成var1、var2依次类推的名字。

var1和var2其实class变量表中存放的名字。

package com.ssdmbbl.javassist;public class Hello {public void hello1(int var1, double var2) {}public Hello() {}
}

可以设置的返回值类型:

public static CtClass booleanType;
public static CtClass charType;
public static CtClass byteType;
public static CtClass shortType;
public static CtClass intType;
public static CtClass longType;
public static CtClass floatType;
public static CtClass doubleType;
public static CtClass voidType;

(二)新增一个变量

URL resource = JavassistTest2.class.getClassLoader().getResource("");
String file = resource.getFile();
System.out.println("文件存储路径:"+file);ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.makeClass("com.ssdmbbl.javassist.Hello");//添加一个hello1的方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctClass.addMethod(ctMethod);//添加一个int类型的,名字为value的变量
CtField ctField = new CtField(CtClass.intType,"value",ctClass);
ctField.setModifiers(Modifier.PRIVATE);
ctClass.addField(ctField);ctClass.writeFile(file);

那么执行后的内容就是如下了:

package com.ssdmbbl.javassist;public class Hello {private int value;public void hello1(int var1, double var2) {}public Hello() {}
}

(三)给变量新增get和set方法

代码修改如下:

   public static void main(String[] args) throws CannotCompileException, IOException {URL resource = JavassistTest2.class.getClassLoader().getResource("");String file = resource.getFile();System.out.println("文件存储路径:"+file);ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.makeClass("com.ssdmbbl.javassist.Hello");//添加一个hello1的方法CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);ctMethod.setModifiers(Modifier.PUBLIC);ctClass.addMethod(ctMethod);//添加一个int类型的,名字为value的变量CtField ctField = new CtField(CtClass.intType,"value",ctClass);ctField.setModifiers(Modifier.PRIVATE);ctClass.addField(ctField);//为value变量添加set方法CtMethod setValue = new CtMethod(CtClass.voidType, "setValue", new CtClass[]{CtClass.intType}, ctClass);setValue.setModifiers(Modifier.PUBLIC);ctClass.addMethod(setValue);//为value变量添加get方法CtMethod getValue = new CtMethod(CtClass.intType, "getValue", new CtClass[]{}, ctClass);getValue.setModifiers(Modifier.PUBLIC);ctClass.addMethod(getValue);ctClass.writeFile(file);}

执行效果:

package com.ssdmbbl.javassist;public class Hello {private int value;public void hello1(int var1, double var2) {}public void setValue(int var1) {}public int getValue() {}public Hello() {}
}

(四)给方法内部添加代码

你是不是很好奇,set和get方法内部并没有代码,当程序运行的时候,肯定会出错的。
下面就来看一下。

我们预想的结果:

    private int value;public void setValue(int var1) {this.value = var1}public int getValue() {return this.value;}

修改如下:

//为value变量添加set方法
CtMethod setValue = new CtMethod(CtClass.voidType, "setValue", new CtClass[]{CtClass.intType}, ctClass);
setValue.setModifiers(Modifier.PUBLIC);
//设置方法体
setValue.setBody("this.value = var1;");ctClass.addMethod(setValue);//为value变量添加get方法
CtMethod getValue = new CtMethod(CtClass.intType, "getValue", new CtClass[]{}, ctClass);
getValue.setModifiers(Modifier.PUBLIC);
//设置方法体
getValue.setBody("return this.value;");
ctClass.addMethod(getValue);

很倒霉,说找不到这个var1这个变量

报错的堆栈信息如下:

Exception in thread "main" javassist.CannotCompileException: [source error] no such field: var1
at javassist.CtBehavior.setBody(CtBehavior.java:474)
at javassist.CtBehavior.setBody(CtBehavior.java:440)
at com.ssdmbbl.javassist.JavassistTest2.main(JavassistTest2.java:41)
Caused by: compile error: no such field: var1

这个原因我们前面其实提到了,因为在编译的时候,会把变量名抹掉,传递的参数会依次在局部变量表中的顺序。

如果传递:

public void test(int a,int b,int c){...
}

那么a,b,c就对应本地变量表中的1,2,3的位置。

 —— ——
|a|1|
|b|2|
|c|3|—— ——

那么我们获取变量时就不能使用原始的名字了,在Javassist中访问方法中的参数使用的是$1, 2 , 2, 2,…,而不是直接使用原始的名字。

我们修改如下:

//为value变量添加set方法
CtMethod setValue = new CtMethod(CtClass.voidType, "setValue", new CtClass[]{CtClass.intType}, ctClass);
setValue.setModifiers(Modifier.PUBLIC);
//设置方法体
setValue.setBody("this.value = $1;");ctClass.addMethod(setValue);//为value变量添加get方法
CtMethod getValue = new CtMethod(CtClass.intType, "getValue", new CtClass[]{}, ctClass);
getValue.setModifiers(Modifier.PUBLIC);
//设置方法体
getValue.setBody("return this.value;");
ctClass.addMethod(getValue);

结果成功了:

public class Hello {private int value;public void hello1(int var1, double var2) {}public void setValue(int var1) {this.value = var1;}public int getValue() {return this.value;}public Hello() {}
}

再来一个:修改hello1方法体,使传递的两个参数相加然后赋值给value;

//添加一个hello1的方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("this.value = $1 + $2;");
ctClass.addMethod(ctMethod);

执行结果如下:

因为我们value是int,$1是int,$2是double,所以做了强制转型处理。

public void hello1(int var1, double var2) {this.value = (int)((double)var1 + var2);
}

(五)在方法体的前后分别插入代码

测试代码如下:

//添加一个hello1的方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("this.value = $1 + $2;");
ctMethod.insertBefore("System.out.println("我在前面插入了:"+$1);");
ctMethod.insertAfter("System.out.println("我在最后插入了:"+$1);");
ctClass.addMethod(ctMethod);

结果如下:

public void hello1(int var1, double var2) {System.out.println("我在前面插入了:" + var1);this.value = (int)((double)var1 + var2);Object var5 = null;System.out.println("我在最后插入了:" + var1);
}

三、Javassist中的一些特殊参数示例讲解

在官方文档中看到有几个比较特殊的标识符,还有几个比较特殊的标识符需要了解。

标识符作用
$0、$1、$2、 3 、 3、 3、…this和方法参数(1-N是方法参数的顺序)
$args方法参数数组,类型为Object[]
$$所有方法参数,例如:m($$)相当于m($1,$2,…)
$cflow(…)control flow 变量
$r返回结果的类型,在强制转换表达式中使用。
$w包装器类型,在强制转换表达式中使用。
$_返回的结果值
$sig类型为java.lang.Class的参数类型对象数组
$type类型为java.lang.Class的返回值类型
$class类型为java.lang.Class的正在修改的类

下面咱们来一起分别来看一下,分析一下,怎么用,有什么用吧。

只介绍几个常见和常用到的吧。
有兴趣的话,剩下的可以看官方文档:Javassist Tutorial

(一)$0,$1,$2,…

这个其实咱们已经在上面用到过了,再来细说一下吧。$0代表this,$1, 2 , 2, 2,…,依次对应方法中参数的顺序。
如果有:

public void test(int a,int b,int c){}

那么如果想引用a和b和c的话,需要这样:

ctMethod.setBody("return $1 + $2 + $3;");

对了还有:静态方法是没有$0的,所以静态方法下$0是不可用的。

(二)$args

$args变量表示所有参数的数组,它是一个Object类型的数组(new Object[]{…}),如果参数中有原始类型的参数,会被转换成对应的包装类型。比如原始数据类型为int,则会被转换成java.lang.Integer,然后存储在args中。

例如我们测试代码如下:

CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("System.out.println($args);");

编译后的结果如下:

public void hello1(int var1, double var2) {System.out.println(new Object[]{new Integer(var1), new Double(var2)});
}

(三)$$

变量 是 所 有 参 数 的 缩 写 , 参 数 用 逗 号 分 割 , 例 如 : m ( 是所有参数的缩写,参数用逗号分割,例如:m( 是所有参数的缩写,参数用逗号分割,例如:m()相当于:m($1,$2,$3,…)。

如何使用呢?

我们经常做一些代码优化,把较为复杂的方法内部的逻辑,提炼出来,提炼到一个公共的方法里。

或者说一个方法调用另一个方法,这两个方法传递的参数是一样的时候就用到了。

例如原java:

public Object m1(String name,String age){...省略10000行代码逻辑
}

提炼后的:

提炼出来的代码,我们也可以在其他地方使用(所谓的公共的代码)。

public Object m1(String name,String age){...省略20行代码逻辑return m2(name,age);
}private Object m2(String name,String age){...
}

那么我们就造一个这个场景来说明一下用法吧。

简单点来说,就是一个方法调用另一个方法,传递全参数时。

CtMethod hello2 = new CtMethod(CtClass.voidType, "hello2", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
hello2.setModifiers(Modifier.PUBLIC);
hello2.setBody("this.value = $1 + $2;");
ctClass.addMethod(hello2);//添加一个hello1的方法
CtMethod hello1 = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
hello1.setModifiers(Modifier.PUBLIC);
hello1.setBody("this.value = $1 + $2;");
hello1.insertAfter("hello2($$);");

可以看到我们hello1调用hello2时,需要传递全部参数。此时即可写成$$方法,当然了也可以写成hello2($1,$2)。

编译后的结果:

public void hello2(int var1, double var2) {this.value = (int)((double)var1 + var2);
}public void hello1(int var1, double var2) {this.value = (int)((double)var1 + var2);Object var5 = null;this.hello2(var1, var2);
}

(四)$cflow(…)

$cflow 的全名为:control flow,这是一个只读变量,返回指定方法递归调用的深度。

我们以计算n的斐波拉契数列为例,来演示一下如何使用。

我们正确的递归算法代码如下:

public int f(int n){if(n <= 1){return n;}return f(n-1) + f(n - 2)}

对于上面这段代码,我们可以这样写:

CtMethod f = new CtMethod(CtClass.intType,"f", new CtClass[]{CtClass.intType}, ctClass);
f.setBody("{if($1 <= 1){" +"     return $1;" +"}" +"return f($1 - 1) + f( $1 - 2);}");
f.setModifiers(Modifier.PUBLIC);

编译后的:

public int f(int var1) {return var1 <= 1 ? var1 : this.f(var1 - 1) + this.f(var1 - 2);
}

那么我们只想在递归到前n次的时候打印log,我们该怎么做呢。

例如,我们下面写的是"$cflow(f) == 1"时

CtMethod f = new CtMethod(CtClass.intType,"f", new CtClass[]{CtClass.intType}, ctClass);
f.setBody("{if($1 <= 1){" +"     return $1;" +"}" +"return f($1 - 1) + f( $1 - 2);}");
f.setModifiers(Modifier.PUBLIC);//在代码body的前面插入
f.useCflow("f");
f.insertBefore("if($cflow(f) == 1){" +"               System.out.println("我执行了,此时的n是:"+$1);" +"           }");

编译后的代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package com.ssdmbbl.javassist;import javassist.runtime.Cflow;public class Hello {public static Cflow _cflow$0 = new Cflow();public int f(int var1) {if (_cflow$0.value() == 1) {System.out.println("我执行到来第2次,此时的n是:" + var1);}boolean var6 = false;int var10000;try {var6 = true;_cflow$0.enter();if (var1 <= 1) {var10000 = var1;var6 = false;} else {var10000 = this.f(var1 - 1) + this.f(var1 - 2);var6 = false;}} finally {if (var6) {boolean var3 = false;_cflow$0.exit();}}int var8 = var10000;_cflow$0.exit();return var8;}public Hello() {}
}

通过查看源码,发先关键的一个地方是调用了Cflow对象的enter方法:

public static Cflow _cflow$0 = new Cflow();..._cflow$0.enter();

点进enter()的内部发现,调用了get().inc()方法:

public class Cflow extends ThreadLocal<Cflow.Depth> {protected static class Depth {private int depth;Depth() { depth = 0; }int value() { return depth; }void inc() { ++depth; }void dec() { --depth; }}@Overrideprotected synchronized Depth initialValue() {return new Depth();}/*** Increments the counter.*/public void enter() { get().inc(); }/*** Decrements the counter.*/public void exit() { get().dec(); }/*** Returns the value of the counter.*/public int value() { return get().value(); }
}

而inc()方法控制着一个全局变量的增加操作。

void inc() { ++depth; }

boolean var6 = false;相当于是一个开关,控制着是否开始和结束。

当参数var1<=1时,即结束

if (var1 <= 1) {var10000 = var1;var6 = false;
}

我们可以使用反射来验证一下,测试代码如下:

package com.ssdmbbl.javassist;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** @author zhenghui* @description:* @date 2021/4/8 10:20 上午*/
public class Test {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {Class<?> aClass = Class.forName("com.ssdmbbl.javassist.Hello");//初始化这个类Object obj = aClass.newInstance();//获取所有的方法Method[] methods = aClass.getMethods();//遍历所有的方法for (Method method : methods) {//当方法为f的时候,进行调用if(method.getName().equals("f")){//调用并传递参数为5,即f(5)method.invoke(obj,5);}}}
}

执行的结果:

为什么是2次呢?原因上面也说了:if(var1 <= 1){…},所以是

我执行了,此时的n是:4
我执行了,此时的n是:3

相关链接

1、javassist的API接口文档

Overview (Javassist API)

2、javassist的github开源地址

https://github.com/jboss-javassist/javassist

3、javassist的官网

Javassist by jboss-javassist

4、javassist官方的英文教程

Javassist Tutorial


http://chatgpt.dhexx.cn/article/aH0wLBDQ.shtml

相关文章

java--javassist学习

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba &#xff08;千叶 滋&#xff09;所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jb…

javassist使用指南

目录 一、快速入门1.1 创建class文件1.2 ClassPool的相关方法1.3 CtClass的相关方法1.4 CtMethod的相关方法1.5 调用生成的类对象1.5.1 通过反射调用1.5.2 通过接口调用 1.6 修改现有的类对象 二、将类冻结三、类搜索路径四、$开头的特殊字符五、ProxyFactory的使用 我们知道Ja…

systemd介绍

由来 Linux 的启动一直采用init进程&#xff0c;这种方法有两个缺点。一是启动时间长。init进程是串行启动&#xff0c;只有前一个进程启动完&#xff0c;才会启动下一个进程&#xff0c;二是启动脚本复杂。init进程只是执行启动脚本&#xff0c;不管其他事情。脚本需要自己处理…

systemd man手册

SYSTEMD&#xff08;1&#xff09;systemd SYSTEMD&#xff08;1&#xff09; 名称 systemd&#xff0c;init-systemd系统和服务管理器 概要 /lib/systemd/systemd [OPTIONS...]init [OPTIONS...] {COMMAND}描述 systemd是Linux操作系统的系统和服务管理器。在启动时作为PID的…

systemd wsl 测试笔记

文章目录 systemd 简介WSL systemdsystemctljournalctlhello serviceSleep 与 Timeout 测试Requires 测试After 测试 systemd 简介 Linux 从关闭到运行, 完整的启动和启动过程有三个主要部分: 硬件启动(Hardware boot): 初始化系统硬件Linux 引导(Linux boot): 加载 Linux 内核…

Linux systemd启动流程

以Ubuntu 18.04.2 LTS为例&#xff0c;列出Systemd启动target中涉及单元(Unit). default.target ( graphical.target by default) graphical.targetRequiresmulti-user.targetWantsdisplay-manager.serviceConflictsrescue.service rescue.targetAftermulti-user.target rescu…

关于 Linux中systemd的一些笔记

写在前面 嗯&#xff0c;准备RHCA&#xff0c;学习整理这部分知识博文内容涉及&#xff1a; systemd简述对于unit的信息的介绍通过systemctl命令控制Service unit的DemoService unit配置文件内容,权值的一些介绍 傍晚时分&#xff0c;你坐在屋檐下&#xff0c;看着天慢慢地黑下…

Linux-Systemd

目录 一、Systemd概述 二、Systemd优势 2.1兼容性 2.2启动速度 2.3systemd 提供按需启动能力 2.4采用 linux 的 cgroups 跟踪和管理进程的生命周期 2.5启动挂载点和自动挂载的管理 2.6实现事务性依赖关系管理 2.7日志服务 2.8 依赖关系 2.9systemd 事务 三、unit(单…

Systemd 简介

一 概述 Linux 服务管理有两种方式service和systemctl。而systemd是Linux系统最新的初始化系统(init)&#xff0c;作用是提高系统的启动速度&#xff0c;尽可能启动较少的进程&#xff0c;尽可能更多进程并发启动&#xff0c;systemd对应的进程管理命令就是systemctl。值得一提…

systemd简介

Systemd 是一个专用于 Linux 操作系统的系统与服务管理器,其目的是要取代Unix时代以来一直在使用的init系统。 systemd概述Systemd 是 Linux 系统中最新的初始化系统(init),它主要的设计目标是克服 sysvinit 固有的缺点,提高系统的启动速度。 systemd框架图 根据 Linux 惯…

Systemd 入门及常用命令

目录 Systemdsystemd架构systemd 系统管理管理系统查看启动耗时查看当前主机的信息 Unit 资源查看当前系统的所有 Unit查看系统状态和单个 Unit 的状态 Unit 管理依赖关系Unit 配置文件查看配置文件的内容 Targettarget&#xff08;Systemd&#xff09; 与 runlevel&#xff08…

systemd 介绍

转自&#xff1a;https://www.linuxidc.com/Linux/2018-03/151291.htm 从 init 系统说起 Linux 操作系统的启动首先从 BIOS 开始&#xff0c;接下来进入 boot loader&#xff0c;由 bootloader 载入内核&#xff0c;进行内核初始化。内核初始化的最后一步就是启动 PID 为 1 的 …

[2020.1.10]systemd介绍

1 systemd基本概念 systemd:a system daemon&#xff0c;相当于以前的init进程&#xff0c;pid1&#xff0c;systemd是1号进程!!! sbin/init --> /lib/systemd/systemd unit: 一个进程,例如lightdm.service job: 一个动作,启动是个job,关闭是个job。开机启动时,systemd…

systemd教程(三)

下来会通过示例来描述不同Service Type值的应用场景。在此之前&#xff0c;强烈建议先阅读前后台进程父子关系和daemon类进程来搞懂进程之间的关系和Daemon类进程的特性。 systemd service&#xff1a;Typeforking 当使用systemd去管理一个长久运行的服务进程时&#xff0c;最…

dpkg打包笔记

dpkg 安装软件 dpkg -i xxx.deb 查看安装目录 dpkg -L xxx 显示版本 dpkg -l xxx 详细信息 dpkg -s xxx 罗列内容 dpkg -c xxxx 卸载软件 dpkg -r xxxx 构建deb包 dpkg -p

dpkg和apt

文章目录 Ubuntu软件安装Deb包安装Deb包简介dpkg常用命令 apt-get源安装apt-get简介apt工作原理apt相关文件常用的apt命令参数apt-getapt-cache dpkg和apt-get的区别 Ubuntu软件安装 Linux有很多种发行版本&#xff0c;各种发行版本之间安装软件方式和命令不一样&#xff0c;同…

dpkg安装

目录 转载&#xff1a;https://blog.csdn.net/u012300744/article/details/80267225 1.安装 &#xff08;3&#xff09;安装 sudo dpkg -i deb文件名 &#xff08;4&#xff09;根据经验&#xff0c;通常情况下会报依赖关系的错误&#xff0c;我们可以使用以下的命令修复安…

dpkg制作deb包详解

1 deb包文件结构 deb 软件包里面的结构&#xff0c;它具有DEBIAN和软件具体安装目录&#xff08;如etc, usr, opt, tmp等&#xff09;。在DEBIAN目录中至少必须包括control文件&#xff0c;还有可能postinst(postinstallation)、postrm(postremove)、preinst(preinstallation)、…

dpkg命令的用法

dpkg命令的用法 dpkg 是Debian package的简写&#xff0c;为”Debian“ 操作系统 专门开发的套件管理系统&#xff0c;用于软件的安装&#xff0c;更新和移除。 所有源自"Debian"的Linux的发行版都使用 dpkg, 例如"Ubuntu" 阅读目录 安装软件列出与该…

dpkg说明

dpkg与centos中的rpm相似&#xff0c;被用于安装&#xff0c;卸载及查询deb包信息。下面简单介绍基础命令。 已有安装包&#xff1a;test.deb。 安装命令: dpkg -i test.deb 安装test.deb软件包 dpkg -c test.deb #查看test.deb软件包中包含的文件结构 安装后查询命令&#xff…