news 2026/10/21 5:48:46

微服务手动打包实战:从Maven多模块到Spring Boot可执行jar

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微服务手动打包实战:从Maven多模块到Spring Boot可执行jar

先说一个我最近遇到的场景。某公司给客户做微服务交付,现场环境完全隔离,没有CI/CD流水线,连内网的依赖仓库都不能访问,所有服务只能靠手工在本地构建、拷贝、部署。那天我抱着笔记本电脑捣鼓了一整天,把一套微服务项目从多模块代码变成一个个可以独立启动的jar包,中间踩了无数坑。这篇文章就把整个手动打包过程、背后的原理和排查经验完整记录下来,适合需要离线部署、临时交付、或者单纯想搞懂构建产物是怎么来的人。

不管你平时是用IDE右侧的Maven面板点一下打包,还是写脚本调CI工具,我都建议至少手动打一次jar包。因为只有自己敲过命令、看过构建日志、处理过依赖冲突,你才能真正理解那个十几兆甚至上百兆的jar包里装了什么,以及为什么有些包在本地能跑,换台机器就起不来。

1. 手动打包的前置认知:先搞清楚你在打什么

先说一个容易翻车的事。很多人习惯在IDE里点一下package按钮,包就打好了,然后传到服务器上发现启动失败,才开始怀疑人生。手动打包这件事,难的不是敲命令,而是你根本不了解构建过程到底干了什么。

微服务项目大多是标准的多模块结构:一个父工程,下面挂十几个子模块。子模块之间互相依赖,典型的关系是:订单服务依赖公共组件模块,用户服务又依赖自己的API模块和公共配置模块。这种结构下有个硬性逻辑——依赖模块必须先被安装到本地仓库,或者跟随本次构建一起编译,否则被依赖方在编译阶段根本找不到类。

这里有两个非常容易混淆的概念:

  • mvn package:把当前模块打包成jar或war,但不会把产物安装到本地Maven仓库。
  • mvn install:打包之后,把jar安装到本地仓库,供其他模块在本地构建时引用。

对于单模块项目,这两个命令差别不大。但对于多模块微服务项目,差别就大了。如果子模块B依赖子模块A,你只对B执行package,而A的jar没有进入本地仓库,构建B时会报"无法解析依赖"或"找不到符号"。

所以手动打包的第一步,是先梳理整个项目的模块依赖结构。最快的办法不是一个个打开pom文件翻,而是在根目录执行:

mvn dependency:tree

这个命令会把整个项目的依赖树打印出来,包括模块间依赖和第三方依赖,信息量非常大。第一次看的时候可能会被刷屏,建议把输出重定向到文件里再慢慢看:

mvn dependency:tree -Doutput=/tmp/dep-tree.txt

1.1 微服务构建产物到底是什么

一个典型Spring Boot微服务的最终构建产物,并不是一个"只包含业务代码的轻量jar",而是一个能直接java -jar启动的可执行jar。它里面至少包含四类东西:

  • 编译后的业务代码,即com/xxx/...下的class文件
  • 所有第三方依赖jar,以嵌套jar的形式放进BOOT-INF/lib目录
  • 项目自身的资源和模板文件,包括application.yml、静态页面、Mapper XML等
  • Spring Boot框架自带的JarLauncher类以及MANIFEST.MF清单文件

这里就引出一个关键认知:普通jar和Spring Boot可执行jar完全是两回事。

产物类型内容能否直接 java -jar
普通jar仅包含模块自身编译类不行,会报"no main manifest attribute"
Spring Boot可执行jar自身类 + 全部依赖 + 启动器可以,独立启动

手动打包首先要确认项目里是否引入了spring-boot-maven-plugin。如果项目继承了spring-boot-starter-parent,但不加这个构建插件,打包出来的多半是普通jar,拿到服务器上用java -jar跑,直接报清单属性错误。

另一个常见的依赖关系问题是:父pom里用<properties>统一管理版本,但某个子模块手滑写死了版本号。本地仓库里如果存着旧版本,依赖解析时可能不会报错,等传到干净环境的服务器上,依赖拉取失败或者版本不一致的问题就全冒出来了。手动打包前花十分钟跑一遍mvn dependency:tree,能省掉后面对着启动日志发呆的几小时。

1.2 环境准备:三处版本必须对齐

手动打包最容易翻车的就是环境不一致,打包机器、构建工具、运行机器三方版本对不上。我总结过三个必须核对的位置:

  • 打包机的JDK版本:用java -version确认,不能低于项目要求的编译目标版本。
  • Maven版本:用mvn -v查看,过旧的Maven可能不兼容新插件的参数。
  • 运行机器的JDK版本:服务器上跑的JDK不能低于编译目标版本。

举个例子,项目pom里用<java.version>11</java.version>指定编译目标,打包机是JDK 17没问题,但你拷到一台只装了JDK 8的服务器上,启动瞬间就会抛UnsupportedClassVersionError。这个错误信息很直白,但很多人为什么没提前发现?因为本地IDE用的是内置JRE,版本早就被IDE接管了,平时根本注意不到。

这里还要提醒一个命令行新手必踩的坑:Maven本身运行依赖JAVA_HOME环境变量。很多人IDE里打包正常,一换到纯命令行就报JAVA_HOME is not set或找不到javac,就是因为没配环境变量。手动打包前先开一个干净终端执行mvn -v,能正常打印版本信息再继续。

版本对齐还有一个隐性坑:Spring Boot版本和Spring Cloud版本是有对应关系的,配错的话本地单个模块启动没问题,多个微服务互相调用时某些注解或组件初始化才报错,排查起来非常痛苦。建议在父pom里统一引入对应版本的spring-cloud-dependencies,使用BOM方式控制版本:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

这样所有子模块都不需要再写Spring Cloud组件的版本号,从根本上避免版本漂移。

2. 核心打包实操:从单模块到多模块

环境确认好了,模块结构也摸清了,下面进入正题:手动打包的命令怎么敲。

2.1 单模块项目的标准打包命令

一个独立的Spring Boot服务,最简单的打包命令是:

mvn clean package

clean的目的是清掉上一次构建遗留的target目录,避免旧class文件混进新jar。package阶段会依次执行编译、测试、打包。但如果本地单元测试配置了外部中间件,比如需要连数据库或Redis的测试,打包时经常挂在测试环节,很烦人。

所以实践中最常用的是跳过测试:

mvn clean package -DskipTests

注意-DskipTests只是不执行测试用例,但会编译测试代码。如果想连测试代码编译都跳过,用:

mvn clean package -Dmaven.test.skip=true

这两个参数的区别很多新手分不清。简单说:-DskipTests是"不跑但保留编译产物",-Dmaven.test.skip=true是"整块测试代码都当不存在"。微服务项目打包为了速度,我一般直接用后者。

如果项目有生产环境和测试环境之分,还可能需要指定Profile:

mvn clean package -Dmaven.test.skip=true -Pprod

这样打包出来的jar里,内置的application-prod.yml会被激活为默认配置。注意,Profile生效的前提是application.yml里声明了对应的环境配置,否则-Pprod只是把临时变量传给Maven,对Spring Boot的运行环境没有任何影响。

2.2 多模块项目的聚合打包与顺序问题

多模块微服务项目,十几个子模块分布在各自目录里。有人习惯一个模块一个模块地cd进去打包,结果就是:先打公共模块,再打API模块,再打业务服务模块,顺序一旦搞错就构建失败。

更好的做法是在根目录执行聚合构建。父pom的<packaging>是pom,<modules>节点列出了所有子模块,根目录一条命令就能驱动整个项目按依赖顺序构建:

mvn clean install -Dmaven.test.skip=true

这里用install而不是package,原因就是前面讲的:子模块之间有依赖,必须把被依赖的模块安装到本地仓库,后续模块才能正确引用。这一步打包完成后,你会在各个子模块的target目录里看到生成的可执行jar。

如果只需要打包其中某一个服务,不想每次都全量构建十几个模块,用-pl和-am组合:

mvn clean package -pl order-service -am -Dmaven.test.skip=true
  • -pl(project list):指定要构建的模块列表,多个用逗号分隔。
  • -am(also make):同时构建该模块依赖的其他模块。

这个组合非常实用。比如我只改了订单服务,依赖的公共模块没动,加-am就能保证依赖模块也一并打包安装,又不会把无关的十几个模块全部构建一遍。实测下来,多模块这样打包能省一半时间。

聚合构建还有个细节:父pom的<modules>声明顺序并不强制等于构建顺序,Maven会按依赖关系自动排序。但如果你是第一次接触一个遗留项目,发现模块关系乱成一团,别慌,mvn install会自动处理依赖拓扑。真正该担心的是某两个模块循环依赖,这种项目手动打包时Maven直接报The projects in the reactor contain a cyclic reference,这种基本只能靠重构解决。

2.3 Gradle项目的手动打包对照

微服务项目并不都是Maven,Gradle也占了相当比例。Gradle手动打包的核心命令是:

gradle clean bootJar

Spring Boot的Gradle插件会注册bootJar任务,产出可执行jar。如果项目里既有普通jar任务又有bootJar任务,要注意只启用一个,否则会产生同名jar互相覆盖。

实际项目中更常用的是:

gradle clean build -x test

-x test等同于Maven的跳过测试。Gradle的依赖管理不像Maven那样有固定的"install到本地仓库"逻辑,多模块项目用gradle build时,依赖模块会在本次构建的reactor内一起编译,所以通常不需要像Maven那样显式install。

这里给个小建议:不要盲目在Maven和Gradle混用场景下手动打包。同一个微服务项目里,不同子模块如果用不同构建工具,依赖处理就很容易乱。我在某个项目里见过父工程是Maven、某个子模块却带了Gradle wrapper,最终只能统一收敛到一个工具,否则手动打包的复杂度直接翻倍。

3. Spring Boot可执行jar的生成原理

很多人在这一步就停了:jar打包出来,能用,然后呢?如果你不想以后遇到"换了台机器就起不来"这种诡异问题,就值得花十分钟理解一下Spring Boot可执行jar的机制。

3.1 spring-boot-maven-plugin到底做了什么

一个普通的Maven项目执行mvn package,打出来的是一个标准jar,里面只有自己编译的class和资源文件。这种jar无法用java -jar启动,因为没有主类信息、没有依赖库。

真正让jar变成可执行的是spring-boot-maven-plugin的repackage目标。这个插件会在package阶段之后对jar做二次处理:把原本的普通jar变成三层结构的可执行jar,同时重写MANIFEST.MF清单。

pom里典型的配置是这样的:

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>

这段配置非常重要。很多人打包出来没有repackage,是因为用了独立插件但没配置executions,或者项目根本没有引入这个插件。排查方法很简单:用压缩工具打开jar,看看有没有BOOT-INF目录,有就是可执行jar,没有就是普通jar。

jar tf app.jar | head -50

或者:

unzip -l app.jar

如果输出里能看到BOOT-INF/classes和BOOT-INF/lib,恭喜,这个jar是Spring Boot插件的产物。

3.2 BOOT-INF目录结构与双类加载器设计

把Spring Boot可执行jar解压开,目录结构是这样:

app.jar ├── BOOT-INF │ ├── classes # 业务代码和资源 │ └── lib # 所有第三方依赖jar ├── META-INF │ ├── MANIFEST.MF # 启动入口定义 │ └── maven-metadata.xml └── org └── springframework └── boot └── loader └── JarLauncher.class

这里有几个关键点:

  • Main-Class是org.springframework.boot.loader.JarLauncher,负责引导。
  • Start-Class才是你自己写的@SpringBootApplication启动类。
  • BOOT-INF/lib里嵌套的jar会被JarLauncher自定义的类加载器读取,不依赖外部ClassPath。

Spring Boot用了一套自定义类加载器机制,专门用来从嵌套jar里加载类。这就是为什么这种jar可以在不把依赖解压到系统目录的情况下独立启动。手动打包完,建议直接查看MANIFEST.MF确认一下关键属性:

unzip -p app.jar META-INF/MANIFEST.MF

正常输出里能看到:

Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.example.DemoApplication

有这两行,说明插件执行成功。这个操作在排查"jar包启动报no main manifest attribute"时极其有用。

3.3 验证jar包可执行性的标准流程

打包完成后的第一件事不是急着拷到服务器,而是在本地先做一次启动验证。我的一贯流程是:

java -jar order-service.jar --server.port=18080

观察日志是否正常打印出端口监听信息和项目启动完成标志。验证完用Ctrl+C停掉,再确认jar是否能正常关闭,不会留下僵死进程。

如果项目包含多个微服务,建议在本地挨个启动一遍,再检查各个服务之间是否能够通过注册中心发现。这一步提前做,能避免把坏包传到现场才发现的尴尬。我在实际交付中就遇到过:某个服务本地启动正常,但打包机的资源编译时被限流,产生了一个不完整的jar,一启动就OutOfMemoryError。本地验证当时没做,到现场才发现,来回浪费了两天时间。

4. 配置文件外置与启动参数设置

jar包一旦打好,里面的application.yml就固定了。但微服务部署场景中,开发、测试、生产环境的数据库地址、注册中心地址往往是不同的。手动打包不可能为每个环境都重新打一次包,所以必须理解配置外置。

4.1 多环境Profile的正确选择方式

Spring Boot的多环境配置通常是这样组织的:

src/main/resources/ ├── application.yml ├── application-dev.yml └── application-prod.yml

application.yml里写公共配置,并通过spring.profiles.active指定默认环境:

spring: profiles: active: dev

但如果把环境写死在jar里,每次切换环境都要重新打包,非常低效。更推荐的做法是:打一个不带环境配置的jar,启动时用参数指定Profile。

启动命令对应变为:

java -jar app.jar --spring.profiles.active=prod

命令行参数--spring.profiles.active=prod的优先级高于jar内部的application.yml。所以即使jar里写死了dev,命令行也能覆盖。这是微服务手动运维中最高频的操作,一定要记牢。

4.2 外置配置文件与优先级详解

如果环境相关的配置量很大,比如包含几百行数据库连接和队列配置,全部塞在Profile文件里会导致jar包体积膨胀,也不方便运维调整。更好的做法是让jar自动读取jar包外部的配置文件。

在jar的同一级目录下放一个config文件夹,或者直接把application.yml放在jar同级目录,Spring Boot启动时就会优先读取外部配置而不是jar内的配置。完整的优先级从高到低大致是:

  • 命令行参数
  • 外部config目录下的配置文件
  • 外部jar同级的配置文件
  • jar包内部的配置文件

所以当你在jar同级的config/application-prod.yml里写了数据库密码,而jar内application.yml里写的是占位符或者默认值,最终生效的会是你外部的配置。这个特性非常适合手动部署场景:代码、依赖全部固化在jar里,环境差异完全由外部配置控制。

启动时还可以显式指定配置文件路径:

java -jar app.jar --spring.config.additional-location=/opt/app/config/

这样即使配置文件不放在jar同级,也能被加载到。跨机器部署时,我习惯把所有服务的配置文件统一下发到固定目录,再配合启动脚本统一拼接参数,避免出现"这台机器读配置A、那台机器读配置B"的混乱。

4.3 JVM参数与后台启动的推荐写法

手动部署微服务,启动命令不能裸奔。一个生产环境可用的启动方式至少包含三部分:JVM内存参数、Spring Boot业务参数、日志重定向。

推荐写法:

nohup java -Xms512m -Xmx1024m \ -XX:+UseG1GC \ -jar app.jar \ --spring.profiles.active=prod \ --server.port=8080 \ > /opt/logs/app.log 2>&1 &

逐个解释:

  • -Xms512m -Xmx1024m:初始堆内存和最大堆内存。生产环境建议-Xms等于-Xmx,避免运行期堆扩容带来的性能抖动。
  • -XX:+UseG1GC:使用G1垃圾回收器,对多核机器上的微服务比较友好。
  • --server.port=8080:覆盖jar内的端口配置。
  • > app.log 2>&1:把标准输出和错误输出都重定向到日志文件。
  • &:放到后台运行,终端关闭后服务不停止。
  • nohup:终端挂断后进程继续运行。

关闭进程时不要用kill -9,应该先找到进程PID,再优雅停止:

ps -ef | grep app.jar | grep -v grep kill 12345

如果用了kill -9强杀,Spring Boot来不及释放注册中心连接、数据库连接池和本地资源,在微服务体系里会留下大量残留会话,可能引起后续服务调用异常。

5. 常见打包与启动问题排查实录

手动打包这件事,命令就那么几条,最花时间的永远在排查阶段。我把工作里遇到频率最高的四类问题整理成速查,每一个都有对应的排查路径。

5.1 no main manifest attribute:打包成了普通jar

这个报错几乎可以断定jar里没有META-INF/MANIFEST.MF中的Main-Class,或者根本没有执行repackage。排查顺序:

  1. 先运行unzip -p app.jar META-INF/MANIFEST.MF,看看有没有Main-Class。
  2. 确认pom里有没有spring-boot-maven-plugin,有没有配repackage的execution。
  3. 确认模块的<packaging>是不是jar,如果写成了pom,根本不会产出可执行jar。
  4. 确认启动类上有没有@SpringBootApplication注解,并且main方法签名是public static void main(String[] args)。

最后一类问题尤其隐蔽:某个模块的启动类不在聚合构建中被扫描的路径下,或者主类名写错了。手动打包时建议在pom的插件配置里显式指定主类:

<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.example.OrderApplication</mainClass> </configuration> </plugin>

这个配置在模块多、存在多个main方法时尤其必要,能绕开"找到多个主类"或"找不到主类"的尴尬。

5.2 ClassNotFoundException:依赖没有被正确打入jar

启动后报找不到某个类,通常有几种可能:

  • 依赖以provided或optional声明,这类依赖不会进入可执行jar。
  • 依赖作用域配置错误,导致repackage时被排除。
  • 启动类依赖了运行时才加载的类,但打包时没包含。

排查方法第一是先看报错类名属于哪个jar。用dependency:tree确认它是否出现在依赖树中,再确认它是否出现在最终jar的BOOT-INF/lib目录里:

unzip -l app.jar | grep 'common-util'

如果依赖树里有但jar里没有,基本就是scope配置问题。把pom里对应的<scope>provided</scope>改成默认的compile,重新打包即可。

还有一种情况是依赖冲突:应用里有两个版本的同一个类,编译时不报错,运行时按classpath顺序加载了旧版类。这种问题需要用mvn dependency:tree -Dverbose查看重复依赖,并通过pom里的<exclusions>排除不需要的版本。

5.3 端口冲突、进程残留与启动缓慢

微服务部署中最常见的问题是端口被占用。启动报Port already in use: 8080时,先查是哪个进程占用了端口:

lsof -i:8080

如果是Java进程,而且看起来像是上一个没关干净的服务实例,慎用kill -9,先找进程的启动命令确认不会误杀,再优雅停止。

启动缓慢这个问题,往往被忽视。微服务启动慢不完全是性能问题,很多时候是配置导致的。比如某个外网地址不通,服务启动时同步调用外部组件超时,一等就是几十秒。手动部署时如果启动日志停在某个位置长时间不动,优先看是不是在请求外部的注册中心或配置中心,检查网络连通性:

telnet 127.0.0.1 8848

如果目标地址不可达,要么改配置,要么等本地缓存生效。遇到这种问题,最有效的办法是把启动日志级别打开,让框架输出更详细的信息:

java -jar app.jar --logging.level.root=DEBUG

注意这个参数只在排查时用,正常启动不要开DEBUG,否则日志量爆炸,反而更难定位。

5.4 包体过大与瘦身策略

微服务jar包动辄上百兆,手动拷贝到隔离环境的时候非常磨人。有几个实用的瘦身方案:

  • 排除不需要的日志框架依赖,统一日志实现。
  • 排查重复依赖,去掉版本冲突带来的冗余。
  • 将第三方jar外置,再用启动参数指定loader路径。比如用-Dloader.path=/opt/libs,可以把依赖放到jar外部,jar本身只保留业务代码,体积大幅缩小。

我在一次现场交付中试过外置依赖方案,jar从200多兆缩小到十几兆,拷贝速度快了非常多。代价是启动命令变长,且所有运行机器上的外部依赖目录必须一致。这个取舍按实际场景决定,不是任何时候都适合。

6. 手动打包的完整落地顺序

讲了这么多原理和命令,最后整理一份可以直接参照的完整操作序列。这是我目前用手动方式交付微服务项目的标准流程:

  1. 确认环境:检查JDK、Maven版本,验证JAVA_HOME。
  2. 清理本地仓库缓存(可选,但干净环境建议执行rm -rf ~/.m2/repository,再重新拉依赖,避免脏jar干扰)。
  3. 在根目录执行mvn clean install -Dmaven.test.skip=true,全量构建并安装到本地仓库。
  4. 如果只需要某个服务,执行mvn clean package -pl order-service -am -Dmaven.test.skip=true。
  5. 查看每个子模块target目录下的jar,确认是Spring Boot可执行jar。
  6. 在本地启动验证,检查关键配置和注册中心连接。
  7. 拷贝jar和外部配置文件到目标服务器,按需修改外部配置。
  8. 用nohup java -jar启动,查看日志确认启动完成。
  9. 执行健康检查接口,确认服务注册成功并往外提供服务。

这套流程看着简单,但每步都有细节。我在第3步用过package而不是install,结果后续模块构建全部失败;在第7步漏拷贝外部配置,服务启动后连不上数据库;在第6步跳过本地验证,结果到现场赔进去一整天。

7. 写在最后的一点体会

手动打jar包这件事,在CI/CD工具大行其道的今天,看起来有点"落后",但它恰恰是理解Java服务构建链路的最好方式。当你亲眼看着一个多模块项目沿着依赖关系依次编译、测试、打包,再看到最终那个几十兆的jar在空荡荡的服务器上成功启动,你对"MyBatis-Plus为什么打不进去""为什么Nacos的配置没生效""为什么换了台机器就报Port already in use"这些问题的理解,会一下子通透很多。

我个人现在即使在有流水线工具的环境下,也会保持每月至少手动打包一次的习惯。不是为了重复劳动,而是为了让自己始终清楚构建过程中每一步发生了什么。工具能省时间,但不能替你理解问题。这套手动打包的经验,至少在需要临时搭环境、排查依赖疑难杂症、甚至帮别人解决"本地能跑服务器上不行"这类问题时,都不会白费。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 5:48:46

DeepSeek大模型应用场景与落地实践全拆解

要说最近这段时间圈子里讨论最多的话题&#xff0c;大模型从榜单评测真正落到业务系统里绝对算一件。我翻后台消息时&#xff0c;被问到最多的已经不是“哪个模型跑分更高”&#xff0c;而是“DeepSeek大模型到底能用在哪、怎么接才能不翻车”。这个问题其实特别实在&#xff0…

作者头像 李华
网站建设 2026/10/21 5:48:34

技能不是知识:盘点、组合、刻意练习与变现的完整方法论

这两年我越来越确认一件事&#xff1a;如果把“skills”简单翻译成“技能”&#xff0c;很多人会本能地开始数自己会什么——Python、Excel、英语、剪辑、沟通……但真正拉开人与人差距的&#xff0c;往往不是这些能写进简历的名目&#xff0c;而是你如何把技能变成稳定输出的能…

作者头像 李华
网站建设 2026/10/21 5:48:33

MATLAB机器人仿真活体包:MOTOMAN-UP6六自由度建模与运动学验证

简介&#xff1a;本资源是一份面向机器人学初学者与MATLAB实践者的入门级仿真教学材料&#xff0c;聚焦Robotics Toolbox在串联六轴机器人&#xff08;MOTOMAN-UP6&#xff09;建模与运动分析中的典型应用。PDF文档系统呈现4个核心仿真程序&#xff1a;Rob1.m实现机器人结构建模…

作者头像 李华
网站建设 2026/10/21 5:48:32

Linux内核struct file深度解析:从VFS到驱动实战

在Linux内核里&#xff0c;struct file可能是你写驱动、做文件系统或者排查内核问题时最常打交道的结构体之一。很多初学者把它和文件描述符&#xff08;fd&#xff09;混在一起&#xff0c;或者把它当作inode的另一种说法&#xff0c;结果看代码时常常一脸懵。这篇文章我想从实…

作者头像 李华
网站建设 2026/10/21 5:48:21

Ubuntu 18.04自建本地镜像源:apt-mirror内网部署全流程实战

我们搞运维的&#xff0c;最烦的就是给一堆机器装环境的时候走公网&#xff0c;要么慢得要死&#xff0c;要么干脆断网没法装。Ubuntu 18.04系统到现在还有不少老项目在用&#xff0c;如果你手头刚好有几十台机器要按同样的软件包列表部署&#xff0c;与其一台台去apt update蹭…

作者头像 李华
网站建设 2026/10/21 5:47:57

浙江省新苗计划申请书模板:创新立项申请书写作指南与避坑技巧

简介&#xff1a;这份资源是浙江省大学生科技创新活动计划&#xff08;新苗人才计划&#xff09;创新立项申请书的Word模板&#xff0c;面向准备申报该计划的高校本科生与指导教师&#xff0c;帮助解决申报书格式不规范、栏目填写无参考、项目论证思路不清等问题。压缩包内共1个…

作者头像 李华