⚡
Day 1 晚间实战:建立第一个可诊断的 Java 后端进程
第 1 周 · Day 1 晚间实战卡
今晚目标:建立第一个可诊断的 Java 后端进程
预计时间:25~35 分钟。
上午我们把后端理解成“监听端口、处理请求的长期运行进程”。今晚不使用 Spring Boot,直接用 JDK 21 实现项目的第一版服务,确保你能观察:
text启动 Java 进程 → 绑定端口 → 接收 HTTP 请求 → 匹配路径 → 返回状态码和 JSON → 记录访问日志
一、业务需求
为“AI 驱动的企业任务协作系统”建立后端运行基线:
- 服务名称:
task-api - 默认监听:
0.0.0.0:8080 - 可以通过
PORT环境变量修改端口 GET /health返回健康状态GET /api/system-info返回服务信息- 不支持的请求方法返回
405 - 未知地址返回
404 - 每个请求在控制台输出方法和路径
二、代码骨架
创建 src/App.java:
javaimport com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
public class App {
private static final String SERVICE_NAME = "task-api";
private static final String VERSION = "0.1.0";
public static void main(String[] args) throws Exception {
int port = Integer.parseInt(
System.getenv().getOrDefault("PORT", "8080")
);
HttpServer server = HttpServer.create(
new InetSocketAddress("0.0.0.0", port),
0
);
server.createContext("/health", exchange -> {
logRequest(exchange);
if (!isGet(exchange)) {
sendJson(exchange, 405, """
{"error":"method_not_allowed"}
""");
return;
}
sendJson(exchange, 200, """
{
"status": "UP",
"service": "task-api"
}
""");
});
server.createContext("/api/system-info", exchange -> {
logRequest(exchange);
if (!isGet(exchange)) {
sendJson(exchange, 405, """
{"error":"method_not_allowed"}
""");
return;
}
String body = """
{
"service": "%s",
"version": "%s",
"port": %d
}
""".formatted(SERVICE_NAME, VERSION, port);
sendJson(exchange, 200, body);
});
server.createContext("/", exchange -> {
logRequest(exchange);
sendJson(exchange, 404, """
{"error":"not_found"}
""");
});
// 暂时使用默认执行器,并发模型留到第 8 周深入。
server.setExecutor(null);
server.start();
System.out.printf(
"%s %s%n",
"Server started:",
"http://127.0.0.1:" + port
);
}
private static boolean isGet(HttpExchange exchange) {
return "GET".equalsIgnoreCase(
exchange.getRequestMethod()
);
}
private static void logRequest(HttpExchange exchange) {
System.out.printf(
"%s %s%n",
exchange.getRequestMethod(),
exchange.getRequestURI()
);
}
private static void sendJson(
HttpExchange exchange,
int status,
String json
) throws IOException {
byte[] body = json.strip()
.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set(
"Content-Type",
"application/json; charset=utf-8"
);
exchange.sendResponseHeaders(status, body.length);
try (var output = exchange.getResponseBody()) {
output.write(body);
}
}
}三、编译并启动
bashmkdir -p out
javac \
--add-modules jdk.httpserver \
-d out \
src/App.java
java \
--add-modules jdk.httpserver \
-cp out \
App使用其他端口启动:
bashPORT=9090 java \
--add-modules jdk.httpserver \
-cp out \
App四、验收任务
健康检查:
bashcurl -i http://127.0.0.1:8080/health
服务信息:
bashcurl -i http://127.0.0.1:8080/api/system-info
测试不支持的方法:
bashcurl -i \ -X POST \ http://127.0.0.1:8080/health
测试未知路径:
bashcurl -i http://127.0.0.1:8080/not-exist
最终应该分别看到:
| 请求 | 状态码 |
|---|---|
GET /health |
200 |
GET /api/system-info |
200 |
POST /health |
405 |
GET /not-exist |
404 |
还可以检查端口是否真的被进程监听:
bashlsof -nP -iTCP:8080 -sTCP:LISTEN
五、为什么监听 0.0.0.0
代码绑定 0.0.0.0,表示服务接受来自所有网络接口的连接,为以后进入 Docker 做准备。
但启动提示使用的是 127.0.0.1,因为客户端不能把 0.0.0.0 当作普通服务器地址使用:
text0.0.0.0 → 服务端的监听范围 127.0.0.1 → 客户端访问本机的地址
六、常见排错
“Address already in use”:
bashlsof -nP -iTCP:8080 -sTCP:LISTEN
说明端口已被其他进程占用,可以关闭旧进程或通过 PORT=9090 更换端口。
“Connection refused”通常意味着:
- Java 进程已经退出
- 客户端访问了错误端口
- 服务没有成功启动
- 访问地址与监听地址不匹配
返回中文后响应异常:
发送长度必须使用:
javabyte[] body = json.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(status, body.length);不能直接使用 json.length(),因为字符数量不一定等于 UTF-8 字节数量。
七、进阶挑战
增加接口:
httpGET /api/tasks
暂时不连接数据库,只返回统一结构:
json{
"data": [],
"total": 0
}同时保证:
GET返回200POST返回405- 响应类型为 JSON
- 控制台能够看到访问日志
八、上午自测参考
- 连接被拒绝通常来自进程未运行、端口错误、监听地址错误、防火墙或容器端口映射问题。
- 全局变量属于整个服务进程,多个用户会共同读写,可能导致身份串号和并发覆盖。
- 容器内只监听
127.0.0.1时,外部流量可能无法进入;服务通常需要监听0.0.0.0。
完成今晚练习后,我们就拥有了项目的第一个后端运行基线。明天上午会继续讲:一条 HTTP 请求到底由哪些部分组成,以及前端调用接口时为什么会遇到跨域、状态码和请求头问题。
分享:掘金同步
如果这篇对你有帮助,欢迎关注公众号「前端达人」,每周更新实用前端干货。

