新闻详情

新闻详情

首页 / 资讯中心 / 详情

Spring Boot中Tomcat配置全指南:从内嵌到外部部署

发布时间:2026/10/1 21:19:08来源:尧图网络
Spring Boot中Tomcat配置全指南:从内嵌到外部部署
1. 先搞清楚Spring Boot到底需要配哪个Tomcat很多人在Spring Boot里配Tomcat上来就找server.port往配置文件里塞结果发现一堆坑改了端口不生效、静态资源404、WebSocket狂断连、并发一上来就502。这篇文章我把Spring Boot中Tomcat配置这件事从头捋一遍从内嵌模式到外部部署从连接器参数到JVM调优把我这些年踩过的坑和验证过的配置都掏出来尽量一篇讲透。先说一个最根本的问题Spring Boot项目里的Tomcat其实有两种完全不同的形态。第一种是内嵌Tomcat。你用spring-boot-starter-web启动一个Spring Boot应用运行java -jar app.jar这时候Tomcat是作为内嵌容器跟着应用一起启动的它不是你单独安装的那个Tomcat而是被Spring Boot打包进项目依赖里的一个库。你没法去改它的server.xml也没法直接改它的bin/catalina.sh所有配置都得通过Spring Boot的配置文件application.yml或者application.properties来传递。第二种是外部Tomcat。你把Spring Boot项目打成war包扔到一个独立安装的Tomcat的webapps目录下由那个Tomcat来加载运行。这种情况下Tomcat是独立进程你可以随意改server.xml、catalina.sh也可以在IDEA里给这个项目专门配置一个Tomcat运行环境。这两种形态的配置方式完全不同很多人分不清导致配置串了。我见过不少同事把内嵌配置写在application.yml里然后部署到外部Tomcat后死活不生效也见过有人把外部Tomcat的server.xml端口改了结果Spring Boot内嵌Tomcat还是用自己配置文件里的端口——因为压根就不是同一个Tomcat。所以配置之前先确认你项目到底是哪种跑法这是第一原则。后面我分两条线来讲内嵌模式的配置要点以及外部Tomcat的部署与配置。2. 内嵌Tomcat的核心配置项照着抄就行2.1 端口与基础参数别只改一个port就完事内嵌Tomcat的配置入口就是Spring Boot的配置文件。先看一组最基础也最常用的配置server: port: 8080 address: 0.0.0.0 error: include-message: always include-stacktrace: always compression: enabled: true mime-types: application/json,application/xml,text/html,text/plain min-response-size: 2048 shutdown: gracefulport不用多说唯一要提醒的是如果你同时配了server.address要注意address指定的是Tomcat监听的网卡地址。0.0.0.0代表所有网卡都能访问如果你只想让本机访问就配127.0.0.1。有次我把服务部署到服务器上端口映射都做好了外面就是访问不了查了半天发现是某次调试顺手把address配成了127.0.0.1等于把服务锁在了本机。shutdown: graceful这个配置我很推荐打开。Spring Boot 2.3之后支持优雅停机开启后应用收到停止信号时会先停止接收新请求然后等待已进行的请求处理完再退出。配合spring.lifecycle.timeout-per-shutdown-phase: 30s可以控制最长等待时间。生产环境升级服务时这个配置能明显减少请求被硬断的报错。2.2 连接器参数maxThreads、acceptCount、maxConnections到底啥关系这是内嵌Tomcat配置里最核心的部分也是很多人最糊涂的地方。先看配置server: tomcat: max-threads: 200 accept-count: 100 max-connections: 10000 min-spare-threads: 20 connection-timeout: 5000 keep-alive-timeout: 20000 max-keep-alive-requests: 500这几个参数很容易混。我用一个生活化的类比来解释想象一个银行网点。maxThreads就是柜台窗口数量所有请求都要排到窗口前由柜员处理这决定了你同时能处理多少个请求。acceptCount是银行大堂的等候区座位数柜台全忙的时候新来的客户可以先在大堂坐着等。maxConnections则是银行门口的安检通道总容量——包括正在办业务的、在大堂等着的、还有站在门口排队的。连接数超过这个上限新请求就直接被拒之门外了连队都排不上。从这个类比能看出maxThreads是真正干活的线程数设太小请求会积压设太大线程上下文切换的开销会吃掉性能。acceptCount是Tomcat接受连接请求后的等待队列长度当所有工作线程都忙时新请求先进入这个队列。队列满了请求就会被拒绝通常是Connection refused。maxConnections是Tomcat能同时接受的TCP连接总数它包含了正在处理的连接和等待处理的连接。达到上限后新的连接会被拒绝。实际配置的时候这三者必须协调。我给个参考思路先根据业务RT接口平均响应时间估算QPS上限比如接口平均耗时200ms单线程每秒能处理5个请求200个线程每秒最多处理1000个请求。这是理论峰值实际还要留30%到50%的余量。然后maxConnections一般远大于maxThreads因为TCP连接可以有很多处于keep-alive空闲状态并不占用线程。acceptCount则根据你愿意让请求等多久来定。网上很多文章会建议把maxThreads调到1000甚至更高我实际用下来的感受是大多数业务200到400就足够了。线程多到一定程度性能提升微乎其微反而CPU都在做线程切换。如果你需要几千的并发量优先考虑横向扩容而不是单机死扛。2.3 超时与Keep-Alive细节决定用户体验再看几个和时间相关的参数connection-timeout接收TCP连接后等待客户端发送请求的时间。默认值一般是60000ms60秒我习惯设成5000ms。为什么要缩短因为正常浏览器发请求是毫秒级的如果5秒还没发出完整请求要么是客户端异常要么是网络问题没必要傻等。keep-alive-timeout一个HTTP长连接空闲多久后关闭。HTTP/1.1默认复用连接如果一个连接空闲太久还占着浪费连接资源。设成20秒左右很常见配合Nginx的keepalive配置一起调。max-keep-alive-requests一条长连接最多能复用多少次。默认是100可以调高到500甚至1000。调高后客户端复用连接的次数更多减少了频繁建立TCP连接的开销。我实际排障时遇到过一种情况前端轮询接口每隔几秒请求一次没用WebSocket却总是出现大量TIME_WAIT。后来排查发现是Nginx到Tomcat的keep-alive没配好导致每次请求都新建连接TCP四次挥手全卡在客户端造成了大量TIME_WAIT。调好keep-alive相关参数后TIME_WAIT明显下降。2.4 还有两个容易被忽略的配置内嵌Tomcat还有两个我经常用的配置点server: tomcat: uri-encoding: UTF-8 max-swallow-size: 10MB servlet: context-path: /api session: timeout: 30muri-encoding设置URI编码如果不显式配置Tomcat默认按ISO-8859-1处理GET请求带中文参数时大概率乱码。Spring Boot 2.x之后这个配置虽然很多时候被Spring的编码过滤器掩盖了问题但万一你的项目用了原生的HttpServletRequest.getParameter()就有你受的了。max-swallow-size是限制请求体的最大体积超过会抛异常。这个参数配合spring.servlet.multipart.max-file-size几乎同时使用很多人只配了max-file-size没配max-swallow-size结果大文件上传还是失败报错还不明显。context-path设置应用的上下文路径相当于所有接口统一加前缀/api。老项目从外部Tomcat迁移到内嵌时这一步最容易漏漏了就等着前端全部404吧。3. 外部Tomcat部署从war包到生产环境3.1 把Spring Boot打成war包而不是jar包有些场景你不得不用外部Tomcat例如运维统一管理所有Java应用要求标准war包部署或者项目里有一些依赖内嵌Tomcat跑不起来的老代码必须用外部容器提供某些特殊能力。这时候第一步就是要让Spring Boot能打出war包。先在pom.xml里做两处改动packagingwar/packaging然后修改启动类继承SpringBootServletInitializerSpringBootApplication public class DemoApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(DemoApplication.class); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }这两步做完mvn clean package就能打出war包。注意这里的configure方法的返回值一定要是SpringApplicationBuilder手滑写错或者漏了war包是能打出来但启动会找不到Spring的入口。打好的war包放到Tomcat的webapps目录下启动Tomcat后war包会自动解压项目名默认就是war包的文件名访问路径是http://ip:8080/war包名/接口路径。这里有个很多人问过的问题war包能不能同时兼容java -jar运行可以。保持main方法不动jar和war两种方式都能启动只是jar方式仍然用内嵌Tomcatwar方式由外部Tomcat加载。双模式启动在测试时很方便我一般都会保留。3.2 IDEA中配置Tomcat运行JavaWeb项目如果你在IDEA里做开发调试不想每次手动打包可以在IDEA里直接配一个Tomcat运行环境。步骤很简单Run-Edit Configurations点加号选Tomcat Server - Local然后在Deployment选项卡里点加号选Artifact选中war包对应的artifactApplication context填写想用的上下文路径。启动前IDEA会自动把项目编译成war包并部署到本地Tomcat的临时目录里不用自己手动复制。这里要吐槽一个非常常见的坑很多人配置完IDEA里的Tomcat后访问地址一直报404页面提示源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源。别慌大概率不是代码问题而是Application context配置没填对。你填/api就访问http://localhost:8080/api/index填的是/或者什么都不填访问路径就要去掉项目名。这个我以前也卡过好久实际上就是IDEA默认的context和项目实际路径不一致。还有一个细节IDEA自带Tomcat集成时默认会使用-Dspring.profiles.active这些JVM参数吗不会你得自己在VM options里加。我经常在IDEA里跑不同环境配置都是直接在Run Configuration里加-Dspring.profiles.activedev省得来回改application.yml。3.3 部署前后端分离项目Context路径与静态资源映射现在的项目基本都是前后端分离前端打包后是一堆静态文件HTML、JS、CSS后端是Spring Boot接口。用外部Tomcat部署前后端分离项目时有两个思路思路一前端静态文件也放在Tomcat的webapps/ROOT下后端war包放在另一个context路径下。前端通过相对路径或者配置的API地址访问后端接口同域部署不存在跨域问题。思路二前端单独部署在Nginx上后端war包放在Tomcat里通过Nginx反向代理转发/api开头的请求到Tomcat。这是生产环境最常见的架构Nginx负责静态文件、负载均衡、域名转发、HTTPS终结Tomcat只负责Java接口。我在linux下部署这类项目时常用的组合是前端静态文件放/opt/frontendNginx配置root指向该目录location /api/ { proxy_pass http://127.0.0.1:8080; }后端war包放Tomcat的webapps/ROOT.war也就是让后端接口直接在8080根路径下提供服务这样Nginx转发时不需要处理路径重写能省掉一大半配置问题。说到ROOT.war这是个值得记住的技巧如果你不想让项目名出现在URL里把war包改名为ROOT.war放到webapps下访问时就不需要写项目名了路径直接是http://ip:8080/接口路径。对于前后端分离部署来说ROOT模式方便很多尤其是Nginx代理配置更干净。4. 并发调优与JVM参数不搞明白就只能瞎试4.1 线程池与连接器的关系以及怎么估算线程数前面讲了maxThreads、acceptCount、maxConnections的作用这里再补充一个容易出问题的点线程池耗尽时的表象。线程池满的时候Tomcat日志里会出现All threads are busy或者客户端那边表现为响应时间骤增、部分请求直接超时。问题来了为什么线程池满有些请求还是能连上因为连接器层面的TCP连接和线程池是解耦的连接可以等待线程才是真正干活的。所以调优的顺序我的经验是先看监控确认瓶颈在哪如果CPU使用率低但响应慢大概率是线程池或锁的问题如果CPU长时间飙满说明计算量太大加线程也没用重点应该是优化接口逻辑或加机器。测试时可以参考这个估算方法单线程QPS 1000 / 接口平均耗时(ms) 理论QPS maxThreads × 单线程QPS 实际QPS需要预留缓冲一般按理论值的60%左右评估比如接口平均耗时200msmaxThreads设200理论QPS是1000实际评估600左右比较稳。然后根据线上监控逐步调整先观察线程活跃数如果长期保持在150以上可以考虑加到300如果长期在50以下说明资源浪费了调低到100还能省内存。4.2 Tomcat启动设置JVM参数三个不同的配置入口JVM参数这块很多教程写得很散。我按使用场景分三类来说第一类是开发环境改IDEA配置。在Run/Debug Configurations的VM options里填形如-Xms512m -Xmx1024m -Dfile.encodingUTF-8 -Dspring.profiles.activedev注意这里的-D开头的系统属性和Spring Boot的配置是两套体系-Dserver.port8081这种虽然后面会被Spring Boot读取但不推荐写在VM options里管理环境相关的配置还是放在配置中心或环境变量里更规范。第二类是外部Tomcat改catalina.sh或setenv.sh。Tomcat启动时读取JVM参数的入口是CATALINA_OPTS推荐的做法是新建一个setenv.sh文件放到bin目录下内容形如CATALINA_OPTS-Xms512m -Xmx1024m -XX:UseG1GC -XX:MaxMetaspaceSize256m为什么要单独建setenv.sh因为Tomcat的catalina.sh在升级或重装时会覆盖你直接在catalina.sh里改的配置全没了。setenv.sh是Tomcat预留的扩展点启动时自动被加载升级覆盖了catalina.sh也没影响。第三类是Docker部署场景用JAVA_OPTS环境变量传入docker run -e JAVA_OPTS-Xms512m -Xmx1024m -p 8080:8080 myapp这里有个很实用的经验JVM参数在容器场景下一定用环境变量方式注入别写死在Dockerfile里。同一个镜像在不同环境开发、测试、生产跑内存规格可能完全不同写死在Dockerfile里意味着每次都要重新构建环境变量注入则只需改启动参数。至于具体怎么设-Xms和-Xmx我的建议是生产环境把这两个值设成一样的。理由很简单避免JVM在运行期间频繁扩容和缩容堆内存。设成相同值等于一开始就分配够空间虽然启动时占用的物理内存多一点但运行期的GC压力更稳定不会出现用着用着Full GC变频繁的诡异现象。小应用如256m到512m起步压力大的服务2g到4g很常见具体看你的业务估算。如果一台机器上跑多个Java服务记住一个原则所有JVM的-Xmx总和不能超过物理内存的70%到80%要留空间给系统缓存和外部资源。4.3 Tomcat本身的内存池Metaspace与Thread Stack除了堆内存还有两个参数容易被忽略-XX:MaxMetaspaceSize控制的是类元数据区的上限。Spring Boot项目启动时会加载大量类如果项目打了很多依赖Metaspace增长很快。默认情况下Java 8之后的Metaspace理论上限是本机可用内存不限的话可能吃掉大量物理内存。我习惯给256m到512m对于大多数Spring Boot服务来说绰绰有余。-Xss是线程栈大小。默认通常是1m如果你的maxThreads设到500以上每多一个线程就多占1m的栈内存500个线程就是500m这个开销相当可观。业务逻辑不复杂时-Xss256k到-Xss512k是够用的。但注意如果接口里递归比较深或者有复杂的调用链栈太小可能抛StackOverflowError这个需要压测验证。如果Tomcat启动时提示OutOfMemoryError: unable to create new native thread说明进程的线程数已经到了系统上限。这时候未必是JVM堆不够很可能是线程栈空间设太大导致每个线程占用的内存多或者Linux的ulimit -u进程数限制被触发了四件事一起排查堆内存、线程栈、系统文件描述符、用户进程数。5. 常见问题与排查技巧实录5.1 启动就闪退/无法启动先看这三种原因Tomcat启动失败或闪退是很常见的问题但大多数人第一反应是上网搜Tomcat闪退。最常见的三种原因第一种是端口冲突。Spring Boot的默认端口8080被占了启动时抛Port already in use。解决方法lsof -i:8080或者Windows下netstat -ano | findstr 8080找到占用进程杀掉或者改端口。如果改了server.port还不生效大概率项目是部署在外部Tomcat里真正生效的端口是server.xml里配的端口。第二种是JVM参数错误。-Xmx设置过大超出机器物理内存或者格式写错JVM启动直接失败。日志里会明确提示。这个问题在配置了JAVA_OPTS的容器场景里比较常见检查启动脚本或环境变量即可。第三种是启动过程中加载的配置有问题比如数据库连接不上、配置中心拉不到配置导致Spring容器初始化失败进程自动退出。这种情况日志通常在应用启动的报错里Tomcat本身进程是起来了的只是应用启动失败。很多人在Tomcat启动日志里看不到端倪就跑偏了要记得把Spring Boot应用的日志也一起看。排查这些问题的通用顺序先看启动日志看Tomcat是否成功初始化连接器然后再看应用日志确认Spring上下文是否加载完成。两边都过一遍基本能快速定位。5.2 部署后访问404路径问题排第一部署web项目最讨厌的就是404问题特别是在IDEA配置Tomcat的场景。常见的404有三种应用上下文路径不对。前面已经说过context-path或war包名与访问URL不一致就会出现404。前端请求的后端接口前缀不对。前后端分离时Nginx或前端代理配的/api前缀和后端实际路径对不上也会404。很多bug在浏览器Network里一眼能看出来接口路径多了一段或少了一段。静态资源404。Spring Boot内嵌Tomcat对静态资源有默认的映射规则classpath:/static/目录下的文件放在/static/**下可访问。但如果你用外部Tomcat直接访问war包里的静态资源路径逻辑完全不同经常会遇到图片或JS加载不出来。这种问题别琢磨Spring Boot的规则了直接看外部Tomcat的webapps目录结构静态文件是不是真的在那个位置。5.3 WebSocket连接频繁断开多半是超时和代理设置的事Spring Boot中集成WebSocket后很多人遇到连接不稳定、频繁断开的问题。之前的热词中就有spring boot 集成web socket yml 配置这在配置Tomcat时确实是个坎。先说基础配置。WebSocket是长连接需要调大相关超时时间同时注意内嵌Tomcat的keep-alive-timeout对WebSocket的兼容影响server: tomcat: keep-alive-timeout: 60000 max-keep-alive-requests: 10000max-keep-alive-requests这里要调大因为WebSocket连接本质上是HTTP Upgrade协议升级长连接一直复用如果这个值太小连接会被频繁关闭。但真实的坑往往在后头如果你的服务前面挂了NginxNginx默认的proxy_read_timeout是60秒也就是说60秒内没有数据传输Nginx会把连接掐断。WebSocket连接大部分时间是空闲的很容易超过60秒然后连接就断了。解决方法是调整Nginx的配置proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s;我遇到过几次WebSocket间歇性断开的问题日志里完全没有异常客户端显示连接关闭服务端没有日志。最后定位都是Nginx的空闲超时掐断的不是Tomcat的问题改完Nginx配置就正常了。5.4 内嵌Tomcat的类加载问题所谓双亲委派机制Tomcat打破双亲委派机制这个话题在热词里出现了我讲一下实际影响。JVM默认的类加载机制叫双亲委派类加载器先让父加载器去加载类父加载器找不到才自己加载。Tomcat打破了这种机制它有一个WebappClassLoader会优先自己加载而不是先交给父加载器。原因很简单一个Tomcat里可以部署多个Web应用每个应用的依赖版本可能冲突比如应用A要用Spring 4应用B要用Spring 5如果都让父加载器加载它们会互相覆盖根本没法共存。Tomcat给每个Web应用一个独立的类加载器自己加载自己WEB-INF下的类这样应用之间就隔离了。这个机制对开发者的实际影响是什么呢最常见的案例内嵌Tomcat和外部Tomcat类加载行为不一致。内嵌Tomcat里你的应用是只有一个Spring Boot应用的类加载器外部Tomcat里你的应用类加载器要处理整个war包的WEB-INF/classes优先、WEB-INF/lib下的jar包父加载器层面还有Tomcat自身的lib目录。如果你在CATALINA_HOME/lib下放了一些jar包理论上父加载器优先实际上只要应用里也带了同样类路径的jar包很容易出现ClassCastException或NoSuchMethodError因为同一个类被两个不同的类加载器各自加载了一次。这算是我踩过的比较隐蔽的坑开发环境跑内嵌Tomcat一切正常部署到公司统一维护的Tomcat后启动偶尔报一些莫名其妙的ClassNotFoundException查来查去发现是CATALINA_HOME/lib里有旧版本的库跟应用jar包冲突了。解决方法是把外部Tomcatlib目录下没有用的jar包清干净尽量只保留Tomcat自带的那些业务相关的依赖一致放在应用war包里别往Tomcat的公共lib里塞。实际排查类加载问题有个技巧在启动参数加-verbose:class或者出现异常时用Thread.currentThread().getContextClassLoader()和getClass().getClassLoader()两个方法打印对比看同一个类是否被多个类加载器加载了。如果两个ClassLoader实例不一样就可以确定是类加载隔离导致的冲突问题。6. 一些配置之外的个人体会Spring Boot项目中Tomcat的配置说到底核心并不是背参数而是要理解你运行的是一个内嵌容器还是一个独立容器这两种形态的配置体系完全是两套逻辑。我见过太多人把server.xml的事情往application.yml上套也见过太多人把application.yml的参数往外部Tomcat上套最后都折腾半天。先定运行形态再选配置体系这个顺序千万不能乱。另外再多说一句配置参数不是越大越好。maxThreads调高到2000也许能扛更大的瞬时流量但代价是更多的线程、更大的内存和更长的GC暂停。真正合理的做法是压测监控双管齐下通过线程活跃数、连接数、RT三个指标反复校准找到最贴近实际业务的阈值。最后一个小技巧收尾Spring Boot应用启动时如果你想知道当前生效的Tomcat配置到底是什么直接在启动参数加--debug看自动配置报告或者在application.yml里加一行server: tomcat: basedir: ./tomcat-logs把Tomcat的运行目录显式指定出来。这样调试期间Tomcat的临时文件和日志都会落到项目目录下排查连接器问题会比看运营商的日志高效得多。配置这东西纸上谈兵没用跑起来看日志调参数才有意义。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

AI日报自动化流水线:信源分层、打分模型与人工编辑的工程实践 2026/10/1 22:20:41

AI日报自动化流水线:信源分层、打分模型与人工编辑的工程实践

1. 一份AI日报的诞生:从信息洪流到可读清单每天早上七点,我的信息采集脚本会准时跑完一轮。屏幕上滚过几百条更新:模型发布、论文预印、产品迭代、行业并购、开源项目提交、监管草案、算力价格波动……如果把这些原封不动丢给团队&#xff0c…

阅读更多 →
Time-TK:多偏移时间嵌入驱动的时序建模新范式 2026/10/1 22:20:35

Time-TK:多偏移时间嵌入驱动的时序建模新范式

1. 这不是又一个“TransformerXX”的缝合怪:Time-TK的底层动机是什么?你肯定见过太多标题党——“TransformerCNN”、“TransformerGNN”、“TransformerAttention增强”,点进去一看,不过是把两个模块简单拼接,加个残差…

阅读更多 →
企业AI落地难?FDE工程师带你从0到1打通业务流程,提升效率 2026/10/1 22:20:34

企业AI落地难?FDE工程师带你从0到1打通业务流程,提升效率

企业购买AI工具后,往往因未能有效融入真实业务流程而导致效果不彰。文章提出FDE(前向部署工程师)模式,通过深入现场与员工共同工作,判断并解决最值得解决的问题,以小范围试点上线的方式逐步推广。FDE服务不…

阅读更多 →
Copilot Pro 300次/月配额不够用?2026年Java程序员的TaoToken应对策略 2026/10/1 22:20:28

Copilot Pro 300次/月配额不够用?2026年Java程序员的TaoToken应对策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
自动驾驶数据集如何适配YOLOv5目录格式与训练实践 2026/10/1 22:20:28

自动驾驶数据集如何适配YOLOv5目录格式与训练实践

简介:面向自动驾驶场景的YOLOV5格式目标检测数据集,涵盖卡车、行人、交通信号灯等11个类别,并划分好训练集与验证集。数据将图片与标签统一按YOLOV5目录存放,无需额外处理即可直接开展模型训练与验证,适合目标检测学习…

阅读更多 →
PHP 8.2的交叉类型怎么用才规范 2026/10/1 22:20:28

PHP 8.2的交叉类型怎么用才规范

前言先纠正版号:纯交叉类型(intersection types,写作 A&B)是 PHP 8.1 引入的,不是 8.2。PHP 8.2 带来的其实是 DNF 类型(Disjunctive Normal Form types),也就是允许把交集和并集…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉