URL 与 URI
| 概念 | 全称 | 示例 | 你该怎么理解 |
|---|---|---|---|
| URL | Uniform Resource Locator | http://localhost:8080/demo/hello | 完整访问地址 |
| URI | Uniform Resource Identifier | /demo/hello | 资源标识路径 |
Request 和 Response,本质上就是把这报文的四块内容映射到 Java API 上。
HTTP 报文与 Request / Response 的对应关系

请求报文示例
POST /admin/auth/login HTTP/1.1
Host: 39.101.189.16:8083
Connection: keep-alive
Content-Length: 45
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Type: application/json;charset=UTF-8
{"username":"admin123","password":"admin123"}
可以拆成四部分:
| 部分 | 示例 | 对应关注点 |
|---|---|---|
| 请求行 | POST /admin/auth/login HTTP/1.1 | 方法、路径、协议 |
| 请求头 | Host、Content-Type、User-Agent | 元信息 |
| 空行 | 分隔头和体 | 没有业务含义 |
| 请求体 | JSON / 表单数据 / 文件数据 | 真正提交的数据 |
响应报文示例
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Set-Cookie: JSESSIONID=24287278-5ebb-407d-a3f7-56b74782c4c7; Path=/; HttpOnly
Content-Length: 200
{"errno":0,"data":{"nickName":"admin123"},"errmsg":"成功"}
同样可以拆成四部分:
| 部分 | 示例 | 对应关注点 |
|---|---|---|
| 响应行 | HTTP/1.1 200 OK | 协议、状态码 |
| 响应头 | Content-Type、Set-Cookie | 浏览器如何解释响应 |
| 空行 | 分隔头和体 | 没有业务含义 |
| 响应体 | HTML / JSON / 图片 / 文件字节流 | 真正返回给客户端的内容 |
可以把它们理解成:
| 对象 | 本质 | 作用 |
|---|---|---|
HttpServletRequest | 服务器对请求报文的封装 | 读取客户端传来的信息 |
HttpServletResponse | 服务器对响应报文的封装 | 组织并返回响应内容 |
在 Servlet 中最常见的入口就是:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// req 负责拿请求信息
// resp 负责写响应结果
}
Request


请求行信息获取
请求行最常见的是:
POST /demo/login?username=zhangsan HTTP/1.1
对应 API:
| 信息 | 方法 | 说明 |
|---|---|---|
| 请求方法 | getMethod() | GET / POST / PUT / DELETE |
| 完整 URL | getRequestURL() | 包含协议、主机、端口、路径 |
| URI | getRequestURI() | 只保留资源路径 |
| 上下文路径 | getContextPath() | 应用根路径 |
| 查询字符串 | getQueryString() | ? 后面的内容 |
| 协议 | getProtocol() | 例如 HTTP/1.1 |
示例:
@WebServlet("/line")
public class LineServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println(req.getMethod());
System.out.println(req.getRequestURL());
System.out.println(req.getRequestURI());
System.out.println(req.getContextPath());
System.out.println(req.getQueryString());
System.out.println(req.getProtocol());
}
}
请求头信息获取
请求头常见内容:
Host: localhost:8080
User-Agent: Mozilla/5.0
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
最常用的方法有两个:
| 方法 | 说明 |
|---|---|
getHeader(String name) | 获取指定请求头 |
getHeaderNames() | 获取全部请求头名称 |
示例:
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
System.out.println(name + ": " + req.getHeader(name));
}
String host = req.getHeader("Host");
String userAgent = req.getHeader("User-Agent");
请求头名称大小写不敏感,所以 Host 和 host 都能取到值。
请求体读取
getReader() 和 getInputStream()
当你处理 JSON 或二进制数据时,参数不一定适合直接用 getParameter()。
这时要读请求体:
| 方法 | 适合场景 |
|---|---|
getReader() | 文本数据,如 JSON、XML |
getInputStream() | 二进制数据 |
文本读取示例:
BufferedReader reader = req.getReader();
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
System.out.println(sb.toString());
字节流示例:
ServletInputStream inputStream = req.getInputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
System.out.write(buffer, 0, len);
}
例如当前端按 raw + JSON 提交请求体时,请求数据就不再适合优先用 getParameter(),而更适合用 getReader() 去读整段文本:

注意:同一个请求里,字符流和字节流不能混用。
客户端与服务器信息
除了请求行和请求头,Request 还能让你知道“这次请求是谁发来的、发到哪里”。
| 信息 | 方法 | 返回值 | 说明 |
|---|---|---|---|
| 服务器IP | getLocalAddr() | String | 接收请求的服务器IP |
| 服务器端口 | getLocalPort() | int | 接收请求的服务器端口 |
| 客户端IP | getRemoteAddr() | String | 发送请求的客户端IP |
| 客户端端口 | getRemotePort() | int | 发送请求的客户端端口 |
示例:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String localAddr = req.getLocalAddr();
int localPort = req.getLocalPort();
String remoteAddr = req.getRemoteAddr();
int remotePort = req.getRemotePort();
System.out.println("请求来源: " + remoteAddr + ":" + remotePort);
System.out.println("目标地址: " + localAddr + ":" + localPort);
}
请求转发(了解)
请求转发是服务器内部把请求交给另一个资源继续处理:
req.getRequestDispatcher("/target.jsp").forward(req, resp);
特点:
- 浏览器地址栏不变
- 还是同一次请求
- 可以共享同一个 Request 中的数据
例如:
req.setAttribute("user", user);
req.getRequestDispatcher("/success.jsp").forward(req, resp);
目标资源里仍然可以取到 user。
Request参数与文件上传

请求参数获取
请求参数最常见的来源有两个:
- GET:出现在 URL 查询字符串里
- POST:出现在请求体里
对应最常用的四个方法:
| 方法 | 返回值 | 用途 |
|---|---|---|
getParameter(String) | String | 取单个参数 |
getParameterValues(String) | String[] | 取同名多值参数 |
getParameterNames() | Enumeration<String> | 枚举全部参数名 |
getParameterMap() | Map<String, String[]> | 拿到全部参数映射 |
示例:
String username = req.getParameter("username");
String[] hobbies = req.getParameterValues("hobby");
Map<String, String[]> paramMap = req.getParameterMap();
结合下面这张图理解它们各自“拿的是哪一层数据”:

多值参数为什么要用 getParameterValues()
例如多选框:
hobby=sing&hobby=dance
在底层会被封装成:
{
"hobby": ["sing", "dance"]
}
所以:
- 单值参数可以用
getParameter() - 多值参数必须考虑
getParameterValues()
POST 中文乱码问题
解决方式:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String username = req.getParameter("username");
}
关键点只有一句话:setCharacterEncoding("UTF-8") 必须写在任何 getParameter() 之前。
参数封装为对象
如果参数一多,手动 setXxx() 会很啰嗦:
User user = new User();
user.setUsername(req.getParameter("username"));
user.setPassword(req.getParameter("password"));
更常见的做法是直接使用 BeanUtils 这类工具类。
如果是 Maven 项目,先引入依赖:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
如果不是 Maven 工程,也要先把对应的 commons-beanutils 相关 jar 包导入项目。
示例:
import org.apache.commons.beanutils.BeanUtils;
User user = new User();
BeanUtils.populate(user, req.getParameterMap());
这样就能把参数名和 JavaBean 属性名对应起来,自动完成封装。
注意两点:
- 表单参数名要和对象属性名保持一致
BeanUtils.populate()底层依赖反射,优先掌握工具类的使用方式即可
文件上传处理
文件上传和普通表单的区别在于:表单必须声明 multipart/form-data。
前端示例:
<form action="/demo/upload" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"><br>
头像:<input type="file" name="avatar"><br>
<input type="submit" value="上传">
</form>
浏览器侧看到的就是一个普通表单加文件选择框,但一旦有文件字段,提交方式就必须切到 multipart/form-data:

Servlet 端要点:
- 使用
@MultipartConfig - 用
getPart()获取上传文件
@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String username = req.getParameter("username");
Part avatarPart = req.getPart("avatar");
String fileName = avatarPart.getSubmittedFileName();
long size = avatarPart.getSize();
String contentType = avatarPart.getContentType();
String savePath = getServletContext().getRealPath("/uploads");
avatarPart.write(savePath + File.separator + fileName);
}
}
常用 Part 方法
| 方法 | 作用 |
|---|---|
getInputStream() | 读取文件内容 |
getSubmittedFileName() | 获取原始文件名 |
getContentType() | 获取 MIME 类型 |
getSize() | 获取文件大小 |
write(String path) | 保存文件 |
如果你是用接口工具调试文件上传,请重点观察 form-data 和文件字段类型,而不是误选成 raw 或 x-www-form-urlencoded:

进一步往底层看,multipart/form-data 请求体其实会把普通字段和文件字段分段封装。
Response

响应行:状态码设置
Response 最基础的能力之一,就是设置状态码:
resp.setStatus(200);
resp.setStatus(302);
响应头:告诉浏览器如何处理结果
通用写法:
resp.setHeader("key", "value");
响应体输出:字符流 vs 字节流
最常用的两个方法:
| 方法 | 适合场景 |
|---|---|
getWriter() | 文本、HTML、JSON |
getOutputStream() | 图片、文件、二进制数据 |
文本输出:
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.write("<h1>Hello</h1>");
writer.write("<p>你好</p>");
字节输出:
resp.setContentType("image/jpeg");
InputStream is = getServletContext().getResourceAsStream("/images/photo.jpg");
ServletOutputStream os = resp.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
注意:同一个响应中,字符流和字节流也不能混用。
特殊响应头:Content-Type
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().write("{\"code\":200}");
常见取值:
| 内容 | Content-Type |
|---|---|
| HTML | text/html;charset=UTF-8 |
| 纯文本 | text/plain;charset=UTF-8 |
| JSON | application/json;charset=UTF-8 |
| JPEG 图片 | image/jpeg |
| PNG 图片 | image/png |
application/pdf |
它既决定浏览器如何解释响应内容,也常常顺手解决中文乱码问题。
Content-Disposition:文件下载
如果你希望浏览器把内容当“附件下载”,常用写法是:
resp.setHeader("Content-Disposition", "attachment;filename=report.pdf");
resp.setContentType("application/pdf");
再配合字节流把文件写出去即可。
Location 与重定向
最底层的重定向可以这么写:
resp.setStatus(302);
resp.setHeader("Location", "https://www.example.com");
更常用的是:
resp.sendRedirect("https://www.example.com");
Refresh:定时刷新或定时跳转
resp.setHeader("Refresh", "1");
resp.setHeader("Refresh", "3;url=/login.html");
这些响应头已经是前端工作,知道它是响应头控制即可。
转发 vs 重定向
| 特性 | 转发(forward) | 重定向(redirect) |
|---|---|---|
| URL变化 | 不变 | 改变 |
| 请求次数 | 1次 | 2次 |
| 数据共享 | 共享Request | 不共享 |
| 跳转范围 | 服务器内部 | 任意URL |
| 适用场景 | MVC内部跳转 | 登录后跳转、跨域跳转 |
反射与通用分发思路
一个 Servlet 里如果要分发多个业务方法,手写 if...else 很笨重。反射恰好能帮助我们提升“通用分发”的能力。
获得 Class 对象的三种方式
Class<UserServiceImpl> c1 = UserServiceImpl.class;
UserServiceImpl service = new UserServiceImpl();
Class<? extends UserServiceImpl> c2 = service.getClass();
Class<?> c3 = Class.forName("com.cskaoyan.service.UserServiceImpl");
在“通用性开发”里,最常用的是:
Class.forName(...)
因为它适合从配置或字符串动态加载类。
通过反射调用字段和方法
字段示例:
Field field = clazz.getDeclaredField("username");
field.setAccessible(true);
field.set(instance, "zhangsan");
方法示例:
Method method = clazz.getDeclaredMethod("login", String.class);
method.setAccessible(true);
Object result = method.invoke(instance, "admin");
典型应用:通用请求分发器
比如请求:
/user/login
/user/register
/user/delete
可以取 URI 最后一段,再通过反射去调用同名方法:
String uri = req.getRequestURI();
String methodName = uri.substring(uri.lastIndexOf("/") + 1);
Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
这就是很多“简化版 MVC 分发器”的入门思路。
总结
把本章压缩成一条链路,就是:
浏览器发起 HTTP 请求
->
Tomcat 封装为 HttpServletRequest / HttpServletResponse
->
Servlet 从 Request 中读取方法、头、参数、请求体
->
业务逻辑处理
->
Servlet 通过 Response 设置状态码、响应头、响应体
->
浏览器按响应头解释并展示结果
核心:
- Request 对应请求报文,Response 对应响应报文
- Request 偏输入,Response 偏输出
- 参数区、请求体、文件上传是三种不同输入来源
Content-Type、Location、Content-Disposition是高频响应头BeanUtils能简化参数封装,反射让通用分发更有扩展性