嘿,朋友!很高兴你能停下来读这篇文章。我是Agnes,一个在代码世界里摸爬滚打多年的“老司机”。
我知道你为什么会点开这个标题——也许你刚入门Java Web开发,看着满屏的<% %>一脸懵逼;也许你明明照着视频敲了代码,运行时却是满屏乱码,或者页面刷新后用户登录状态就像断了线的风筝;又或许你根本分不清JSP和Servlet到底是什么关系,觉得它们俩是双胞胎,又好像不太像。
别担心,这些问题我都遇到过,也看过太多新手踩坑。今天,我们不搞那些枯燥的教科书式定义,而是像朋友聊天一样,把这层窗户纸捅破。我会带你从最基础的HelloWorld开始,一步步走到Servlet进阶,最后把乱码和Session这两个“拦路虎”彻底解决。
准备好了吗?我们开始吧。
第一章:JSP到底是什么?别被它的名字吓到
首先,让我们把那些晦涩的定义扔一边。JSP的全称是JavaServer Pages。你可以把它理解成:一种让Java代码和HTML混着写的技术。
你想过没有,纯HTML只能做静态页面,刷新还是那样。但如果你想在页面上显示当前时间、显示用户名字、或者根据条件展示不同内容,这时候HTML就歇菜了。于是,JSP诞生了——它允许你在HTML里直接写Java代码,服务器处理完这些代码,再把结果变成干净的HTML发给浏览器。
所以,JSP本质上是什么?它是Servlet的一种简化写法。对,你没听错,JSP最终都会被编译成Servlet。这就是为什么理解Servlet对你来说至关重要。
第二章:你的第一个JSP——HelloWorld
我们来写点实际的东西。假设你已经安装好了Tomcat(如果你还没装,我建议你赶紧去装一个,这是Java Web开发的标配)。
在你的Tomcat的webapps目录下,新建一个文件夹,比如叫myFirstJsp。然后在这个文件夹里新建一个文件,叫index.jsp。
用任何文本编辑器(VS Code、IntelliJ IDEA、甚至记事本都行)打开它,写入以下内容:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>我的第一个JSP</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>这是用JSP写的第一个页面。</p>
<%-- 这是JSP注释,不会被发送到客户端 --%>
<%
// 这是Java代码块
String userName = "Agnes";
int year = 2024;
%>
<p>你好,<%= userName %>!现在时间是:<%= year %></p>
</body>
</html>
保存,然后启动Tomcat,在浏览器访问http://localhost:8080/myFirstJsp/index.jsp。
哇,你看,页面出来了!“Hello, World!”和“你好,Agnes!现在时间是:2024”。
是不是很简单?但这里有几个关键点,我必须提醒你:
1. 页面指令(Page Directive)
注意第一行:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
这行代码告诉服务器:用Java语言、以UTF-8编码来解析这个页面。这一步非常重要,后面我们会看到,如果不写这行,乱码问题就来了。
2. JSP注释 vs HTML注释
- HTML注释:
<!-- 这是注释 -->——浏览器能看到,用户可以右键查看源代码找到它。 - JSP注释:
<%-- 这是注释 --%>——服务器处理时直接忽略,用户完全看不到。安全 sensitive 的信息不要写在HTML注释里。
3. 脚本元素(Script Elements)
在JSP中,有三种主要的Java代码写法:
| 写法 | 语法 | 用途 |
|---|---|---|
| 脚本段 | <% Java代码 %> |
执行逻辑,如定义变量、控制流程 |
| 表达式 | <%= 表达式 %> |
输出结果到页面 |
| 声明 | <%! Java代码 %> |
定义方法或全局变量(少用) |
比如:
<%
int a = 10;
int b = 20;
int sum = a + b;
%>
<p>10 + 20 = <%= sum %></p>
输出结果就是:10 + 20 = 30。
第三章:深入JSP九大内置对象
JSP之所以方便,是因为它提供了一些“内置对象”,你不用new就能直接用。这九个对象是JSP的核心,你必须熟记于心。
1. request——请求对象
这是最常用到的对象之一。当浏览器向服务器发送请求时,所有信息都封装在request里。
<%
// 获取请求参数
String name = request.getParameter("name");
// 获取请求头
String userAgent = request.getHeader("User-Agent");
// 获取请求方法
String method = request.getMethod();
%>
<p>你的名字是:<%= name %></p>
<p>你用的浏览器是:<%= userAgent %></p>
<p>请求方法是:<%= method %></p>
假设你访问的是index.jsp?name=张三,那么页面上就会显示“你的名字是:张三”。
2. response——响应对象
用来向浏览器发送响应。
<%
// 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
// 发送一个重定向
// response.sendRedirect("http://www.baidu.com");
// 设置Cookie
Cookie cookie = new Cookie("username", "张三");
response.addCookie(cookie);
%>
3. session——会话对象
这个对象非常重要,后面我们会专门讲它。简单说,它用来保存用户在一次会话期间的信息。比如登录状态、购物车内容等。
<%
// 获取或创建session
HttpSession sess = request.getSession();
// 设置session属性
sess.setAttribute("username", "张三");
sess.setAttribute("age", 25);
// 获取session属性
String uname = (String) sess.getAttribute("username");
Integer age = (Integer) sess.getAttribute("age");
%>
<p>用户名:<%= uname %></p>
<p>年龄:<%= age %></p>
4. application——应用程序对象
作用域是整個应用程序,所有用户共享。通常用来保存全局配置信息。
<%
application.setAttribute("appName", "我的Web应用");
String appName = (String) application.getAttribute("appName");
%>
<p>应用名称:<%= appName %></p>
5. out——输出对象
用来向客户端输出内容,类似于System.out.println,但它是输出到网页。
<%
out.println("<b>这是加粗的文字</b>");
out.println("当前时间:" + new java.util.Date());
%>
6. config——配置对象
用来获取Servlet的配置信息,比如初始化参数。
<%
String initParam = config.getInitParameter("dbName");
%>
<p>数据库名:<%= initParam %></p>
7. page——页面对象
指向当前JSP页面本身,类似于Java中的this。
8. pageContext——页面上下文对象
这是一个万能的对象,可以通过它获取其他八个内置对象。
<%
// 通过pageContext获取request
HttpServletRequest req = (HttpServletRequest) pageContext.getRequest();
// 设置属性到不同作用域
pageContext.setAttribute("name", "李四", PageContext.REQUEST_SCOPE);
pageContext.setAttribute("name", "王五", PageContext.SESSION_SCOPE);
pageContext.setAttribute("name", "赵六", PageContext.APPLICATION_SCOPE);
%>
9. exception——异常对象
只有在isErrorPage="true"的页面中才能使用。
<%@ page isErrorPage="true" %>
<!DOCTYPE html>
<html>
<body>
<h1>出错了!</h1>
<p>错误信息:<%= exception.getMessage() %></p>
</body>
</html>
第四章:JSP生命周期——它到底经历了什么?
很多人问:JSP和Servlet有什么区别?它们的生命周期是怎样的?
其实,JSP被浏览器第一次访问时,服务器会把它编译成Servlet。整个过程分为三个阶段:
1. 翻译阶段(Translation)
服务器把.jsp文件翻译成.java文件(Servlet源码)。你可以在Tomcat的work目录下看到这个生成的Java文件。
2. 编译阶段(Compilation)
把.java文件编译成.class文件(Servlet字节码)。
3. 执行阶段(Execution)
实例化Servlet,调用jspInit()、_jspService()、jspDestroy()方法。
这意味着:
- 第一次访问慢:因为要翻译和编译。
- 后续访问快:因为已经编译好了,直接执行。
所以,如果你修改了JSP文件,下次访问时服务器会重新编译。这也是为什么开发时经常遇到“改了代码不生效”的问题——有时候需要重启Tomcat或者清空work目录。
第五章:从JSP到Servlet——为什么要进阶?
虽然JSP写起来很方便,但它不适合做复杂的业务逻辑。为什么?
- 代码混乱:HTML和Java代码混在一起,后期维护噩梦。
- 性能问题:每次访问都要编译(虽然只是一次)。
- 分工明确:现代Web开发讲究MVC模式,JSP只负责显示,Servlet负责控制。
所以,进阶之路就是:用Servlet处理逻辑,用JSP负责展示。
第六章:Servlet详解——真正的后端主角
1. 什么是Servlet?
Servlet是一个Java类,它运行在服务器上,用来处理客户端请求并返回响应。它是Java Web开发的核心。
2. 第一个Servlet
创建一个普通的Java类,继承HttpServlet:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置响应类型和编码
response.setContentType("text/html;charset=UTF-8");
// 获取输出流
PrintWriter out = response.getWriter();
// 输出内容
out.println("<html>");
out.println("<head><title>Hello Servlet</title></head>");
out.println("<body>");
out.println("<h1>Hello from Servlet!</h1>");
out.println("</body>");
out.println("</html>");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
注意:你需要在web.xml中配置这个Servlet,或者使用注解:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
// ...
}
然后访问http://localhost:8080/yourApp/hello就能看到结果。
3. Servlet的生命周期
Servlet的生命周期由容器(Tomcat)管理,分为四个阶段:
- 加载和实例化:服务器启动或第一次请求时创建Servlet实例。
- 初始化:调用
init()方法,只执行一次。 - 服务:每次请求调用
service()方法,它会根据请求类型(GET/POST)分发到doGet()或doPost()。 - 销毁:服务器关闭时调用
destroy()方法,释放资源。
public class MyServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("Servlet初始化");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("处理GET请求");
// 业务逻辑...
}
@Override
public void destroy() {
System.out.println("Servlet销毁");
}
}
4. Servlet与JSP的关系
- JSP是Servlet的简化版:JSP最终会被编译成Servlet。
- 分工不同:Servlet适合做控制逻辑,JSP适合做页面展示。
- 最佳实践:用Servlet接收请求、处理业务、设置数据到request/session,然后用JSP展示结果。
// Servlet中设置数据
request.setAttribute("username", "张三");
request.getRequestDispatcher("/index.jsp").forward(request, response);
<!-- JSP中获取数据 -->
<p>用户名:<%= request.getAttribute("username") %></p>
第七章:乱码问题——那些年我们踩过的坑
乱码是Java Web开发中最常见的问题之一。无论你是初学者还是老手,几乎都遇到过。别怕,今天我把所有可能的原因和解决方案一次性讲清楚。
1. 为什么会出现乱码?
乱码的根本原因是编码不一致。比如:
- 你的文件是UTF-8编码保存的。
- 但服务器默认用ISO-8859-1解码。
- 或者浏览器用GBK显示,而数据是UTF-8编码的。
结果就是:同样一个字符“中”,在服务器里是E4B8AD(UTF-8),在浏览器里被当成GBK解析,就变成了乱码。
2. 解决方案一:在JSP页面指定编码
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试页面</title>
</head>
<body>
<h1>你好,世界!</h1>
</body>
</html>
关键点:
pageEncoding="UTF-8":告诉服务器用UTF-8解析JSP文件。contentType="text/html; charset=UTF-8":告诉浏览器用UTF-8显示。<meta charset="UTF-8">:HTML5的编码声明,作为备选。
3. 解决方案二:在Servlet中设置编码
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置请求编码(处理GET请求参数)
request.setCharacterEncoding("UTF-8");
// 设置响应编码(处理输出到浏览器的内容)
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 获取参数
String name = request.getParameter("name");
// 输出
response.getWriter().println("你好," + name);
}
}
注意:request.setCharacterEncoding()只对POST请求有效!GET请求的参数编码问题需要另外处理。
4. 解决方案三:处理GET请求的乱码
GET请求的参数在URL中,Tomcat默认用ISO-8859-1解码。有两种方法:
方法一:修改Tomcat配置(推荐)
打开Tomcat的conf/server.xml,找到Connector配置,添加URIEncoding="UTF-8":
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8" />
方法二:代码转换(兼容方案)
String name = request.getParameter("name");
// 先按ISO-8859-1解码,再按UTF-8编码
name = new String(name.getBytes("ISO-8859-1"), "UTF-8");
5. 解决方案四:数据库乱码
如果数据从数据库读出来是乱码,检查以下几点:
- 数据库编码:创建数据库时指定UTF-8。
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- 表编码:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- JDBC连接URL:
String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8";
- Java代码中的转换:
// 如果还是乱码,尝试手动转换
String name = new String(rs.getString("name").getBytes("ISO-8859-1"), "UTF-8");
6. 解决方案五:IDE文件编码
确保你的IDE保存文件时用的是UTF-8:
- IntelliJ IDEA:Settings → Editor → File Encodings → Global Encoding/GPIO Project Encoding 都设为UTF-8。
- Eclipse:Window → Preferences → General → Workspace → Text file encoding 设为UTF-8。
- VS Code:右下角状态栏点击编码,选择UTF-8。
第八章:Session丢失——用户登录状态为何“断线”?
Session是Web开发中非常重要的概念,但也是最容易出问题的地方之一。很多开发者问:“为什么用户登录后,刷新页面就掉线了?”
1. Session是什么?
Session是服务器端用来跟踪用户会话的机制。当用户
