Jetty
Jetty 是一个开源的轻量级 Web 服务器和Servlet 容器,专为高吞吐量和低延迟场景设计。它与 Apache Tomcat 类似,但架构更灵活,常用于嵌入式系统、微服务和需要快速启动的应用场景。
Servlet
Servlet是运行在 Web 服务器(如 Tomcat、Jetty)上的 Java 程序,负责处理客户端请求(如 HTTP 请求)并返回响应。
作为Servlet容器的Jetty,会根据“配置映射”,找到请求对应的Servlet。比如在本例中,webapp/WEB-INF/web.xml
进行了如下配置
<servlet>
<servlet-name>CarsServlet</servlet-name>
<servlet-class>org.apache.olingo.server.sample.CarsServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CarsServlet</servlet-name>
<url-pattern>/cars.svc/*</url-pattern>
</servlet-mapping>
这个配置告诉容器:
- Servlet 类:org.apache.olingo.server.sample.CarsServlet
- URL 映射:所有 /cars.svc/* 的请求都交给这个 Servlet 处理
- 启动时机:load-on-startup=1 表示容器启动时立即初始化
当你访问 http://localhost:9080/cars.svc/Cars 时:
当Jetty 接收请求后:
- 根据 web.xml 中的 /cars.svc/* 匹配
- 找到对应的 CarsServlet 类
- 调用 CarsServlet.service() 方法处理请求
Servlet生命周期和线程安全
完整交互流程
- 客户端发送 HTTP 请求
- 浏览器向 Web 服务器发送请求(如访问http://localhost:8080/app/hello)。
- 请求包含 URL、HTTP 方法(GET/POST 等)、请求头和请求体。
- Web 服务器接收并转发请求
- Web 服务器(如 Tomcat、Jetty)接收到请求后,将其转发给 Servlet 容器。
- Servlet 容器查找匹配的 Servlet
- 请求映射器根据 URL 路径查找对应的 Servlet:
- 检查web.xml中的配置。
- 或检查 Servlet 类上的@WebServlet注解。
- 示例:/hello → 映射到HelloServlet。
- 创建 Request/Response 对象
- 容器创建HttpServletRequest和HttpServletResponse对象。
- 从线程池分配一个工作线程处理请求(Servlet 容器通常采用多线程模型)。
- 调用 Servlet 方法
- 容器调用 Servlet 的service()方法,根据 HTTP 方法转发到doGet()或doPost():
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 处理GET请求
}
- 处理业务逻辑
- Servlet 通过request获取参数,调用业务逻辑(如 Service 层):
String name = req.getParameter("name");
User user = userService.getUserByName(name);
- 设置响应数据
- Servlet 通过response设置响应内容、状态码和头信息:
resp.setContentType("application/json");
resp.getWriter().write("{\"status\":\"success\"}");
- 返回响应到客户端
- 容器将response对象转换为 HTTP 响应,通过 Web 服务器返回给客户端。
- 客户端展示响应内容
- 浏览器解析并渲染 HTML、JSON 或其他格式的响应数据。
调试配置
在我们的案例中,Java代码会打包成war,然后被Jetty使用。所以我们没法直接调试Java代码,需要附加到Jetty上。
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.sample;
import java.io.IOException;
import java.util.ArrayList;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.apache.olingo.commons.api.edmx.EdmxReference;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataHttpHandler;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.sample.data.DataProvider;
import org.apache.olingo.server.sample.edmprovider.CarsEdmProvider;
import org.apache.olingo.server.sample.processor.CarsProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CarsServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(CarsServlet.class);
@Override
protected void service(final HttpServletRequest req, final HttpServletResponse resp)
throws ServletException, IOException {
try {
HttpSession session = req.getSession(true);
DataProvider dataProvider = (DataProvider) session.getAttribute(DataProvider.class.getName());
if (dataProvider == null) {
dataProvider = new DataProvider();
session.setAttribute(DataProvider.class.getName(), dataProvider);
LOG.info("Created new data provider.");
}
OData odata = OData.newInstance();
ServiceMetadata edm = odata.createServiceMetadata(new CarsEdmProvider(), new ArrayList<EdmxReference>());
ODataHttpHandler handler = odata.createHandler(edm);
handler.register(new CarsProcessor(dataProvider));
handler.process(req, resp);
} catch (RuntimeException e) {
LOG.error("Server Error", e);
throw new ServletException(e);
}
}
}
具体步骤如下:
- 在Visual Studio Code中通过ctrl+shift+P唤出命令框,输入
Debug:Add Configuration
- 在打开的
.vscode\launch.json
中设置“附加调试”的配置。完整内容如下:
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Debug CarsServlet",
"request": "attach",
"hostName": "localhost",
"port": 5005,
"projectName": "odata-server-sample"
}
]
}
需要注意的是,我们配置的是端口号是5005。这样就是告诉IDE,调试时需要连接到该端口才能获取调试信息以及进行调试操作。
- 进入Servlet所在工程目录,打包代码
cd .\server\
mvn clean package
- 启动带调试端口的jetty
$env:MAVEN_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"; mvn org.eclipse.jetty:jetty-maven-plugin:11.0.20:run
上述命令中关于环境变量的部分解读如下:
$env:MAVEN_OPTS
: PowerShell 中设置环境变量的语法
-agentlib:jdwp
: 启用 Java Debug Wire Protocol (JDWP) 调试代理
transport=dt_socket
: 使用 socket 传输协议进行调试通信
server=y
: JVM 作为调试服务器(等待调试器连接)
suspend=n
: 应用启动时不暂停(如果是 suspend=y 则会等待调试器连接后才继续)
address=5005
: 调试端口设为 5005
执行流程
- 设置环境变量: MAVEN_OPTS 会被 Maven 自动读取并传递给启动的 JVM
- 启动 Jetty: Maven 启动 Jetty 服务器,同时开启调试端口
- 监听连接: JVM 在 5005 端口监听调试器连接
- 服务运行: Jetty 在 9080 端口提供 Web 服务
- 切到Servlet类的文件,点击F5进行调试(下断点,然后打开浏览器访问:[http://localhost:8080/cars.svc/
m
e
t
a
d
a
t
a
]
(
h
t
t
p
:
/
/
l
o
c
a
l
h
o
s
t
:
8080
/
c
a
r
s
.
s
v
c
/
metadata](http://localhost:8080/cars.svc/
metadata](http://localhost:8080/cars.svc/metadata))