封装服务器要发送给客户端的所有信息,包括 HTTP 状态码、响应头、响应体(内容)等。
在 Java Web 开发中,主要有两种主流的 API 来处理 response:
- Servlet API (最传统、最基础)
- Spring MVC (目前最流行、最高效)
下面我们分别对这两种 API 进行详细说明。
Servlet API 中的 response
在标准的 Java Web 开发中,response 是一个 javax.servlet.http.HttpServletResponse 对象,它由 Servlet 容器(如 Tomcat)创建,并通过 service 方法传递给我们的 Servlet。
核心功能
HttpServletResponse 提供了一系列方法来构建响应。
常用方法详解
1 设置响应状态码
状态码是一个三位数的数字,用于表示响应的结果,最常见的是 200(成功)、404(未找到)、500(服务器内部错误)等。
// 设置状态码为 200 (OK),这是默认值,通常不需要显式设置 response.setStatus(HttpServletResponse.SC_OK); // 或者 response.setStatus(200); // 设置状态码为 404 (Not Found) response.setStatus(HttpServletResponse.SC_NOT_FOUND); // 或者 response.setStatus(404); // 设置状态码为 302 (Found),用于重定向 response.setStatus(HttpServletResponse.SC_FOUND); // 或者 response.setStatus(302);
2 设置响应头
响应头是服务器发送给客户端的附加信息,如内容类型、内容长度、缓存控制等。
// text/html 表示 HTML 文本
// charset=UTF-8 指定字符编码为 UTF-8,防止中文乱码
response.setContentType("text/html;charset=UTF-8");
// 设置自定义响应头
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // 禁止缓存
response.setHeader("Custom-Header", "This is a custom value");
// 更便捷的方法设置常用头
response.setCharacterEncoding("UTF-8"); // 设置字符编码
3 获取输出流并写入响应体
响应体就是客户端实际看到或接收到的内容,我们需要通过获取输出流来写入这些内容。
有两种输出流:
getOutputStream(): 用于输出二进制数据(如图片、PDF、视频等)。getWriter(): 用于输出文本数据(如 HTML、JSON、XML 等)。
注意: 在同一个响应中,不能同时调用 getOutputStream() 和 getWriter(),否则会抛出 IllegalStateException。
示例:输出简单的 HTML
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应头
response.setContentType("text/html;charset=UTF-8");
// 2. 获取 PrintWriter 输出流
PrintWriter out = response.getWriter();
// 3. 写入 HTML 内容
out.println("<html>");
out.println("<head><title>Hello Servlet</title></head>");
out.println("<body>");
out.println("<h1>你好,世界!</h1>");
out.println("<p>这是通过 Servlet 输出的响应。</p>");
out.println("</body>");
out.println("</html>");
// 4. 关闭流 (虽然 Tomcat 会自动回收,但显式关闭是好习惯)
out.close();
}
示例:输出 JSON
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应头为 JSON 格式
response.setContentType("application/json;charset=UTF-8");
// 2. 获取 PrintWriter 输出流
PrintWriter out = response.getWriter();
// 3. 构建一个简单的 JSON 字符串
String jsonResponse = "{\"name\": \"张三\", \"age\": 30, \"city\": \"北京\"}";
// 4. 写入 JSON 数据
out.print(jsonResponse);
// 5. 关闭流
out.close();
}
4 重定向
重定向是服务器告诉客户端去访问另一个 URL,客户端会自动发起一个新的请求到新的 URL。
实现方式:
// 方式一:设置状态码和响应头
response.setStatus(HttpServletResponse.SC_FOUND); // 302
response.setHeader("Location", "https://www.example.com/new-location");
// 方式二:更便捷的方法
response.sendRedirect("https://www.example.com/new-location");
// 或者重定向到当前项目内的其他路径
response.sendRedirect("login.jsp");
重定向 vs. 转发:
- 重定向 (
sendRedirect): 客户端行为,会产生两次请求,浏览器的地址栏会改变。 - 转发 (
RequestDispatcher.forward): 服务器行为,只产生一次请求,浏览器地址栏不变。
Spring MVC 中的 response
在 Spring MVC 框架中,我们通常不直接操作原始的 HttpServletResponse 对象,框架为我们提供了更现代、更灵活的方式来处理响应,这使得代码更简洁、更易于维护。
Spring MVC 的核心思想是:控制器方法返回什么,框架就把它渲染成什么。
核心处理方式
1 返回 String(视图名)
这是最常见的方式,返回的字符串会被 Spring 的视图解析器解析成一个具体的视图(如 JSP、Thymeleaf 模板等)。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyController {
@GetMapping("/hello")
public String sayHello() {
// 返回 "hello" 字符串
// 假设视图解析器配置为前缀 /WEB-INF/views/ 和后缀 .jsp
// Spring 会尝试去 /WEB-INF/views/hello.jsp 寻找并渲染这个页面
return "hello";
}
}
2 返回 ModelAndView 对象
这是最传统、最灵活的 Spring MVC 方式。ModelAndView 对象可以同时携带数据和视图信息。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class ModelAndViewController {
@GetMapping("/user")
public ModelAndView getUser() {
ModelAndView mav = new ModelAndView();
// 1. 设置视图名
mav.setViewName("userProfile"); // 对应 userProfile.jsp
// 2. 向模型中添加数据,这些数据可以在视图中访问
mav.addObject("username", "李四");
mav.addObject("email", "lisi@example.com");
return mav;
}
}
在 userProfile.jsp 中,你可以通过 ${username} 和 ${email} 来访问这些数据。
3 返回对象或集合(如 @ResponseBody)
当我们想直接返回数据(如 JSON 或 XML)而不是渲染一个页面时,可以使用 @ResponseBody 注解,Spring 会自动将返回的对象序列化成 JSON 格式,并写入响应体。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class JsonController {
@GetMapping("/api/user")
@ResponseBody // 告诉 Spring,这个方法的返回值直接写入响应体,不进行视图解析
public User getUserAsJson() {
User user = new User();
user.setName("王五");
user.setAge(25);
return user;
}
}
当访问 /api/user 时,浏览器会收到类似 {"name":"王五","age":25} 的 JSON 响应。
4 使用 ResponseEntity
ResponseEntity 是 Spring 提供的一个更强大的类,它允许你完全控制响应的状态码、头信息和体,这相当于对原始 HttpServletResponse 的高级封装。
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // @RestController = @Controller + @ResponseBody
public class ResponseEntityController {
@GetMapping("/api/custom-response")
public ResponseEntity<String> getCustomResponse() {
// 1. 设置响应体
String body = "这是一个自定义的响应";
// 2. 设置响应头
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
headers.add("Custom-Header", "MyValue");
// 3. 创建 ResponseEntity,指定状态码、头信息和体
// 返回 200 OK 状态码
return new ResponseEntity<>(body, headers, HttpStatus.OK);
}
@GetMapping("/api/not-found")
public ResponseEntity<String> getNotFoundResponse() {
return new ResponseEntity<>("资源未找到", HttpStatus.NOT_FOUND);
}
}
5 在方法参数中注入 HttpServletResponse
如果你确实需要直接操作原始的 response 对象(写入文件流),你也可以将其作为方法参数注入。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
public class DirectResponseController {
@GetMapping("/download")
@ResponseBody
public void downloadFile(HttpServletResponse response) throws IOException {
// 设置响应头,告诉浏览器这是一个文件下载
response.setContentType("application/octet-stream");
String headerKey = "Content-Disposition";
String headerValue = "attachment; filename=\"example.txt\"";
response.setHeader(headerKey, headerValue);
// 写入文件内容
response.getOutputStream().write("这是一个下载文件的内容".getBytes());
}
}
总结与对比
| 特性 | Servlet API (HttpServletResponse) |
Spring MVC |
|---|---|---|
| 核心对象 | javax.servlet.http.HttpServletResponse |
无直接对象,通过返回值或 ModelAndView、ResponseEntity 等控制 |
| 操作方式 | 命令式:response.setStatus(), response.setHeader(), response.getWriter().write() |
声明式:通过方法的返回值、@ResponseBody、ResponseEntity 来定义响应 |
| 响应体写入 | 直接获取 OutputStream 或 Writer 并写入 |
返回 POJO,框架自动序列化为 JSON/XML;或返回视图名,框架渲染页面 |
| 状态码/头信息 | 手动调用 setStatus() 和 setHeader() |
在 ResponseEntity 中集中设置;或通过 @ResponseStatus 注解 |
| 重定向 | response.sendRedirect() |
return "redirect:/new-url" (视图前缀) |
| 适用场景 | 学习 Servlet 原理、简单项目、需要直接操作底层流的场景 | 几乎所有现代 Java Web 项目,特别是 RESTful API 和复杂应用 |
| 代码风格 | 较为繁琐,需要手动管理每个细节 | 更简洁、更面向对象,符合框架设计理念 |
- Servlet API 是底层的、手动的构建响应的方式,你需要告诉服务器“一步一步该做什么”。
- Spring MVC 是高层的、声明的构建响应的方式,你只需要告诉服务器“你想要什么结果”,框架会帮你处理好所有细节。
在现代开发中,除非你正在学习 Servlet 原理或处理一些非常特殊的底层需求,否则强烈推荐使用 Spring MVC 的方式,因为它能极大地提高开发效率和代码质量。
