
1. 这不是“背书清单”而是一张JavaWeb故障排查地图你打开IDEA点下绿色三角形浏览器弹出HTTP Status 404 – Not Found你刚配好Tomcat启动日志里却刷出SEVERE: Failed to initialize connector [Connector[HTTP/1.1-8080]]你兴冲冲写完一个ServletdoGet()方法里加了System.out.println(Hello from Servlet!)可控制台一片寂静——这些不是玄学是JavaWeb知识体系里最真实的“触点”。我带过三届校招培训发现90%的新人卡在同一个地方他们把Servlet、HTTP、Tomcat、Maven当成四个独立名词去记忆却从没真正理解它们之间那根看不见的“数据流绳索”是如何缠绕、打结、又如何被一把剪刀利落地解开的。这篇复习笔记不按教科书顺序罗列概念而是以一次真实项目启动失败为切口倒推每一个环节的底层逻辑。你会看到mvn clean package命令背后Maven如何用坐标groupId:artifactId:version在本地仓库里精准定位一个JAR包Tomcat启动时conf/server.xml里那个Connector port8080 /标签如何像一扇物理门一样决定着HTTP请求能否真正敲开你的应用大门一个WebServlet(/hello)注解最终如何被编译成字节码、被类加载器载入、又被Servlet容器注册为可调用的端点。所有热词——tomcat安装及配置教程、eclipse创建基于maven的servlet项目、http连接复用——都不是孤立的搜索词条而是这张地图上不同海拔的标记点。如果你正被502 Bad Gateway或404 Not Found反复折磨或者想彻底搞懂为什么换了个Maven仓库地址项目就跑不起来那么这篇笔记就是为你写的。它不教你“怎么点菜单”而是带你亲手拆开Tomcat的bin/startup.bat脚本看清每一行set命令在Windows环境里究竟设置了什么它不告诉你pom.xml里该写什么依赖而是解释清楚为什么javax.servlet-api的scope必须是provided否则打包进WAR包会导致Tomcat启动报java.lang.LinkageError。这不是复习是重装你的JavaWeb认知引擎。2. 知识点解构从HTTP协议到Maven依赖管理的全链路穿透2.1 HTTP协议不只是“请求-响应”而是状态机与连接生命周期的精密舞蹈很多人把HTTP当成一个简单的“发个GET收个200”的管道这直接导致后续所有问题都找不到根因。HTTP 1.1的核心其实是两个相互缠绕的状态机连接状态机和事务状态机。前者管理TCP连接的建立、复用、关闭后者管理单次请求-响应的完整生命周期。当你在浏览器地址栏输入http://localhost:8080/hello并回车背后发生的是DNS解析此处跳过因是localhostTCP三次握手客户端随机选一个源端口如54321向服务端8080端口发起SYNHTTP事务开始TCP连接建立后客户端发送HTTP请求行GET /hello HTTP/1.1紧接着是请求头Host: localhost:8080,Connection: keep-alive,User-Agent: ...连接复用决策点关键就在Connection: keep-alive这个头。如果服务端也支持并返回Connection: keep-alive这个TCP连接就不会在响应结束后立即关闭而是进入TIME_WAIT状态等待后续请求复用。这就是http连接复用的本质——它省去了重复三次握手的开销但代价是服务端必须维护连接池客户端必须正确管理连接超时。很多502 Bad Gateway错误根源就在于反向代理如Nginx与后端Tomcat之间的keep-alive配置不一致Nginx认为连接还活着Tomcat却因超时已将其关闭导致Nginx转发请求时发现连接已断只能返回502。提示在Tomcat的conf/server.xml中Connector标签的connectionTimeout默认20000毫秒和keepAliveTimeout默认与connectionTimeout相同共同决定了连接能“挂起”多久。若你的前端应用频繁发送短间隔请求而keepAliveTimeout设得太小如5000就会出现大量Connection reset by peer错误。再看一个常被忽略的细节HTTP状态码404。它绝不仅仅是“页面没找到”。当Tomcat收到/hello请求它会按以下路径查找资源先查webapps/ROOT/hello目录是否存在再查webapps/ROOT/hello.html或webapps/ROOT/hello.jsp文件最后遍历所有已注册的Servlet映射web.xml或WebServlet看是否有路径匹配/hello的Servlet。所以404错误的排查必须沿着这条路径逐级检查URL是否拼错WAR包是否部署到了正确的webapps子目录WebServlet(/hello)是否写在了正确的类上web.xml里的servlet-mapping是否指向了正确的servlet-name我见过太多人在IDEA里右键Run As - Run on Server却忘了检查Deployment Assembly里是否把src/main/webapp目录映射到了/路径下结果代码全对就是404。2.2 Servlet不是“一个接口”而是容器驱动的、有严格生命周期的组件模型Servlet这个词被严重泛化了。新手常以为public class HelloServlet implements Servlet就是全部殊不知Servlet接口本身只有5个方法init(),service(),destroy(),getServletConfig(),getServletInfo()而真正让开发变得可行的是它的两个核心子类GenericServlet提供通用实现和HttpServlet专为HTTP定制。HttpServlet的精妙之处在于它将service()方法按HTTP动词做了二次分发收到GET请求自动调用doGet()收到POST调用doPost()。这层抽象让你无需手动解析request.getMethod()字符串。但更关键的是生命周期。Servlet不是你new出来的普通对象而是由Servlet容器如Tomcat全权管理的。其生命周期完全由容器控制加载与实例化Tomcat启动时或首次请求到达时根据web.xml的load-on-startup值或注解扫描加载类并调用无参构造器创建实例初始化调用init(ServletConfig config)方法传入配置对象。此时config.getInitParameter(encoding)才能拿到你在web.xml里为该Servlet配置的初始化参数服务每次HTTP请求到来容器都会在一个新线程中调用service()方法销毁Tomcat关闭或应用卸载时调用destroy()方法释放资源。注意init()和destroy()在整个Servlet生命周期中只执行一次而doGet()/doPost()则可能被并发调用成百上千次。因此所有需要一次性初始化的昂贵操作如数据库连接池、缓存加载都应放在init()里所有需要清理的资源如关闭连接池都应在destroy()里完成。把数据库连接写在doGet()里是典型的线程安全灾难。WebServlet注解的原理也源于此。它本质上是一个编译时元数据Tomcat在启动扫描阶段会通过反射读取类上的WebServlet提取urlPatterns {/hello}等信息并在内存中构建一张“URL路径 - Servlet实例”的映射表。这比web.xml配置更灵活但也有陷阱如果你的Servlet类没有被Tomcat的类加载器成功加载比如因为WEB-INF/lib里缺了某个JAR注解根本不会被扫描到自然也就没有映射结果就是404。2.3 Tomcat不是“一个软件”而是由Server、Service、Connector、Engine、Host、Context六层嵌套构成的容器架构把Tomcat当成一个黑盒的“Java Web服务器”是最大的认知误区。它的设计是高度模块化和分层的每一层都有明确的职责边界。理解这六层结构是解决tomcat启动出现、tomcat启动后访问404等一切问题的基石。Server最顶层代表整个Tomcat实例。一个server.xml文件就定义了一个Server。它包含一个或多个Service。Service一个Service是Connector和Engine的组合。一个Server可以有多个Service例如一个处理HTTP一个处理AJP用于与Apache HTTPD集成。Connector这是Tomcat的“网络接入层”。它负责接收客户端连接。Connector port8080 protocolHTTP/1.1 /定义了一个HTTP连接器Connector port8009 protocolAJP/1.3 /则定义了一个AJP连接器。protocol属性决定了它用什么协议解析字节流。Engine一个Service有且只有一个Engine它是所有请求的“处理引擎”。它不直接处理请求而是将请求分发给下属的Host。Host代表一个虚拟主机对应一个域名。Host namelocalhost appBasewebapps /是最常见的配置。appBase指定了Web应用的根目录即webapps文件夹。Context这是最细粒度的容器代表一个单独的Web应用WAR包或目录。每个部署在webapps下的应用如myapp.war或myapp/目录都会被创建一个对应的Context。Context的path属性如/myapp就是应用的上下文路径。现在把http://localhost:8080/myapp/hello这个URL代入这个模型localhost:8080→ 匹配到Connector请求被接收/myapp→Engine根据Host namelocalhost找到Host再根据/myapp路径找到对应的Context/hello→Context内部的Servlet映射机制找到HelloServlet并调用其doGet()。所以当你遇到tomcat启动出现错误第一反应不该是重装而是打开logs/catalina.out看错误堆栈的顶层类名。如果是org.apache.catalina.startup.Catalina开头说明是Server/Service层初始化失败如server.xml语法错误如果是org.apache.catalina.core.StandardContext那问题就出在某个具体的Web应用Context上比如web.xml格式不对或者WEB-INF/lib里有冲突的JAR。2.4 Maven不是“下载jar的工具”而是基于坐标系统的、可复现的项目构建与依赖治理引擎maven是干嘛的这个问题答案绝不能是“用来下载jar包的”。Maven的核心价值在于它用一套唯一、全局、可寻址的坐标系统groupId:artifactId:version解决了Java生态中长期存在的“JAR地狱”问题。想象一下没有Maven的时代你需要手动下载spring-webmvc-5.3.30.jar、jackson-databind-2.13.4.2.jar、logback-classic-1.4.5.jar……然后把它们一股脑丢进lib目录。一旦jackson-databind需要升级你得手动找新包、删旧包、检查版本兼容性——这过程极易出错。Maven的pom.xml本质是一份项目契约。它声明了groupId: 项目所属组织如com.exampleartifactId: 项目名称如my-web-appversion: 项目版本如1.0-SNAPSHOTdependencies: 项目所依赖的其他库的坐标。当你执行mvn clean packageMaven会做三件大事解析依赖树根据pom.xml递归计算出项目所需的所有JAR包及其传递依赖。例如你引入了spring-webmvc它又依赖spring-beans、spring-coreMaven会自动把它们都拉下来。依赖调解Dependency Mediation当多个依赖间接引入了同一个库的不同版本如A依赖log4j-1.2.17B依赖log4j-2.17.1Maven会按“最近原则”nearest definition选择版本。这能避免运行时NoSuchMethodError。构建生命周期执行clean阶段删除target目录compile阶段编译src/main/javapackage阶段将编译后的class和src/main/webapp打包成WAR。实操心得maven配置阿里云仓库之所以成为高频热词是因为Maven官方中央仓库repo.maven.apache.org在国内访问极慢。修改~/.m2/settings.xml在mirrors节点下添加阿里云镜像能将依赖下载速度提升10倍以上。但要注意镜像只是“缓存”它不会改变Maven的依赖解析逻辑只是加速了下载。另一个致命陷阱是scope作用域。scopeprovided/scope是Servlet开发的黄金法则。javax.servlet-api这个JAR由Tomcat在运行时提供你的WAR包里绝对不能包含它。否则当Tomcat启动时它的类加载器会先加载自己lib目录下的servlet-api.jar而你的WAR包里又有一个同名类就会触发LinkageError。provided的作用就是告诉Maven“这个依赖只在编译和测试时需要打包时请忽略”。3. 实操复现从零搭建一个可调试的MavenServletTomcat项目3.1 环境准备避开Windows路径与编码的双重雷区在Windows上配置JavaWeb开发环境有两个经典坑几乎人人都踩过空格路径和GBK编码。首先JAVA_HOME和MAVEN_HOME的路径绝对不能包含中文或空格。C:\Program Files\Java\jdk-11.0.12这种路径会导致Maven在执行mvn compile时javac命令解析参数失败报错error: invalid flag: Files\Java\jdk-11.0.12\lib\tools.jar。解决方案将JDK安装到C:\dev\jdk-11.0.12Maven解压到C:\dev\apache-maven-3.8.6。其次Windows默认的GBK编码与Java源文件的UTF-8编码冲突。当你在IDEA里写了一个含中文的System.out.println(你好)用Maven编译时javac会以GBK读取源文件导致乱码编译失败。解决方法是在MAVEN_HOME/conf/settings.xml的profiles节点内添加一个profileprofile idutf8/id properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties /profile然后在activeProfiles中激活它。这样mvn compile就会带上-Dfile.encodingUTF-8参数确保编译器用UTF-8读取.java文件。3.2 创建Maven骨架用Archetype生成标准Web项目结构不要手动创建一堆空文件夹。Maven提供了archetype原型机制能一键生成符合规范的项目骨架。执行以下命令mvn archetype:generate -DgroupIdcom.example -DartifactIdmy-web-app -DarchetypeArtifactIdmaven-archetype-webapp -DinteractiveModefalse这个命令会生成一个标准的JavaWeb项目结构my-web-app/ ├── pom.xml ├── src/ │ └── main/ │ ├── java/ # Java源码目录初始为空 │ ├── resources/ # 配置文件目录如log4j2.xml │ └── webapp/ # Web资源根目录 │ ├── index.jsp │ └── WEB-INF/ │ └── web.xml注意maven-archetype-webapp生成的pom.xml默认是war打包类型且packaging为war这是Servlet项目的铁律。如果这里写成了jarmvn package会生成一个JAR包而不是WARTomcat自然无法部署。3.3 编写第一个Servlet从web.xml到WebServlet的演进实践我们先用最传统的web.xml方式再迁移到注解方式体会两者的差异。第一步添加Servlet API依赖在pom.xml的dependencies中加入dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependencyscopeprovided/scope再次强调这是生死线。第二步编写Servlet类在src/main/java/com/example/下创建HelloServlet.javapackage com.example; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class HelloServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置响应内容类型避免中文乱码 resp.setContentType(text/html;charsetUTF-8); PrintWriter out resp.getWriter(); out.println(h1Hello from Servlet!/h1); out.println(pCurrent time: System.currentTimeMillis() /p); out.close(); } }第三步配置web.xml编辑src/main/webapp/WEB-INF/web.xml添加Servlet声明和映射?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 servlet servlet-nameHelloServlet/servlet-name servlet-classcom.example.HelloServlet/servlet-class /servlet servlet-mapping servlet-nameHelloServlet/servlet-name url-pattern/hello/url-pattern /servlet-mapping /web-app第四步打包与部署执行mvn clean package会在target/目录下生成my-web-app.war。将此WAR包复制到Tomcat的webapps/目录下启动Tomcatbin/startup.bat访问http://localhost:8080/my-web-app/hello即可看到页面。第五步迁移到WebServlet删除web.xml中的servlet和servlet-mapping配置直接在HelloServlet类上添加注解WebServlet(/hello) public class HelloServlet extends HttpServlet { // ... doGest方法保持不变 }重新mvn clean package部署访问效果完全一样。注解方式更简洁但web.xml并未过时它仍是配置过滤器Filter、监听器Listener和全局初始化参数的唯一方式。3.4 Tomcat集成调试在IDEA中实现热部署与断点调试在IDEA中直接运行Tomcat远比手动拷贝WAR包高效。配置步骤如下打开File - Project Structure - Project确认Project SDK和Project language level设置正确如JDK 11File - Project Structure - Modules确认Sources和Resources路径正确指向src/main/java和src/main/resourcesRun - Edit Configurations...点击左上角号选择Tomcat Server - Local在Server选项卡中Application server点击Configure...选择你的Tomcat解压目录在Deployment选项卡中点击号选择Artifact然后选择my-web-app:war exploded注意是exploded即解压模式支持热部署点击OK保存。此时点击IDEA右上角的绿色三角形IDEA会自动启动Tomcat并将项目以“解压”形式部署到tomcat/work/Catalina/localhost/my-web-app/目录下。你可以在HelloServlet的doGet()方法第一行打上断点刷新浏览器IDEA会自动停在断点处你可以查看req和resp对象的所有属性这是学习Servlet内部机制的最直观方式。注意exploded模式下修改Java文件后IDEA会自动编译并热替换class无需重启Tomcat。但修改web.xml或pom.xml仍需重启。4. 故障排查实战从502 Bad Gateway到404 Not Found的速查手册4.1HTTP Status 404 – Not Found五层排查法404是JavaWeb开发中最常见的错误但它背后的原因千差万别。我们按Tomcat的六层架构自上而下进行排查排查层级检查项常见现象快速验证方法Connector (网络层)Tomcat是否在监听8080端口浏览器显示ERR_CONNECTION_REFUSEDnetstat -ano | findstr :8080Windows或lsof -i :8080Mac/LinuxHost (虚拟主机)server.xml中Host的name是否与访问域名一致访问http://127.0.0.1:8080正常但http://localhost:8080404将Host namelocalhost改为Host name127.0.0.1或反之Context (应用层)WAR包是否成功部署webapps/目录下是否有对应文件夹logs/catalina.out中出现Deploying web application directory日志查看webapps/目录确认my-web-app/文件夹存在且非空Servlet映射 (路由层)URL路径是否与WebServlet或web.xml中的url-pattern完全匹配访问/hello404但/hello/多一个斜杠却成功检查WebServlet(/hello)确认没有多余空格或字符类加载 (代码层)Servlet类是否被正确编译并放入WEB-INF/classes/logs/catalina.out中出现ClassNotFoundException: com.example.HelloServlet进入webapps/my-web-app/WEB-INF/classes/com/example/确认HelloServlet.class存在我曾遇到一个诡异案例WebServlet(/hello)明明写对了但就是404。最后发现HelloServlet.java文件保存时IDEA默认用了CRLFWindows换行符而Tomcat在Linux服务器上运行对换行符敏感导致注解解析失败。将文件编码改为UTF-8换行符改为LF后问题解决。4.2Unexpected status 502 Bad Gateway反向代理与上游服务的握手失败502 Bad Gateway错误通常意味着你前面有一层反向代理如Nginx、Apache它作为“网关”将客户端请求转发给后端的Tomcat但Tomcat未能给出有效的HTTP响应。这与Tomcat自身的404完全不同。排查思路必须跳出Tomcat聚焦在代理层与上游的通信上检查代理配置Nginx的upstream块中server地址是否正确指向了Tomcat的IP和端口例如upstream backend { server 127.0.0.1:8080; }。如果Tomcat监听的是127.0.0.1:8080而Nginx配置的是server 192.168.1.100:8080必然502。检查连接健康度Nginx默认会对上游服务器做健康检查。如果Tomcat进程崩溃或端口未监听Nginx会将其标记为down后续请求直接返回502。查看Nginx的error.log搜索upstream关键字会看到类似no live upstreams while connecting to upstream的错误。检查超时设置这是最隐蔽的坑。Nginx的proxy_read_timeout默认60秒必须大于Tomcat的connectionTimeout默认20秒。如果Tomcat处理一个请求耗时70秒Nginx在60秒后就会主动断开连接向上游返回502。解决方案是统一调大两者例如都设为120秒。检查Keep-Alive如前所述Nginx与Tomcat的keep-alive配置必须一致。在Nginx的location块中添加proxy_http_version 1.1; proxy_set_header Connection ;这行proxy_set_header Connection 至关重要它清除了客户端发来的Connection: close头强制Nginx与Tomcat之间使用长连接。4.3Tomcat启动出现日志分析的黄金三步法当Tomcat双击startup.bat后一闪而逝或IDEA控制台刷出大量红色错误不要慌。所有答案都在logs/catalina.out里。分析日志遵循三步法第一步定位错误源头滚动日志到最底部找到第一个以SEVERE:或ERROR:开头的行。例如SEVERE: Failed to initialize connector [Connector[HTTP/1.1-8080]] ... Caused by: java.net.BindException: Address already in use: bind这行Caused by:就是真正的根因——端口8080已被占用。第二步追溯堆栈路径从Caused by向上看找到at org.apache.catalina.startup.Catalina.load(Catalina.java:xxx)这一行。Catalina是Tomcat的启动入口类这意味着错误发生在Server/Service初始化阶段与具体Web应用无关。第三步执行针对性修复BindException: Address already in use→netstat -ano | findstr :8080找到PIDtaskkill /PID xxx /F杀掉进程java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener→ 检查pom.xml是否漏掉了spring-web依赖或web.xml中listener-class写错了包名java.lang.OutOfMemoryError: Metaspace→ 在bin/catalina.bat中set JAVA_OPTS...后添加-XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m。实操心得在bin/catalina.batWindows或bin/catalina.shLinux的JAVA_OPTS变量中永远加上-Dfile.encodingUTF-8 -Duser.timezoneGMT08。前者解决中文乱码后者解决new Date()在不同时区显示的时间差问题这是生产环境的标配。4.4 Maven依赖冲突mvn dependency:tree是你的X光机mvn clean package成功但运行时报NoSuchMethodError或NoClassDefFoundError十有八九是依赖冲突。Maven的dependency:tree插件就是你的X光机能透视整个依赖树。执行命令mvn dependency:tree -Dincludesorg.slf4j:slf4j-api它会输出所有与slf4j-api相关的依赖路径例如[INFO] com.example:my-web-app:war:1.0-SNAPSHOT [INFO] \- org.springframework:spring-webmvc:jar:5.3.30:compile [INFO] \- org.springframework:spring-web:jar:5.3.30:compile [INFO] \- org.springframework:spring-beans:jar:5.3.30:compile [INFO] \- org.slf4j:slf4j-api:jar:1.7.32:compile [INFO] \- ch.qos.logback:logback-classic:jar:1.4.5:compile [INFO] \- ch.qos.logback:logback-core:jar:1.4.5:compile [INFO] \- org.slf4j:slf4j-api:jar:2.0.7:compile这里清晰地看到spring-webmvc引入了slf4j-api:1.7.32而logback-classic引入了slf4j-api:2.0.7。Maven按“最近原则”选择了2.0.7但Spring 5.3.30是为1.7.x编译的调用LoggerFactory.getLogger()时2.0.7的API签名已变导致NoSuchMethodError。解决方案是在pom.xml中用exclusions排除掉不需要的版本dependency groupIdch.qos.logback/groupId artifactIdlogback-classic/artifactId version1.4.5/version exclusions exclusion groupIdorg.slf4j/groupId artifactIdslf4j-api/artifactId /exclusion /exclusions /dependency然后显式声明一个统一的slf4j-api版本。这便是Maven依赖管理的艺术不是简单地“加”而是精确地“裁”。5. 经验沉淀那些只有踩过坑才懂的硬核技巧5.1 Tomcat端口冲突的“静默杀手”Skype与IISAddress already in use错误大家都知道是端口被占。但有一个“静默杀手”常被忽略Skype。老版本的Skype8.0默认会劫持80和443端口用于P2P通信。当你在server.xml中把Connector port80 /却发现启动失败netstat却查不到任何进程占用80大概率就是Skype在作祟。解决方案打开Skype -Tools - Options - Advanced - Connection取消勾选Use port 80 and 443 as alternatives for incoming connections。另一个是Windows自带的IISInternet Information Services。如果你的电脑启用了IIS它也会监听80端口。在Windows服务管理器services.msc中找到World Wide Web Publishing Service将其停止并设置为“禁用”即可释放80端口。5.2web.xmlvsWebServlet何时该用哪个WebServlet看似先进但并非万能。我的经验是优先用WebServlet对于简单的、功能单一的Servlet如一个登录验证Servlet、一个数据导出Servlet注解方式代码更紧凑易于理解和维护。必须用web.xml当需要配置初始化参数init-param或加载顺序load-on-startup时。例如一个数据库连接池Servlet你可能需要在init()方法中读取maxPoolSize、minPoolSize等参数这些参数只能在web.xml中配置。WebServlet虽然也支持initParams但它是静态的无法在运行时动态更改。5.3 Maven离线开发-o参数与本地仓库的终极备份公司内网或出差时没有外网mvn clean package会卡死在下载依赖上。Maven的-ooffline参数就是为此而生。但前提是你必须提前在有网环境下将项目所需的所有依赖都下载到本地仓库~/.m2/repository。终极备份技巧将整个~/.m2/repository文件夹压缩成m2-repo-backup.zip存到U盘。当需要离线开发时解压到新电脑的~/.m2/目录下然后所有mvn命令都加上-o参数例如mvn clean compile -o。这样即使没有网络Maven也能从本地仓库完美构建项目。这是我给所有学员的“生存包”。5.4 HTTP协议调试神器curl与telnet的组合拳当浏览器无法给出足够信息时curl和telnet是你的最佳搭档。curl -v http://localhost:8080/hello-vverbose参数会打印完整的HTTP请求和响应头。你能