Servlet_JSP详解

O泡李华 12

Servlet + JSP 详解

本章位置:第二阶段 Java 核心框架
前置知识:Servlet、HTML/CSS、JavaBean、HTTP 基础
本章内容:JSP 基础、Servlet 与 JSP 配合、传统 MVC
下一篇:Servlet 交互 / 作用域 / EL / JSTL
学习目标:理解 JSP 是什么、JSP 为什么本质上还是 Servlet、JSP 的基本语法与内置对象、Servlet 如何向 JSP 传递数据,以及传统 Java Web 中 Servlet + JSP 的 MVC 分工。


一、为什么学习 JSP

上一章我们已经会:

浏览器
↓
Tomcat
↓
Servlet
↓
Service
↓
Mapper
↓
数据库

也已经会在 Servlet 中:

response
        .getWriter()
        .write(
                "<h1>Hello</h1>"
        );

但是如果页面越来越复杂:

<table>
    ...
</table>

全部写成:

writer.write(...)

会非常痛苦。


二、Servlet 拼 HTML 的问题

例如:

response
        .getWriter()
        .write(
                "<html>"
                + "<body>"
                + "<h1>学生列表</h1>"
                + "</body>"
                + "</html>"
        );

问题:

HTML 和 Java 混在一起

代码难看

前端页面难维护

标签容易拼错

不方便写 CSS

不方便设计页面

于是出现:

JSP

三、JSP 是什么

JSP:

JavaServer Pages

中文可以理解:

Java 服务器页面

它允许我们在:

HTML 页面

中嵌入一些:

Java 相关表达式和标签

四、JSP 的核心作用

传统 Java Web 中,

JSP 主要负责:

页面展示

Servlet 主要负责:

请求处理
业务调度

所以:

Servlet
Controller

JSP
View

五、Servlet + JSP 基本流程

例如学生列表:

浏览器
↓
/student/list
↓
StudentListServlet
↓
调用 Service
↓
得到 List<Student>
↓
request.setAttribute
↓
forward
↓
student-list.jsp
↓
生成 HTML
↓
浏览器

六、JSP 文件放在哪里

Maven Web 项目常见:

src/main/webapp

例如:

src/main/webapp
├─ index.jsp
├─ login.jsp
└─ WEB-INF
   └─ views
      └─ student-list.jsp

七、为什么 JSP 常放 WEB-INF

如果放:

WEB-INF/views

浏览器不能直接访问。

例如:

/WEB-INF/views/student-list.jsp

不能直接在浏览器地址栏打开。

只能:

服务器内部转发

八、为什么这样更合理

如果用户可以直接访问 JSP:

可能绕过 Servlet

例如:

没有经过权限判断

没有加载数据

没有执行业务逻辑

所以推荐:

浏览器先访问 Servlet
↓
Servlet 再 forward JSP

九、第一个 JSP

创建:

hello.jsp

内容:

<%@ page
        contentType="text/html;charset=UTF-8"
        pageEncoding="UTF-8"
%>

<!DOCTYPE html>

<html>

<head>

    <meta charset="UTF-8">

    <title>
        Hello JSP
    </title>

</head>

<body>

    <h1>
        Hello JSP
    </h1>

</body>

</html>

十、JSP 本质是什么

这是最重要的知识点之一:

JSP 最终会被 Tomcat 翻译成 Servlet。

也就是说:

JSP
不是完全独立的新运行机制

而是:

JSP
↓
Tomcat 翻译
↓
Java Servlet 源码
↓
编译成 class
↓
运行

十一、JSP 请求执行流程

第一次访问:

浏览器请求 JSP
↓
Tomcat 找到 JSP
↓
JSP 翻译成 Java Servlet
↓
编译成 .class
↓
创建 Servlet 对象
↓
执行
↓
返回 HTML

后续访问:

如果 JSP 没修改
一般直接使用编译后的 Servlet

十二、为什么 JSP 第一次可能稍慢

因为第一次访问可能需要:

翻译 JSP

编译 Java

加载 class

创建 Servlet

所以第一次可能比后续访问慢。


十三、JSP 翻译后的类

Tomcat 会把 JSP 翻译成一个类似:

public final class xxx_jsp
        extends HttpJspBase {
}

的类。

本质仍然属于:

Servlet 体系

十四、JSP 生命周期

JSP 生命周期和 Servlet 很像。

大概:

JSP 翻译
↓
编译
↓
加载
↓
实例化
↓
jspInit()
↓
_jspService()
↓
jspDestroy()

十五、jspInit()

类似:

Servlet.init()

通常:

只执行一次

十六、_jspService()

每次 JSP 请求:

主要执行这个方法

而且:

通常不需要也不能自己重写

JSP 页面内容最终会被放入:

_jspService()

中。


十七、jspDestroy()

类似:

Servlet.destroy()

JSP 销毁时调用。


十八、JSP 三类传统脚本元素

传统 JSP 中有:

声明

脚本片段

表达式

分别:

<%! ... %>

<% ... %>

<%= ... %>

十九、JSP 声明

语法:

<%!
    int count = 0;
%>

它会被翻译成:

JSP Servlet 的成员变量或成员方法

二十、JSP 声明的线程安全风险

例如:

<%!
    int count = 0;
%>

多个请求可能共享:

同一个 JSP Servlet 对象

所以:

成员变量可能有线程安全问题

因此现代开发:

尽量不要在 JSP 中写成员变量

二十一、JSP 脚本片段

<%
    String name =
            "张三";

    out.println(
            name
    );
%>

这段代码会放进:

_jspService()

内部。


二十二、JSP 表达式

<%= "Hello" %>

等价于:

把结果输出到页面

例如:

<%= request.getAttribute("name") %>

二十三、传统脚本为什么不推荐

如果大量使用:

<%
    ...
%>

页面会变成:

HTML
+
Java
+
业务逻辑

非常混乱。

后来会使用:

EL

JSTL

减少 Java 脚本。


二十四、学习 JSP 脚本的目的

不是让你以后大量使用,

而是为了理解:

JSP 本质

JSP 如何执行 Java

旧项目代码

现代 JSP 推荐:

Servlet + EL + JSTL

二十五、JSP 指令

JSP 常见指令:

page

include

taglib

语法:

<%@ ... %>

二十六、page 指令

例如:

<%@ page
        contentType="text/html;charset=UTF-8"
        pageEncoding="UTF-8"
%>

常见属性:

contentType

pageEncoding

import

errorPage

isErrorPage

session

二十七、contentType

例如:

contentType="text/html;charset=UTF-8"

告诉浏览器:

响应内容是 HTML
字符编码 UTF-8

二十八、pageEncoding

pageEncoding="UTF-8"

表示:

JSP 文件自身编码

二十九、import

例如:

<%@ page
        import="java.util.List"
%>

类似 Java:

import java.util.List;

三十、为什么 JSP 中尽量少 import 业务类

如果页面里大量:

<%@ page import="..." %>

然后写大量 Java,

说明:

MVC 分层可能已经混乱

更推荐:

Servlet 准备数据
JSP 只展示

三十一、include 指令

<%@ include
        file="/common/header.jsp"
%>

属于:

静态包含

三十二、静态包含

可以理解:

JSP 翻译阶段
把多个文件合成一个

常用于:

公共头部

公共导航

公共底部

三十三、动态包含

还可以:

<jsp:include
        page="/common/header.jsp"
/>

这是:

运行时包含

三十四、静态 include 和动态 include

简单理解:

<%@ include %>
翻译阶段合并

<jsp:include>
运行阶段包含

三十五、JSP 注释

JSP 注释:

<%--
    JSP 注释
--%>

这类注释:

服务器处理时就会去掉

浏览器看不到。


三十六、HTML 注释

<!-- HTML 注释 -->

服务器通常仍会输出到浏览器。

用户查看页面源码:

可能看得到

三十七、Java 注释

JSP 脚本中:

<%
    // Java 注释
%>

也是 Java 层面的注释。


三十八、JSP 九大内置对象

传统 JSP 中有九大内置对象:

request

response

session

application

out

pageContext

config

page

exception

三十九、request

类型:

HttpServletRequest

就是:

当前请求对象

例如:

<%= request.getParameter("name") %>

四十、response

类型:

HttpServletResponse

就是:

当前响应对象

例如:

<%
    response.setStatus(200);
%>

但一般不建议 JSP 做复杂响应控制。


四十一、session

类型:

HttpSession

表示:

当前用户会话

后面作用域章节详细讲。


四十二、application

本质:

ServletContext

表示:

整个 Web 应用

四十三、out

类型:

JspWriter

用于:

向响应中输出内容

例如:

<%
    out.println(
            "Hello"
    );
%>

四十四、pageContext

类型:

PageContext

是 JSP 中非常重要的对象。

可以:

访问多个作用域

获取 request

获取 response

获取 session

获取 application

转发

包含

四十五、config

类型:

ServletConfig

表示:

JSP 对应 Servlet 的配置

四十六、page

相当于:

当前 JSP Servlet 对象

类似:

this

四十七、exception

只有页面配置:

isErrorPage="true"

时通常可以使用。

表示:

当前异常对象

四十八、为什么 JSP 有这些内置对象

因为 JSP 最终翻译成 Servlet,

Tomcat 在生成 _jspService() 时已经帮我们准备好了:

request
response
session
application
out
...

所以 JSP 中直接可以用。


四十九、Servlet 如何传数据给 JSP

Servlet:

request.setAttribute(
        "name",
        "张三"
);

然后:

request
        .getRequestDispatcher(
                "/WEB-INF/views/user.jsp"
        )
        .forward(
                request,
                response
        );

JSP:

<%= request.getAttribute("name") %>

五十、request attribute 是核心桥梁

传统 MVC 中:

Servlet
负责准备数据

request
负责携带数据

JSP
负责展示数据

五十一、传 JavaBean

Servlet:

Student student =
        studentService.findById(
                1L
        );

request.setAttribute(
        "student",
        student
);

JSP:

<%
    Student student =
            (Student)
            request.getAttribute(
                    "student"
            );
%>

然后:

<%= student.getName() %>

五十二、为什么这种 JSP 写法不够好

因为页面中又开始:

import Java 类

强制类型转换

调用 Getter

写 Java 脚本

所以后面 EL 会改成:

${student.name}

更简洁。


五十三、传 List

Servlet:

List<Student> students =
        studentService.findAll();

request.setAttribute(
        "students",
        students
);

传统 JSP:

<%
    List<Student> students =
            (List<Student>)
            request.getAttribute(
                    "students"
            );
%>

五十四、传统 for 循环展示

<%
    for (
        Student student :
        students
    ) {
%>

    <p>
        <%= student.getName() %>
    </p>

<%
    }
%>

这种代码虽然能运行,

但:

可读性比较差

五十五、后面 JSTL 会怎么写

后面可以变成:

<c:forEach
        items="${students}"
        var="student"
>

    <p>
        ${student.name}
    </p>

</c:forEach>

更接近:

模板页面

五十六、Servlet + JSP 最基本 MVC

例如:

StudentListServlet
↓
studentService.findAll()
↓
request.setAttribute("students", list)
↓
forward
↓
student-list.jsp

这是传统 MVC 最常见模式。


五十七、Model 是什么

MVC 中:

Model

负责:

数据
业务状态
业务逻辑

例如:

Student
StudentService
StudentMapper

五十八、View 是什么

View:

负责展示

传统 Java Web:

JSP

五十九、Controller 是什么

Controller:

负责接收请求
调用业务
选择页面

传统 Java Web:

Servlet

六十、Servlet 和 JSP 不应该互相抢职责

错误:

Servlet 拼大量 HTML

JSP 直接访问数据库

推荐:

Servlet
做控制

Service
做业务

Mapper
做数据库

JSP
做展示

六十一、完整学生列表流程

浏览器访问:

/student/list

Servlet:

@WebServlet(
        "/student/list"
)
public class StudentListServlet
        extends HttpServlet {

    private final StudentService
            studentService =
            new StudentService();

    @Override
    protected void doGet(
            HttpServletRequest request,
            HttpServletResponse response
    )
            throws ServletException,
            IOException {

        List<Student> students =
                studentService
                        .findAll();

        request.setAttribute(
                "students",
                students
        );

        request
                .getRequestDispatcher(
                        "/WEB-INF/views/student-list.jsp"
                )
                .forward(
                        request,
                        response
                );
    }
}

六十二、student-list.jsp

传统脚本版:

<%@ page
        contentType="text/html;charset=UTF-8"
        pageEncoding="UTF-8"
%>

<%@ page
        import="java.util.List"
%>

<%@ page
        import="com.example.entity.Student"
%>

<!DOCTYPE html>

<html>

<head>

    <meta charset="UTF-8">

    <title>
        学生列表
    </title>

</head>

<body>

<h1>
    学生列表
</h1>

<table border="1">

    <thead>

    <tr>

        <th>ID</th>

        <th>学号</th>

        <th>姓名</th>

        <th>年龄</th>

        <th>专业</th>

    </tr>

    </thead>

    <tbody>

    <%
        List<Student> students =
                (List<Student>)
                request.getAttribute(
                        "students"
                );

        if (
            students != null
        ) {

            for (
                Student student :
                students
            ) {
    %>

    <tr>

        <td>
            <%= student.getId() %>
        </td>

        <td>
            <%= student.getStudentNo() %>
        </td>

        <td>
            <%= student.getName() %>
        </td>

        <td>
            <%= student.getAge() %>
        </td>

        <td>
            <%= student.getMajor() %>
        </td>

    </tr>

    <%
            }
        }
    %>

    </tbody>

</table>

</body>

</html>

六十三、这个案例虽然能用,但不够现代

因为 JSP 中包含:

Java import

类型转换

for 循环

Java 方法调用

后面会继续优化为:

EL
+
JSTL

六十四、登录页面案例

login.jsp:

<%@ page
        contentType="text/html;charset=UTF-8"
        pageEncoding="UTF-8"
%>

<!DOCTYPE html>

<html>

<head>

    <meta charset="UTF-8">

    <title>
        登录
    </title>

</head>

<body>

<form
        action="<%= request.getContextPath() %>/login"
        method="post"
>

    <div>

        <label>
            用户名
        </label>

        <input
                type="text"
                name="username"
        >

    </div>

    <div>

        <label>
            密码
        </label>

        <input
                type="password"
                name="password"
        >

    </div>

    <button
            type="submit"
    >
        登录
    </button>

</form>

</body>

</html>

六十五、为什么 action 要加 contextPath

假设项目部署路径:

/demo

直接:

action="/login"

可能请求:

http://localhost:8080/login

而不是:

http://localhost:8080/demo/login

所以:

<%= request.getContextPath() %>

非常重要。


六十六、Servlet 登录处理

@WebServlet(
        "/login"
)
public class LoginServlet
        extends HttpServlet {

    @Override
    protected void doPost(
            HttpServletRequest request,
            HttpServletResponse response
    )
            throws IOException,
            ServletException {

        request.setCharacterEncoding(
                "UTF-8"
        );

        String username =
                request.getParameter(
                        "username"
                );

        String password =
                request.getParameter(
                        "password"
                );

        if (
            "admin".equals(
                    username
            )
            &&
            "123456".equals(
                    password
            )
        ) {

            request.setAttribute(
                    "username",
                    username
            );

            request
                    .getRequestDispatcher(
                            "/WEB-INF/views/success.jsp"
                    )
                    .forward(
                            request,
                            response
                    );

            return;
        }

        request.setAttribute(
                "message",
                "用户名或密码错误"
        );

        request
                .getRequestDispatcher(
                        "/login.jsp"
                )
                .forward(
                        request,
                        response
                );
    }
}

六十七、登录失败为什么用 forward

因为我们想保留:

request.message

让 login.jsp 显示错误。

如果重定向:

会新建 request

原 attribute 丢失。


六十八、success.jsp

<%@ page
        contentType="text/html;charset=UTF-8"
        pageEncoding="UTF-8"
%>

<!DOCTYPE html>

<html>

<body>

<h1>
    登录成功
</h1>

<p>
    欢迎:
    <%= request.getAttribute("username") %>
</p>

</body>

</html>

六十九、JSP 页面编码

建议统一:

UTF-8

JSP:

<%@ page
        contentType="text/html;charset=UTF-8"
        pageEncoding="UTF-8"
%>

HTML:

<meta charset="UTF-8">

项目源文件也使用:

UTF-8

七十、JSP 中文乱码排查

如果 JSP 中文乱码,检查:

JSP pageEncoding

response contentType

项目文件编码

IDEA Encoding

Tomcat URIEncoding

请求参数编码

七十一、pageEncoding 和 response 编码不是同一件事

pageEncoding
JSP 源文件怎么读

contentType charset
响应给浏览器时怎么编码

两者都建议:

UTF-8

七十二、JSP 编译错误

如果 JSP 写错 Java 语法:

<%
    int a =
%>

访问页面时可能看到:

500
JasperException

因为 JSP 翻译/编译失败。


七十三、什么是 Jasper

Tomcat 中负责 JSP:

翻译和编译

的组件通常叫:

Jasper

所以 JSP 报错常看到:

JasperException

七十四、JSP 404

JSP 404 常见:

路径错

文件不存在

项目没部署

Context Path 错

WEB-INF 页面被浏览器直接访问

七十五、WEB-INF 页面直接访问为什么 404

例如:

http://localhost:8080/demo/WEB-INF/views/a.jsp

浏览器不能直接访问。

应该:

Servlet forward

七十六、JSP 500

常见:

JSP Java 代码编译错误

NullPointerException

类型转换错误

import 错误

对象不存在

优先看:

Tomcat 控制台完整堆栈

七十七、request.getAttribute 返回 Object

例如:

Object value =
        request.getAttribute(
                "student"
        );

所以传统 JSP 需要:

(Student) value

七十八、强制类型转换风险

如果实际放的是:

User

你却:

(Student)

会:

ClassCastException

这也是为什么 EL 更方便。


七十九、request 参数和 attribute 再复习

参数:

request.getParameter(
        "name"
);

通常来自:

客户端

attribute:

request.getAttribute(
        "name"
);

通常来自:

服务器自己 setAttribute

八十、JSP 中不要直接 new Service

不推荐:

<%
    StudentService service =
            new StudentService();

    List<Student> list =
            service.findAll();
%>

这会让 JSP:

承担业务控制

破坏 MVC。


八十一、JSP 中更不要直接操作 MyBatis

错误:

<%
    SqlSession session = ...
%>

页面不应该直接:

访问数据库

八十二、JSP 页面只应该做什么

推荐:

读取已经准备好的展示数据

输出 HTML

少量展示判断

少量循环展示

后面通过:

EL + JSTL

完成。


八十三、Servlet 决定跳哪个 JSP

例如:

if (
    success
) {

    forward(
        success.jsp
    );

} else {

    forward(
        error.jsp
    );
}

这体现:

Controller 决定 View

八十四、JSP 不应该决定复杂业务流程

例如:

余额够不够
订单能不能支付
用户有没有权限

这些属于:

Service / Controller

不是 JSP。


八十五、JSP 表达式和输出转义问题

例如:

<%= request.getParameter("name") %>

如果用户输入:

<script>
...
</script>

直接输出可能造成:

XSS

八十六、为什么后面 JSTL c:out 更安全

后面可以:

<c:out
        value="${name}"
/>

默认可以对特殊字符做:

HTML 转义

比直接:

<%= ... %>

更安全。


八十七、传统 JSP 安全原则

不要:

直接输出不可信用户 HTML

尤其:

评论

昵称

留言

搜索关键词

应该:

转义

八十八、JSP 动态包含案例

header.jsp:

<header>

    <h1>
        学生管理系统
    </h1>

</header>

页面:

<jsp:include
        page="/common/header.jsp"
/>

八十九、公共页面拆分

常见:

header.jsp

footer.jsp

sidebar.jsp

navbar.jsp

减少:

重复 HTML

九十、JSP Forward 标签简单了解

JSP 也支持:

<jsp:forward
        page="/login.jsp"
/>

但真正控制请求流:

更推荐 Servlet

九十一、JSP useBean 简单了解

传统 JSP 标签:

<jsp:useBean
        id="student"
        class="com.example.Student"
        scope="request"
/>

可以创建/获取 Bean。

但现代开发中:

使用频率较低

了解即可。


九十二、jsp:setProperty

传统:

<jsp:setProperty
        name="student"
        property="name"
        value="张三"
/>

九十三、jsp:getProperty

<jsp:getProperty
        name="student"
        property="name"
/>

九十四、为什么这些标签现在不常用

因为后来:

EL

JSTL

Spring MVC

提供了更清晰的方式。


九十五、JSP 四大作用域预览

JSP 中常见四个作用域:

page

request

session

application

详细放到下一篇。


九十六、page 作用域

当前 JSP 页面有效。

通过:

pageContext

管理。


九十七、request 作用域

一次请求有效。

例如:

request.setAttribute(
        "student",
        student
);

转发后仍然能使用。


九十八、session 作用域

同一用户会话中有效。

常用于:

登录用户
购物车
会话状态

九十九、application 作用域

整个 Web 应用共享。

本质:

ServletContext

一百、为什么 request 最适合 Servlet → JSP

因为:

展示数据通常只需要当前请求

例如:

学生列表

商品详情

查询结果

没必要放:

session
application

一百零一、不要滥用 session

如果所有数据都放 session:

内存占用增加

数据容易过期

状态变复杂

多页面互相影响

应该根据:

生命周期

选择正确作用域。


一百零二、Servlet + JSP CRUD 架构

例如学生管理:

/student/list
StudentListServlet

/student/add
StudentAddServlet

/student/edit
StudentEditServlet

/student/delete
StudentDeleteServlet

页面:

student-list.jsp

student-form.jsp

一百零三、列表页面流程

GET /student/list
↓
StudentListServlet
↓
service.findAll()
↓
request.setAttribute
↓
forward student-list.jsp

一百零四、新增页面流程

GET:

/student/add-page
↓
forward form.jsp

POST:

/student/add
↓
读取参数
↓
Service 新增
↓
redirect /student/list

一百零五、编辑流程

GET:

/student/edit?id=1
↓
查询 Student
↓
request.setAttribute
↓
forward form.jsp

POST:

/student/update
↓
读取表单
↓
更新
↓
redirect list

一百零六、删除流程

POST /student/delete
↓
读取 id
↓
删除
↓
redirect list

不建议用普通 GET 做危险删除操作。


一百零七、为什么删除更适合 POST/DELETE

GET 应该:

主要读取资源

删除属于:

改变服务器状态

所以不要:

用户点一个 GET 链接就直接删除

一百零八、JSP 表单回显

编辑页面要显示已有值:

<input
        name="name"
        value="<%= student.getName() %>"
>

后面 EL:

value="${student.name}"

更简洁。


一百零九、表单错误回显

新增失败:

request.setAttribute(
        "message",
        "学号已存在"
);

request.setAttribute(
        "student",
        student
);

forward(
        form.jsp
);

这样页面可以:

显示错误
保留用户输入

一百一十、什么时候失败用 forward

如果希望保留:

当前 request 数据
表单输入
错误信息

通常:

forward

比较方便。


一百一十一、什么时候成功用 redirect

新增成功后:

redirect 到列表

防止:

刷新重复提交

一百一十二、MVC 完整结构

Browser
↓
Servlet Controller
↓
Service
↓
Mapper
↓
MySQL
↓
Controller
↓
request attribute
↓
JSP View
↓
HTML
↓
Browser

一百一十三、传统 Java Web 包结构

com.example
├─ entity
├─ mapper
├─ service
├─ servlet
└─ util

页面:

webapp
└─ WEB-INF
   └─ views

一百一十四、为什么 Servlet 类不要叫 XxxPage

更推荐:

UserListServlet

UserAddServlet

LoginServlet

名称表达:

处理什么请求

一百一十五、JSP 命名建议

例如:

login.jsp

student-list.jsp

student-form.jsp

student-detail.jsp

做到:

见名知意

一百一十六、常见错误:JSP 里写数据库连接

错误:

JSP
→ JDBC / MyBatis

正确:

JSP
只展示

Servlet / Service
负责逻辑

一百一十七、常见错误:Servlet 不 forward

Servlet:

request.setAttribute(
        "students",
        list
);

然后没有:

forward

浏览器不会自动跳到 JSP。


一百一十八、常见错误:forward 路径写错

推荐:

request
        .getRequestDispatcher(
                "/WEB-INF/views/student-list.jsp"
        )

路径:

以 Web 应用根目录为基准

一百一十九、常见错误:sendRedirect 到 WEB-INF

错误:

response.sendRedirect(
        "/WEB-INF/views/a.jsp"
);

因为重定向是:

浏览器重新请求

浏览器无法直接访问 WEB-INF。

应该:

forward

一百二十、forward 可以进入 WEB-INF

因为:

服务器内部转发

所以:

forward(
        "/WEB-INF/views/a.jsp"
)

是可以的。


一百二十一、常见错误:request attribute 名写错

Servlet:

request.setAttribute(
        "students",
        list
);

JSP:

request.getAttribute(
        "studentList"
);

结果:

null

名称必须一致。


一百二十二、常见错误:request 被重定向后丢失

Servlet:

request.setAttribute(
        "message",
        "成功"
);

response.sendRedirect(
        "/result.jsp"
);

result.jsp:

拿不到 message

因为:

重定向是新请求

一百二十三、常见错误:页面直接访问导致数据为空

你直接打开:

/student-list.jsp

但没有经过:

StudentListServlet

所以 request 中:

没有 students

这也是为什么推荐放:

WEB-INF

一百二十四、常见错误:JSP import 类找不到

可能:

类包名错误

Java 类没有编译

项目部署旧版本

Artifact 没更新

一百二十五、常见错误:JSP 修改后没生效

检查:

Tomcat 是否重新部署

IDEA Artifact 更新

浏览器缓存

JSP 是否改对项目

一百二十六、常见错误:JSP 500 JasperException

优先看:

Root Cause
Caused by
具体 JSP 行号

不要只看:

最上面一行错误

一百二十七、JSP 编译后文件在哪里

Tomcat 通常会在:

work

目录生成 JSP 对应 Java 和 class。

如果使用 IDEA:

CATALINA_BASE

可能是 IDEA 临时目录。

所以编译后的 JSP 文件未必在:

Tomcat 安装目录

一百二十八、为什么 CATALINA_BASE 很重要

如果 IDEA 使用自己的:

CATALINA_BASE

那:

work
logs
conf
webapps

等运行文件可能在:

IDEA 创建的实例目录

而不是 CATALINA_HOME。


一百二十九、JSP 与前后端分离

传统:

Servlet + JSP

属于:

服务端渲染

服务器生成:

HTML

返回浏览器。


一百三十、Vue 模式

前后端分离:

Servlet / SpringBoot
返回 JSON

Vue
负责生成页面

流程:

Browser
↓
Vue
↓
HTTP API
↓
Java
↓
JSON
↓
Vue
↓
DOM

一百三十一、为什么还要学 JSP

因为 JSP 能帮助理解:

服务端渲染

MVC

request 转发

作用域

EL

JSTL

传统 Java Web

这些概念后面 Spring MVC 仍然有对应思想。


一百三十二、JSP 与 Spring MVC View

Spring MVC 也可以:

Controller
↓
Model
↓
JSP

只是现代项目更多:

JSON + Vue

一百三十三、JSP 不等于过时到完全没用

需要客观看:

新项目中使用减少

老项目中仍常见

学习 MVC 很有价值

考试和课程仍可能要求

一百三十四、练习题 1:Hello JSP

创建:

hello.jsp

显示:

Hello JSP

一百三十五、练习题 2:JSP 脚本元素

分别练习:

<%! %>

<% %>

<%= %>

观察:

它们分别被翻译到哪里

一百三十六、练习题 3:Servlet 传字符串

Servlet:

request.setAttribute(
        "message",
        "欢迎学习 JSP"
);

forward JSP。

JSP:

输出 message

一百三十七、练习题 4:Servlet 传 Student

Servlet:

查询 Student

setAttribute

forward

JSP:

显示 id
name
age
major

一百三十八、练习题 5:Servlet 传 List

查询:

List<Student>

JSP 用传统:

for

输出表格。


一百三十九、练习题 6:登录页面

实现:

login.jsp

LoginServlet

success.jsp

要求:

POST 登录

失败 forward

成功 forward 或 redirect

并理解区别。


一百四十、练习题 7:公共 header

创建:

header.jsp

分别使用:

<%@ include %>

<jsp:include>

观察效果。


一百四十一、练习题 8:WEB-INF

把 JSP 放:

WEB-INF/views

测试:

浏览器直接访问失败

Servlet forward 成功

一百四十二、练习题 9:学生列表 MVC

完成:

StudentListServlet

StudentService

StudentMapper

student-list.jsp

完整展示数据库学生。


一百四十三、练习题 10:新增后重定向

POST:

/student/add

成功后:

redirect /student/list

验证:

刷新列表不会重复新增

一百四十四、必须掌握的 JSP 语法

<%@ page %>

<%@ include %>

<%@ taglib %>

<%! %>

<% %>

<%= %>

<%-- --%>

一百四十五、必须掌握内置对象

request

response

session

application

out

pageContext

config

page

exception

一百四十六、必须掌握 Servlet + JSP 配合

request.setAttribute(
        "key",
        value
);

request
        .getRequestDispatcher(
                "/WEB-INF/views/a.jsp"
        )
        .forward(
                request,
                response
        );

一百四十七、必须回答的问题

学完后应该能够回答:

1. JSP 是什么?

2. JSP 为什么本质上还是 Servlet?

3. JSP 第一次访问为什么可能较慢?

4. JSP 生命周期是什么?

5. <% %> 是什么?

6. <%= %> 是什么?

7. <%! %> 是什么?

8. JSP 九大内置对象有哪些?

9. request 和 session 分别是什么?

10. 为什么 JSP 常放 WEB-INF?

11. Servlet 如何把数据传给 JSP?

12. request.setAttribute 和 getParameter 有什么区别?

13. forward 和 redirect 对 JSP 有什么影响?

14. Servlet + JSP 中 MVC 如何分工?

15. JSP 中为什么不应该直接操作数据库?

16. 为什么后面要学习 EL 和 JSTL?

17. JSP 500 JasperException 怎么排查?

一百四十八、Servlet + JSP 知识结构

Servlet + JSP
│
├─ Servlet
│   ├─ 接收请求
│   ├─ 获取参数
│   ├─ 调用 Service
│   ├─ setAttribute
│   └─ forward
│
├─ JSP
│   ├─ page 指令
│   ├─ 脚本元素
│   ├─ 内置对象
│   ├─ include
│   └─ 页面展示
│
├─ MVC
│   ├─ Model
│   ├─ View
│   └─ Controller
│
├─ 作用域
│   ├─ page
│   ├─ request
│   ├─ session
│   └─ application
│
└─ 页面流转
    ├─ forward
    └─ redirect

一百四十九、从 JSP 到 EL / JSTL

现在我们已经会:

<%
    Student student =
            (Student)
            request.getAttribute(
                    "student"
            );
%>

<%= student.getName() %>

也会:

<%
    for (
        Student student :
        students
    ) {
%>

    ...

<%
    }
%>

但这种写法:

Java 代码太多

所以后面要学习:

EL
Expression Language

把:

<%= student.getName() %>

改成:

${student.name}

再学习:

JSTL

把 Java for 循环改成:

<c:forEach>

一百五十、本章总结

JSP 最核心的一句话:

JSP 是一种服务端页面技术,最终会被 Tomcat 翻译成 Servlet,用来生成动态 HTML。

Servlet + JSP 最核心分工:

Servlet
负责控制

Service
负责业务

Mapper
负责数据访问

JSP
负责展示

完整流程:

浏览器
↓
Servlet
↓
Service
↓
Mapper
↓
数据库
↓
Servlet
↓
request.setAttribute
↓
forward
↓
JSP
↓
HTML
↓
浏览器

一定要记住:

JSP 本质上仍然是 Servlet

JSP 不应该写复杂业务逻辑

JSP 不应该直接访问数据库

Servlet 不应该拼大量 HTML

WEB-INF 中 JSP 需要服务器内部转发

当前请求展示数据优先放 request

到这里:

Servlet + JSP

这套传统 Java Web MVC 基础已经建立。

下一篇按照课程表继续:

Servlet 交互
作用域
EL
JSTL

重点会解决:

页面怎么更方便取数据

四大作用域怎么选

JSP 怎么彻底减少 Java 脚本

列表和条件怎么用标签完成