HTML

CSS

JavaScript

JQuery

什么是JQuery

JQuery,顾名思义,就是JavaScript和Query(查询),它是辅助JavaScript开发的js类库

使用JQuery的好处

JQuery是免费的、开源的,JQuery的语法设计可以使开发更加便捷,例如操作文档对象、选择DOM元素、制作动画效果、事件处理、使用ajax以及其他功能。

JQuery的核心函数

$是JQuery的核心函数,能完成JQuery的很多功能,$()就是调用$这个函数。
1.传入参数为函数时:表示页面加载完成之后。相当于window.onload=function(){}
2.传入参数为HTML字符串时:会为我们创建这个html标签对象
3.传入参数为选择器字符串时:如$(“#id属性值”):id选择器,还适用于标签名和类名
4.传入参数为DOM对象时,将DOM对象包装为JQuery对象返回

1
2
3
4
5
6
7
8
$(function () {  //表示页面加载完成之后,相当于window.onload=function(){}
let $btnObj = $("#btnId") //表示按ID查询标签对象,习惯在变量名前加$
$btnObj.click(function () {
alert('jQuery的单击事件')
})
// 创建并添加对象
$(`<div>你好</div>`).appendTo("body")
})

JQuery对象和DOM对象使用区别

JQuery对象不能使用DOM对象的属性和方法
DOM对象也不能使用JQuery对象的属性和方法

DOM对象和JQuery对象互转

1.DOM对象转化为JQuery对象
let $obj = $(dom对象)
2.JQuery对象转化为DOM对象
let dom = $obj[下标]

基本选择器

#ID选择器:根据id查找标签对象
.class选择器:根据class查找标签对象
element选择器:根据标签名查找标签对象
*选择器:表示任意的,所有的元素
selector1,selector2组合选择器:合并选择器1,选择器2的结果并返回
示例:

1
2
3
4
5
6
$("#id")  //id选择器
$(".class") //class选择器
$("div") //element选择器
$("*") //*选择器
$("div,span") //组合选择器

层级选择器

后代选择器

HTML代码:

1
2
3
4
5
6
7
8
<form>
<input name="name">
<fieldset>
<label>NewSeletter</label>
<input name="newsletter">
</fieldset>
</form>
<input name="none">

JQuery代码:

1
$("form input")  //父与子元素中间用空格隔开

父子选择器

jQuery代码:

1
$("form > input")  //input是form的一个孩子节点

prev+next

匹配所有紧接在prev元素后的next元素

1
$("label + input")

prev + siblings

匹配在prev后所有的元素

1
$("form ~ input")  //匹配所有form之后的input元素

过滤选择器

基本过滤选择器

:first

获取第一个元素

1
2
3
4
5
6
7
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>

jQuery代码:

1
$("li:first")

:last

获取最后一个元素
jQuery代码:

1
$("li:last")

:not(selector)

去除所有与给定选择器匹配的元素

1
2
<input name="apple">
<input name="flower" checked="checked">

jQuery代码:

1
$("input:not(:checked)")

:even

匹配所有索引值为偶数的元素,从0开始计数

1
2
3
4
5
6

<table>
<tr>Header</tr>
<tr>Value1</tr>
<tr>Value2</tr>
</table>

jQuery代码:

1
2
<!--查找表格的135行-->
$("tr:even")

:odd

匹配所有索引值为奇数的元素,从0开始计数
jQuery代码:

1
2
<!--查找表格的246行-->
$("tr:odd")

:eq(index)

匹配一个给定索引值的元素
jQuery代码:

1
2
// 查找第二行
$("tr:eq(1)")

:gt(index)

匹配所有大于给定索引值的元素
jQuery代码:

1
$("tr:gt(0)")  //查找第二行和第三行,即索引值是1和2.比0大

:lt(index)

匹配所有小于给定索引值的元素
jQuery代码:

1
$("tr:lt(2)")  //查找第一行和第二行,即索引值是0和1,比2小

匹配如h1,h2,h3之类的标题元素

1
2
3
4
<h1>Header 1</h1>
<p>Context1</p>
<h2>Context2</h2>
<p>Context3</p>

jQuery代码:

1
$("header").css("background","#EEE")  //给页面内所有的标题都加上背景色

:animated

匹配所有正在执行动画效果的元素

其它选择器请查看jQueryAPI文档

https://jquery.cuishifeng.cn/index.html

XML

什么是XML

xml是可扩展的标记性语言。
主要作用为:1.用来保存数据,而且这些数据具有自我描述性。

1
2
3
4
5
6
7
8
9
10
11
<!--students.xml-->
<students>
<student>
<id>1</id>
<name>张三</name>
</student>
<student>
<id>2</id>
<name>李四</name>
</student>
</students>

2.它还可以作为项目或者模块的配置文件。
3.还可以作为网络传输数据的格式。

XML语法

文档声明

1.创建一个xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8" ?>
<!--
以上内容就是xml文件内容的声明
version="1.0" 表示xml的版本
encoding="utf-8" encoding表示xml文件的编码

-->
<books> <!-- books表示多个图书信息 -->
<book sn="SN123456789"> <!-- book表示一个图书信息 sn属性表示图书序列号 -->
<name>时间简史</name> <!-- name标签表示书名 -->
<author>霍金</author> <!-- author标签表示作者 -->
<price>75</price> <!-- price标签表示价格 -->
</book>
<book sn="SN123456769"> <!-- book表示一个图书信息 sn属性表示图书序列号 -->
<name>Java从入门到入土</name> <!-- name标签表示书名 -->
<author>安格斯</author> <!-- author标签表示作者 -->
<price>9.9</price> <!-- price标签表示价格 -->
</book>
</books>

xml注释

xml和html的注释一样:<!– –>

xml元素

XML 命名规则

1.名称可以含字母、数字以及其他的字符
2.名称不能以数字或者标点符号开始
3.名称不能以字符 “xml”(或者 XML、Xml)开始
4.名称不能包含空格
5.可使用任何名称,没有保留的字词。

XML语法规则

1.所有XML元素都必须有关闭标签。
2.XML标签对大小写敏感。
3.XML必须正确的嵌套。
4.文档必须有一个根元素,且是唯一的。
5.XML的属性值必须加引号。
6.XML中的特殊字符,与html保持一致。
7.文本区域(CDATA区):
CDATA语法可以告诉xml解析器,CDATA里的文本内容,只是纯文本,不需要xml语法解析。
CDATA格式:<![CDAT[这里可以把输入的字符原样显示,不会解析xml]]>

dom4j解析技术

使用第三方公司的dom4j.jar进行xml的解析
1.在src文件夹下创建一个com.geo.pojo的文件夹
2.在pojo文件夹下创建一个java文件
3.定义所需要的变量
4.使用alt+ins快速创建get和set
5.使用alt+ins创建构造函数
6.导入下载好的jar包,在根目录下创建一个lib文件夹用于存放

Dom4j.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.geo.pojo;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;

import java.math.BigDecimal;
import java.util.List;

/**
* @author yuxiaohong
* @package com.geo.pojo
* @date 2023/3/21 10:18
* @description
*/
public class Dom4jTest {
@Test
public void test1() throws Exception {
// 创建一个SaxReader输入流,去读取xml配置文件,生成Document对象
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("D:\\IDEA_Workspace\\JavaWeb\\_05-xml\\xml\\books.xml");

System.out.println(document);
}


// 读取books.xml文件生成Book类
@Test
public void test2() throws DocumentException {
// 1.读取books.xml文件
SAXReader reader = new SAXReader();
// 在Junit测试中,相对路径是从模块名开始算
Document document = reader.read("src/books.xml");
// 2.通过Document对象获取根元素
Element rootElement = document.getRootElement();
System.out.println(rootElement);
// 3.通过根元素获取book标签对象
// element()和elements()都是通过标签名查找子元素
List<Element> books = rootElement.elements("book");
// 4.遍历,处理每个book标签转换为Book类
for (Element book : books){
// axXML()是把标签对象转换为标签字符串
Element nameElement = book.element("name");
// getText()方法可以获取标签中的文本内容
String nameText = nameElement.getText();

String priceText = book.elementText("price");
String authorText = book.elementText("author");
String snValue = book.attributeValue("sn");
System.out.println(new Book(snValue,nameText, BigDecimal.valueOf(Double.parseDouble(priceText)),authorText));
}
}
}

输出结果:

JavaWeb的概念

什么是JavaWeb

JavaWeb是指,所有通过Java语言编写可以通过浏览器访问的程序的总称,叫JavaWeb。
JavaWeb是基于请求和响应来开发的。

什么是请求

请求是指客户端给服务器发送数据,叫请求Request

什么是响应

响应是指服务器给客户端回传数据,叫响应Response

Web资源的分类

web资源按实现的技术和呈现的效果不同,又分为静态资源和动态资源两种。
静态资源:html、css、js、txt、mp4、jpg图片
动态资源:jsp页面,servlet程序

常用的web服务器

Tomcat、Jboss、GlassFish、Resin、WebLogic

Tomcat服务器和Servlet版本的对应关系:

Tomcat的使用

下载、安装、配置环境变量

目录介绍

bin:用于存放Tomcat服务器的可执行程序
conf:用于存放Tomcat服务器的配置文件
lib:用于存放Tomcat服务器的jar包
logs:用于存放Tomcat服务器运行时输出的日志信息
temp:用于存放Tomcat运行时产生的临时的数据
webapps:用于存放部署的web工程
work:是Tomcat工作时的目录,用来存放Tomcat运行时jsp翻译为servlet的源码,和Session钝化的目录

如何修改Tomcat的端口号

找到Tomcat目录下的conf目录,找到server.xml配置文件。
找到connector标签,将port属性更改为想要的端口号即可(1-65535)。
修改完端口号,一定要重启Tomcat服务器才会生效。

如何部署web工程到Tomcat中

第一种方法:只需要把web工程的目录拷贝到webapps的目录中即可

然后输入地址:http://localhost:8080/工程名

第二种部署方法:找到Tomcat下的conf目录\Catalina\localhost\下,创建如下的配置文件:

xxxxxxxxxx37 1public List executeQuery(Class clazz, String sql, Object… params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {2    Connection connection = JdbcUtilsV2.getConnection();3    PreparedStatement preparedStatement = connection.prepareStatement(sql);4    if (params != null && params.length != 0) {5        for (int i = 1; i <= params.length; i++) {6            preparedStatement.setObject(i, params[i - 1]);7       }8   }9    ResultSet resultSet = preparedStatement.executeQuery();10    List list = new ArrayList<>();11    ResultSetMetaData metaData = resultSet.getMetaData();12    int columnCount = metaData.getColumnCount();13    while (resultSet.next()) {14        T t = clazz.newInstance();15        for (int i = 1; i <= columnCount; i++) {16            // 对象的属性值17            Object object = resultSet.getObject(i);18            // 获取指定下角标列的名称19            String columnLabel = metaData.getColumnLabel(i);20​21            // 反射,给对象的属性赋值22            Field field = clazz.getDeclaredField(columnLabel);23            // 权限可能是私有的,更改权限24            field.setAccessible(true);25            field.set(t, object);26       }27        list.add(t);28​29   }30    resultSet.close();31    preparedStatement.close();32    if (connection.getAutoCommit()) {33        JdbcUtilsV2.closeConnection();34   }35    return list;36​37}java

ROOT工程的访问,以及默认index.html页面的访问

当我们在浏览器地址栏中输入访问地址如下:
http://localhost:8080/ ===> 没有工程名的时候,默认访问的是ROOT工程
http://localhost:8080/工程名/ ===> 没有资源名,默认访问index.html页面

IDEA整合Tomcat服务器

IDEA中动态web工程的操作

  1. 创建一个新模块(JavaEE,现为JakartaEE)

  2. 如果访问servlet报500错误,可能是JavaEE版本太低了(Tomcat版本太高了)。所以选择这个。

  3. 然后打开项目结构,创建工件,选择Web应用程序展开型,基于模块。

  4. 如果没有基于模块的选项,则添加web支持。

  5. 此时如果报错无法保存源根xxxxxxx,解决方法:

  6. 控制台乱码(高版本JDK22和tomcat10.1),将文件编码全部改为GBK,tomcat运行配置虚拟机属性改为GBK,tomcat文件夹中conf文件夹下的logging.properties,其下内容改为:java.util.logging.ConsoleHandler.encoding = GBK。

  7. 在webapp目录下的WEB-INF目录下创建一个lib文件夹用于存放导入的jar包

  8. 确认Tomcat实例中有要部署运行的web工程模块

Servlet技术

什么是Servlet

1.Servlet是JaveEE规范之一,就是接口。
2.Servlet是JavaWeb三大组件之一,分别是:Servlet程序、Filter过滤器、Listener监听器。
3.Servlet是运行在服务器上的一个Java小程序,它可以接受客户端发送过来的请求,并响应数据给客户端。

手动实现Servlet程序

1.编写一个类去实现Servlet接口
创建HelloServlet类
2.实现一个Service方法,处理请求,并响应数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.example._06servlet;

import jakarta.servlet.*;

import java.io.IOException;

/**
* @author yuxiaohong
* @package com.example._06servlet
* @date 2023/3/27 20:20
* @description
*/
public class HelloServlet implements Servlet {
// 下面使用alt+ins生成
@Override
public void init(ServletConfig servletConfig) throws ServletException {

}

@Override
public ServletConfig getServletConfig() {
return null;
}

/**
* service方法是专门用来处理请求和响应的
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
* */
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello Servlet 被访问了");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}



3.到web.xml中去配置servlet程序的访问地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<!-- servlet标签给Tomcat配置Servlet程序-->
<servlet>
<!-- servlet-name标签给Servlet程序起一个别名,一般是类名-->
<servlet-name>HelloServlet</servlet-name>
<!-- servlet-class是servlet程序的全类名(包名+类名)-->
<servlet-class>com.example._06servlet.HelloServlet</servlet-class>
</servlet>
<!-- servlet-mapping标签给servlet程序配置访问地址-->
<servlet-mapping>
<!-- servlet-name标签的作用是告诉服务器,当前配置的地址给哪个Servlet程序使用-->
<servlet-name>HelloServlet</servlet-name>
<!-- url-pattern标签配置访问地址
/ 斜杠在服务器解析的时候,表示地址为:http://ip:root/工程路径
/ hello 表示地址为:http://ip:root/工程路径/hello
-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>

在部署运行之后在路径后加上/hello就会访问到HelloServlet,并且控制台会输出Hello Servlet被访问了

Servlet的生命周期

1.执行Servlet构造器方法
2.执行init初始化方法
第一二步是在第一次访问的时候创建Servlet程序会调用。
3.执行service方法
第三步,每次访问都会调用。
4.执行destroy销毁方法
第四步,在web工程停止的时候调用。

Servlet请求的分发处理

什么是请求分发:针对get和post操作执行不同的代码
在HelloServlet.java文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.example._06servlet;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;

import java.io.IOException;

/**
* @author yuxiaohong
* @package com.example._06servlet
* @date 2023/3/27 20:20
* @description
*/
public class HelloServlet implements Servlet {

public HelloServlet() {
System.out.println("1.构造器方法");
}

@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("2.init初始化");
}

@Override
public ServletConfig getServletConfig() {
return null;
}

/**
* service方法是专门用来处理请求和响应的
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
* */
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
// 执行你的功能代码
System.out.println("3.service === Hello Servlet 被访问了");

// 请求分发操作
//类型转换(因为它有getMethod()方法)
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
//获取请求方式
String method= httpServletRequest.getMethod();

if("GET".equals(method)){
doGet();
}else if("POST".equals(method)){
doPost();
}
}

// 做get请求的操作
public void doGet(){
System.out.println("get请求");
}

// 做post请求的方法
public void doPost(){
System.out.println("post请求");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {
System.out.println("4.destroy销毁方法");
}
}

通过继承HttpServlet实现Servlet程序

一般在实际项目开发中,都是使用继承HttpServlet类的方式去实现Servlet程序
1.编写一个类去继承HttpServlet类

2.根据业务需要重写doGet或doPost方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.example._06servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
* @author yuxiaohong
* @package com.example._06servlet
* @date 2023/3/27 22:42
* @description
*/
public class HelloServlet2 extends HttpServlet {
/**
* doGet()在get请求的时候调用
* @param req
* @param resp
* @throws ServletException
* @throws IOException
* */
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HelloServlet2的doGet方法");
}
/**
* doPost()在post请求的时候调用
* @param resp
* @param req
* @throws ServletException
* @throws IOException
* */
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("HelloServlet2的doPost方法");
}
}

3.到web.xml中的配置Servlet程序的访问地址

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>HelloServlet2</servlet-name>
<servlet-class>com.example._06servlet.HelloServlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet2</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>

使用IDEA创建Servlet程序

创建方法:

然后在web.xml文件中补充一下servlet-mapping标签,IDEA会为我们自动创建Servlet标签

Servlet类的继承体系

ServletConfig类

ServletConfig类从类名上看,就知道是Servlet程序的配置信息类。
Servlet程序和ServletConfig对象都是由Tomcat负责创建,我们负责使用。
Servlet程序默认是第一次访问的时候创建,ServletConfig是每个Servlet程序创建时,就创建一个对应的ServletConfig对象

ServletConfig的三大作用

1.可以获取Servlet程序的别名 servlet-name 的值
2.获取初始化参数init-param
3.获取servletContext对象

1
2
3
4
5
6
7
8
9
10
11
12
    @Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("2.init初始化");

// 1.可以获取Servlet程序的别名 servlet-name 的值
System.out.println("servlet的别名是"+servletConfig.getServletName());
// 2.获取初始化参数init-param
System.out.println("初始化参数username的值是"+servletConfig.getInitParameter("username"));
System.out.println("初始化参数url的值是"+servletConfig.getInitParameter("url"));
// 3.获取servletContext对象
System.out.println("获取servletContext对象是"+servletConfig.getServletContext());
}

ServletContext类

什么是ServletContext

1.ServletContext是一个接口,表示Servlet上下文对象
2.一个web工程,只有一个ServletContext对象实例
3.ServletContext是一个域对象
4.ServletContext是在web工程部署启动的时候创建,在web工程停止的时候销毁

域对象是可以像Map一样存取数据的对象,叫域对象,域指的是存取对象的操作范围。

存数据:setAttribute()
取数据:getAttribute()
删数据:removeAttribute()

ServletContext类的四个作用

1.获取web.xml中配置的上下文参数context-param
2.获取当前的工程路径:/工程路径
3.获取工程部署后在服务器硬盘上的绝对路径
4.像Map一样存取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.example._06servlet; /**
* @author yuxiaohong
* @package ${PACKAGE_NAME}
* @date 2023/3/28 15:05
* @description ${DESCRIPTION}
*/

import jakarta.servlet.*;
import jakarta.servlet.http.*;

import java.io.IOException;

public class ContextServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.获取web.xml中配置的上下文参数context-param
ServletContext context = getServletConfig().getServletContext();
String username = context.getInitParameter("username");
System.out.println("context-param参数username的值是:"+username);
String password = context.getInitParameter("password");
System.out.println("context-param参数password的值是:"+password);
// 2.获取当前的工程路径:/工程路径
System.out.println("当前工程路径:"+context.getContextPath());
// 3.获取工程部署后在服务器硬盘上的绝对路径
/**
* 斜杠被服务器解析地址为:http://ip:port/工程名/
* */
System.out.println("工程部署的路径是:"+context.getRealPath("/"));
System.out.println("工程下css目录的绝对路径是:"+context.getRealPath("/css"));
System.out.println("工程下img目录的绝对路径是:"+context.getRealPath("/image/QQ图片20220907091157.jpg"));
// 4.像Map一样存取数据

context.setAttribute("key1","value1");
System.out.println("context1中获取域数据key1的的值是:"+context.getAttribute("key1"));

// 在这个servlet程序中创建域数据之后,在其他servlet程序中也可以获取到,当然前提是已经被创建了
// 重启服务器会销毁这些数据
}

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

}
}

HTTP协议

所谓的HTTP协议就是指,客户端和服务器之间通信时,发送的数据,需要遵守的规则,叫HTTP协议,HTTP协议中的数据又叫报文。

请求的HTTP协议格式

客户端给服务器发送数据叫请求,服务器给客户端回传数据叫响应。

请求又分为get请求和post请求

GET请求

1.请求行
(1)请求的方式 GET
(2)请求的资源路径[+?+请求参数]
(3)请求的协议和版本号 HTTP/1.1
2.请求头
key: value组成,不同键值对表示不同的含义
一段HTTP GET请求
第一行:请求行
1.请求的方式:GET
2.请求的资源路径:/06_servlet
3.请求的协议的版本号:HTTP/1.1
第二行及以后:请求头
Accept:告诉服务器,客户端可以接收的数据类型
Accept-Language:告诉服务器客户端可以接收的语言类型
1.zh_CN:中文中国
2.en_US:英文美国
User-Agent:就是浏览器的信息
Accept-Encoding:告诉服务器,客户端可以接收的数据编码(压缩)格式
Host:表示请求的服务器ip和端口号
Connection:告诉服务器当前连接如何处理
1.Keep-Alive:告诉服务器回传完数据不要马上关闭,保持一小段时长的连接
2.Closed:马上关闭

POST请求

1.请求行
(1)请求的方式 POST
(2)请求的资源路径[+?+请求参数]
(3)请求的协议和版本号 HTTP/1.1
2.请求头
key: value组成,不同键值对表示不同的含义

3.请求体 ===> 就是发送给服务器的数据
一段HTTP POST请求
第一行:请求行
1.请求的方式:POST
2.请求的资源路径:/06_servlet/hello3
3.请求的协议和版本号:HTTP/1.1
第二行及以后:请求头
1.Accept:表示客户端可以接收的数据类型
2.Accept-Language:表示客户端可以接收的语言类型
3.Referer:表示请求发起时,浏览器地址栏中的地址(从哪来)
4.User-Agent:表示浏览器的信息
5.Content-Type:表示发送的数据的类型
application/x-www-form-urlencoded:表示提交的数据格式是:name=value&name=value,然后对其进行url编码
url编码是把非英文内容转换为:%xx%xx
multipart/form-data:表示以多段的形式提交数据给服务器(以流的形式,用于上传)
6.Content-length:表示发送的数据的长度
7.Cache-Control:表示如何控制缓存,no-cache不缓存
最后一行:请求体
action=login&username=root(发送给服务器的数据)

哪些是get请求,哪些是post请求
Get请求有哪些:

1.form标签method=get
2.a标签
3.link标签引入css
4.script标签引入js文件
5.img标签引入图片
6.iframe引入html页面
7.在浏览器地址栏中输入地址后敲回车

Post请求有哪些:

form标签method=post

响应的HTTP协议格式

1.响应行:
(1)响应的协议和版本号
(2)响应状态码
(3)响应状态描述符
2.响应头
(1)key:value
空行
3.响应体 ===> 就是回传给客户端的数据

第一行:响应行
1.响应的协议:HTTP/1.1
2.响应状态码:200
3.响应状态描述符:OK
第二行及以下:响应头
1.Server:表示服务器的信息
2.Content-Type:表示响应体的数据类型
3.Content-length:响应体的长度
4.Date:请求响应的时间(格林时间)
空行以下:响应体

常见的响应码

200:表示请求成功
302:表示请求重定向
404:表示请求服务器已经收到了,但是数据不存在(请求地址错误)
500:表示服务器已经收到请求,但是服务器内部错误

MIME类型说明

MIME是HTTP协议中数据类型。
MIME的英文全称是”Multipurpose Internet Mail Extensions”多功能Internet邮件扩充服务。MIME类型的格式是”大类型/小类型“,并与某一种文件的扩展名相对应。
常见的MIME类型:

HttpServletRequest类

HttpServletRequest类有什么作用

每次只要有请求进入Tomcat服务器,Tomcat服务器就会把请求过来的HTTP协议信息解析好封装到Request对象中。
然后传递到service方法(doGet和doPost)中给我们使用。我们可以通过HttpServletRequest对象,获取到所有的请求信息。

HttpServletRequest类的常用方法

1.getRequestURI():获取请求的资源路径
2.getRequestURL():获取请求的统一资源定位符(绝对路径)
3.getRemoteHost():获取客户端的ip地址
4.getHeader():获取请求头
5.getParameter():获取请求的参数
6.getParameterValues():获取请求的参数(多个值的时候使用)
7.getMethod():获取请求的方式GET或POST
8.setAttribute(key,value):设置域数据
9.getAttribute(key):获取域数据
10.getRequestDispatcher():获取请求转发对象

请求的转发

请求转发是指,服务器收到请求后,从一个资源跳转到另一个资源的操作

具体实现代码:

Servlet1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的参数
String username = req.getParameter("username");
System.out.println("在Servlet1中查看参数:"+username);

// 给材料盖一个章,并传给Servlet2,使用域数据
req.setAttribute("key","Servlet1的章");

// 问路,Servlet2怎么走
// 请求转发,必须要以斜杠开头
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servlet2");

// 走向Servlet2
requestDispatcher.forward(req,resp);
}
}

Servlet2

1
2
3
4
5
6
7
8
9
10
11
12
public class Servlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
System.out.println("在Servlet2中查看参数:"+username);

Object key = req.getAttribute("key");
System.out.println("Servlet1是否有章:"+key);
// 处理自己的业务:
System.out.println("servlet2处理自己的业务");
}
}

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<servlet>
<servlet-name>Servlet1</servlet-name>
<servlet-class>com.geo.servlet.Servlet1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet1</servlet-name>
<url-pattern>/servlet1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Servlet2</servlet-name>
<servlet-class>com.geo.servlet.Servlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet2</servlet-name>
<url-pattern>/servlet2</url-pattern>
</servlet-mapping>

请求转发的特点:
1.浏览器地址栏没有变化
2.他们是一次请求
3.他们共享Request域中的数据
4.可以转发到WEB-INF目录下

base标签的作用:主要用于工程中相对路径的参照,写在title标签的下方

HttpServletResponse类

HttpServletResponse类的使用

HttpServletResponse类和HttpServletRequest类一样,每次请求进来,Tomcat服务器就会创建一个Response对象传递给Servlet程序去使用。HttpServletRequest表示请求过来的信息,HttpServletResponse表示所有响应的信息。
需要返回给客户端的消息,就可以通过HttpServletResponse对象来进行设置。

两种输出流的使用

字符流 getOutputStream() 常用于下载(传递二进制数据)
字节流 getWriter() 常用于回传字符串(常用)

两个输出流同时只能使用一个。
使用了字节流,就不能再使用字符流。否则会报错。

往客户端回传数据

1.往客户端回传字符串数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.geo.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class ResponseIOServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.write("之一's content!"); //使用中文会出现乱码,回传的字符集编码默认是ISO-8859-1,所以中文会乱码
}
}

客户端乱码解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.geo.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class ResponseIOServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();

// 设置服务器回传的数据编码集
// resp.setCharacterEncoding("UTF-8");
// 通过响应头,设置浏览器也使用UTF-8字符集
// resp.setHeader("Content-Type", "text/html;charset=UTF-8");

// 这一行代码同时设置好了服务器的编码以及响应头的编码,此方法一定要在获取流对象之前调用才有效
resp.setContentType("text/html;charset=UTF-8");
writer.write("之一's content!");
}
}

请求重定向

请求重定向,是指客户端向服务器发出请求,然后服务器告诉客户端,给一个新的地址,去新地址进行访问,称作请求的重定向。(因为之前的地址可能被废弃了)

请求重定向的特点

1.浏览器中的地址会发生变化
2.两次请求
3.不共享Request域中的数据
4.不能访问WEB-INF下的资源
5.可以访问工程外部的资源,比如百度等

Response1.java

1
2
3
4
5
6
7
8
9
10
public class Response1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("访问到了Response1");
// 设置响应状态码,表示重定向
resp.setStatus(302);
// 设置响应头,说明新的地址在哪里
resp.setHeader("Location", "http://localhost:8080/_07_servlet/response2");
}
}

Response2.java

1
2
3
4
5
6
7
public class Response2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("我是Response2");
resp.getWriter().write("我是Response2");
}
}

上述代码实现的功能就是当访问Response1的时候,会跳转到Response2的页面,然后输出相关内容。从而实现重定向的功能。

第二种重定向方法,更加简洁

Response1.java

1
2
3
4
5
6
7
public class Response1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("访问到了Response1");
resp.sendRedirect("http://localhost:8080/_07_servlet/response2")
}
}

JavaEE项目的三层架构

Web层负责页面展示,Service层负责业务逻辑的处理,Dao层负责与数据库进行交互。

JavaEE项目为什么要进行分层?
分层的目的是为了解耦,降低代码的耦合度,方便项目后期的维护和升级。

一般分为以下结构:
Web层: com.geo.web/servlet/controller
service层: com.geo.service service接口包
com.geo.service.impl service接口实现类
Dao持久层: com.geo.dao Dao接口包
com.geo.dao.impl Dao接口实现类
实体Bean对象: com.geo.pojo/entity/domain/bean JavaBean类
测试包: com.geo.test/junit
工具类: com.geo.utils

第一步:创建好项目结构
创建好之后项目结构如下所示:

第二步,在数据库中创建好相应的数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE DATABASE book;

USE book;

CREATE TABLE t_user(
`id` int PRIMARY KEY auto_increment,
`username` VARCHAR(20),
`password` VARCHAR(32),
`email` VARCHAR(200)
);

INSERT INTO t_user(`username`,`password`,`email`) VALUES('admin','admin','geo');

SELECT * FROM t_user;

可以看到已经创建好t_user的表格了。

第三步,创建好数据库表对应的JavaBean对象。

在pojo层中创建User类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.geo.pojo;

/**
* @author yuxiaohong
* @package com.geo.pojo
* @date 2023/7/7 9:41
* @description
*/
public class User {
private Integer id;
private String username;
private String password;
private String email;

// 生成get和set方法
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
'}';
}

// 无参构造函数
public User() {
}

// 有参构造函数
public User(Integer id, String username, String password, String email) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
}
}

第四步,编写JdbcUtils类
首先在Utils包下简历JdbcUtils类,然后将下载好的mysql-connector-java.jar和druid.jar拖到web-inf下的lib文件夹中,然后选中这两个jar包右键添加到库。
然后将jdbc.properties的配置文件添加到src文件夹下,其中的内容如下所示:
这些都是本机数据库的一些相关配置信息。

1
2
3
4
5
6
username=root
password=123456
url=jdbc:mysql://localhost:3306/book
driverClassName=com.mysql.cj.jdbc.Driver
initialSize=5
maxActive=10

然后在JdbcUtils类中进行连接池的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.geo.utils;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
* @author yuxiaohong
* @package com.geo.utils
* @date 2023/7/7 9:48
* @description
*/
public class JdbcUtils {

private static DruidDataSource dataSource;

static {
try {
Properties properties = new Properties();
// 读取jdbc.properties属性配置文件
InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 从流中加载数据
properties.load(inputStream);
// 创建数据库连接池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}

}


// 获取数据库连接池中的连接
// 如果返回null,说明获取连接失败
public static Connection getConnection() {
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}

// 关闭连接,放回数据库连接池
public static void close(Connection connection) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}


第五步,在test包下创建JdbcUtilsTest测试类,需要添加hamcrest.jar和junit.jar到库中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.geo.test;

import com.geo.utils.JdbcUtils;
import org.junit.Test;

import java.sql.Connection;

/**
* @author yuxiaohong
* @package com.geo.test
* @date 2023/7/7 11:08
* @description
*/
public class JdbcUtilsTest {
@Test
public void testJdbcUtils() {
for (int i = 0; i < 100; i++) {
Connection connection = JdbcUtils.getConnection();
// 最大连接数量是有限制的,所以要保证每次使用完之后及时关闭连接
System.out.println(connection);
JdbcUtils.close(connection);
}
}
}

第六步,编写BaseDao,在Dao.impl包中创建BaseDao类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.geo.dao.impl;

import com.geo.utils.JdbcUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.logging.Handler;

/**
* @author yuxiaohong
* @package com.geo.dao.impl
* @date 2023/7/7 11:34
* @description
*/
public abstract class BaseDao {
// 使用jdbc操作数据库
private QueryRunner queryRunner = new QueryRunner();

/**
* @Author: yuxiaohong
* @Description: update方法用来执行insert、update、delete语句。
* @Return 如果返回-1说明执行失败
*/
public int update(String sql, Object... args) {
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.update(connection, sql, args);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.close(connection);
}
// return -1;
}

/**
* @Author: yuxiaohong
* @Description: 查询返回的一条JavaBean的sql语句
* @Params: type:返回的对象类型
* @Params: sql:执行的sql语句
* @Params: args:sql对应的参数
* @Params: T:返回的类型的泛型
* @Return
*/
public <T> T queryForOne(Class<T> type, String sql, Object... args) {
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.query(connection, sql, new BeanHandler<T>(type), args);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.close(connection);
}
}

/**
* @Author: yuxiaohong
* @Description: 查询返回的多个JavaBean的sql语句
* @Params: type:返回的对象类型
* @Params: sql:执行的sql语句
* @Params: args:sql对应的参数
* @Params: T:返回的类型的泛型
* @Return
*/
public <T> List<T> queryForList(Class<T> type, String sql, Object... args) {
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.query(connection, sql, new BeanListHandler<>(type), args);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.close(connection);
}
}
}

第七步,编写UserDao和测试,在Dao包中创建接口UserDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.geo.dao;

import com.geo.pojo.User;

/**
* @author yuxiaohong
* @package com.geo.dao.impl
* @date 2024/1/9 16:05
* @description 这个接口主要用于处理用户的登录操作
*/
public interface UserDao {

/*
* @Author: yuxiaohong
* @Description: 根据用户查询用户信息
* @Param: username 用户名
* @Return 如果返回null,说明没有这个用户,反之亦然。
*/
User queryUserByUsername(String username);

/*
* @Author: yuxiaohong
* @Description: 通过用户名和密码查询是否正确
* @Param: username
* @param password
* @Return com.geo.pojo.User
*/
User queryUserByUsernameAndPassword(String username, String password);

/*
* @Author: yuxiaohong
* @Description:保存用户的信息(注册)
* @Param: user 用户
* @Return int
*/
int saveUser(User user);
}

实现类:UserImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.geo.dao.impl;

import com.geo.dao.UserDao;
import com.geo.pojo.User;

/**
* @author yuxiaohong
* @package com.geo.dao.impl
* @date 2024/1/11 17:12
* @description
*/
public class UserImpl extends BaseDao implements UserDao {
@Override
public User queryUserByUsername(String username) {
String sql = "select `id`,`username`,`password`,`email` from t_user where username = ?";
return queryForOne(User.class, sql, username);
}

@Override
public User queryUserByUsernameAndPassword(String username, String password) {
String sql = "select `id`,`username`,`password`,`email` from t_user where username = ? and password = ?";
return queryForOne(User.class, sql, username, password);
}

@Override
public int saveUser(User user) {
String sql = "insert into t_user(`username`,`password`,`email`) values(?,?,?);";
return update(sql, user.getUsername(), user.getPassword(), user.getEmail());
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.geo.test;

import com.geo.dao.UserDao;
import com.geo.dao.impl.UserImpl;
import com.geo.pojo.User;
import org.junit.Test;

import static org.junit.Assert.*;

/**
* @author yuxiaohong
* @package com.geo.test
* @date 2024/1/11 17:21
* @description
*/
public class UserDaoTest {
@Test
public void queryUserByUsername() {
UserDao userDao = new UserImpl();
if (userDao.queryUserByUsername("admin1") == null) {
System.out.println("用户名可用!");
} else {
System.out.println("用户名已存在!");
}
}

@Test
public void queryUserByUsernameAndPassword() {
UserDao userDao = new UserImpl();
if (userDao.queryUserByUsernameAndPassword("admin", "admin123") == null) {
System.out.println("账号或密码不正确!");
} else {
System.out.println("登录成功!");
}
}

@Test
public void saveUser() {
UserDao userDao = new UserImpl();
System.out.println(userDao.saveUser(new User(1, "xiaojie", "11111", "135611")));
}
}

测试结果最终都正确无误,数据库中也新增了添加好的数据。

第八步:编写UserService和测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.geo.service;

import com.geo.pojo.User;

/**
* @author yuxiaohong
* @package com.geo.service.impl
* @date 2024/1/11 18:22
* @description
*/
public interface UserService {
/*
* @Author: yuxiaohong
* @Description: 用户注册的业务
* @Param: user 用户信息
* @Return void
*/
void registerUser(User user);

/*
* @Author: yuxiaohong
* @Description: 用户登录的业务
* @Param: user
* @Return com.geo.pojo.User
*/
User login(User user);

/*
* @Author: yuxiaohong
* @Description: 判断用户名是否存在的业务
* @Param: username
* @Return boolean
*/
boolean existsUsername(String username);
}

UserService接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.geo.service.impl;

import com.geo.dao.UserDao;
import com.geo.dao.impl.UserImpl;
import com.geo.pojo.User;
import com.geo.service.UserService;
import jdk.jshell.spi.ExecutionControl;

/**
* @author yuxiaohong
* @package com.geo.service.impl
* @date 2024/1/11 18:27
* @description
*/
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserImpl();

@Override
public void registerUser(User user) {
userDao.saveUser(user);
}

@Override
public User login(User user) {
return userDao.queryUserByUsernameAndPassword(user.getUsername(), user.getPassword());
}

@Override
public boolean existsUsername(String username) {
if (userDao.queryUserByUsername(username) == null) {
// 说明没查到,表示可用
return false;
}
return true;
}
}

UserService接口的测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.geo.test;

import com.geo.pojo.User;
import com.geo.service.UserService;
import com.geo.service.impl.UserServiceImpl;
import org.junit.Test;

import static org.junit.Assert.*;

/**
* @author yuxiaohong
* @package com.geo.test
* @date 2024/1/11 18:32
* @description
*/
public class UserServiceTest {
UserService userService = new UserServiceImpl();

@Test
public void registerUser() {
userService.registerUser(new User(1, "xiaohong", "123456", "qq@c.om"));
}

@Test
public void login() {
System.out.println(userService.login(new User(null, "xiahon", "1234", "adwa @")));
}

@Test
public void existsUsername() {
System.out.println(userService.existsUsername("xiaohong"));
}
}

项目阶段二:用户注册和登录的实现

需求1:用户注册

  1. 访问注册页面
  2. 填写注册信息,提交给服务器
  3. 服务器应当保存用户
  4. 当用户已经存在提示用户注册失败,用户名已存在
  5. 当用户不存在->注册成功

Tips:当前阶段,在表单提交的时候,使用base标签+相对路径,之后使用框架的阶段再使用绝对路径进行配置。

  1. 首先,在title标签下加上base标签,其中的路径写成部署后的工程路径:

    1
    2
    3
    <title>书城首页</title>
    <!-- 写base标签,永远固定相对路径跳转的结果-->
    <base href="http://localhost:8080/book/">
  2. 下方的使用到图片资源的路径book就相当于映射到了web文件夹。

    工程项目路径

    对应于图中的工程路径,图片以及资源就可以写为:

    1
    2
    <link type="text/css" rel="stylesheet" href="static/css/style.css">
    <script type="text/javascript" src="static/script/jquery.js"></script>
  3. 处理用户注册操作的servlet中的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    package com.geo.web;

    import com.geo.pojo.User;
    import com.geo.service.UserService;
    import com.geo.service.impl.UserServiceImpl;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;

    import java.io.IOException;

    /**
    * @author yuxiaohong
    * @package com.geo.web
    * @date 2024/1/15 12:42
    * @description
    */
    public class RegistServlet extends HttpServlet {
    private UserService userService = new UserServiceImpl();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 1.获取请求的参数
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    String email = req.getParameter("email");
    String code = req.getParameter("code");

    // 2.检查验证码是否正确,写死,要求:abcde
    if ("abcde".equalsIgnoreCase(code)) {
    // 检查用户名是否可用
    if (userService.existsUsername(username)) {
    // 返回true说明用户名已存在
    System.out.println("用户名" + username + "已存在");
    // 跳回注册页面
    req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);
    } else {
    // 表示此用户名可用,调用UserService方法存放到数据库当中
    userService.registerUser(new User(null, username, password, email));
    // 跳转到注册成功的页面
    req.getRequestDispatcher("/pages/user/regist_success.html").forward(req, resp);
    }

    } else {
    System.out.println("验证码错误,验证码是" + code);
    // 验证码错误,跳回注册页面
    req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);
    }
    }
    }

需求2:用户登录

  1. 访问登录页面

  2. 填写用户名密码后提交

  3. 服务器判断用户是否存在

  4. 如果登录失败返回用户名或密码的错误信息

  5. 登录成功则提示登录成功

    使用LiginServlet处理登录业务,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.geo.web;

import com.geo.pojo.User;
import com.geo.service.UserService;
import com.geo.service.impl.UserServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
* @author yuxiaohong
* @package com.geo.web
* @date 2024/1/23 15:04
* @description 处理用户的登录业务
*/
public class LoginServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//调用userService.login()处理登录业务
User login = userService.login(new User(null, username, password, null));
if (login == null) {
//说明登录失败
req.getRequestDispatcher("/pages/user/login.html").forward(req, resp);
} else {
//说明登录成功,跳转到登录成功页面
req.getRequestDispatcher("/pages/user/login_success.html").forward(req, resp);
}
}
}

JSP

什么是JSP,有什么作用?

  1. JSP的全称是Java Server Page,Java的服务器页面。

  2. JSP的主要作用是代替Servlet程序回传html页面的数据。

  3. 因为Servlet程序回传html页面数据是一件非常繁琐的事情,开发成本和维护成本都极高。

  4. 如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package com.geo.servlet;

    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;

    import java.io.IOException;
    import java.io.PrintWriter;

    /**
    * @author yuxiaohong
    * @package com.geo.servlet
    * @date 2024/1/23 16:05
    * @description
    */
    public class PrintHtml extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("text/html; charset = utf-8");
    //通过响应的回传流回传html页面数据
    PrintWriter writer = resp.getWriter();
    writer.write("<!DOCTYPE html>\r\n");
    writer.write("<html lang=\"en\">\r\n");
    writer.write("<head>\r\n");
    writer.write("<meta charset=\"utf-8\">\r\n");
    writer.write("<title>Title</title>\r\n");
    writer.write("</head>\r\n");
    writer.write("<body>\r\n");
    writer.write("这是html页面数据\r\n");
    writer.write("</body>\r\n");
    writer.write("</html>\r\n");
    }
    }

JSP的本质是什么?

  1. JSP页面本质上是一个Servlet程序,当我们第一次访问JSP页面的时候,Tomcat服务器会自动把JSP页面翻译成为一个Java源文件,并且将他编译为class字节码程序。

JSP的三种page指令

  1. JSP的page指令可以修改JSP页面中的一些重要属性,或者行为。

    1
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    1. language:表示JSP翻译后是什么语言的文件,暂时只支持Java。
    2. contentType:表示JSP返回的数据类型是什么,也是源码中response.setContentType()参数值。
    3. pageEncoding:表示当前JSP页面文件本身的字符集。
    4. import:与Java一样进行导包导类。
    5. autoFlush:设置当out输出流缓冲区满了之后,是否自动刷新缓冲区。默认值是true。
    6. buffer:设置out输出流缓冲区的大小,默认是8kb。
    7. errorPage:设置当JSP页面运行时错误,自动跳转的页面。
    8. isErrorPage:设置当前页面是否是错误信息页面,默认是false,如果是true,可以获取异常信息。
    9. session:设置访问当前JSP页面是否会创建HttpSession对象,默认是true。
    10. extends:设置JSP翻译出来的Java类默认继承谁。
  2. JSP中的常用脚本

    1. 声明脚本

      1. 声明脚本的格式是:<%! 中间声明Java代码 %>。

      2. 作用:可以给JSP翻译出来的Java类定义属性和方法,或静态代码块、内部类等。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        <%@ page import="java.util.Map" %>
        <%@ page import="java.util.HashMap" %><%--
        Created by IntelliJ IDEA.
        User: 13511
        Date: 2024/1/23
        Time: 16:58
        To change this template use File | Settings | File Templates.
        --%>
        <%@ page contentType="text/html;charset=UTF-8" language="java" %>
        <html>
        <head>
        <title>Title</title>
        </head>
        <body>
        这是html页面数据
        <%--1.声明类属性--%>
        <%!
        private Integer id;
        private String name;
        private static Map<String, Object> map;
        %>
        <%--2.声明静态代码块--%>
        <%!
        static {
        map = new HashMap<String, Object>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        }
        %>
        <%--3.声明类方法--%>
        <%!
        public int abc() {
        return 12;
        }
        %>
        <%--4.声明内部类--%>
        <%!
        public static class A {
        private Integer id = 12;
        private String abc = "ab";
        }
        %>
        </body>
        </html>

    2. 表达式脚本

      1. 表达式脚本的格式是:<%=表达式%>。

      2. 表达式脚本的作用是:在JSP页面上输出数据。

        1
        2
        3
        4
        5
        6
        7
        8
        <%--输出整型--%>
        <%=12%> <br/>
        <%--输出浮点型--%>
        <%=12.12%> <br/>
        <%--输出字符串--%>
        <%="我是字符串"%> <br/>
        <%--输出对象--%>
        <%=map%> <br/>
    3. 代码脚本

      1. 代码脚本的格式是:

        <%

        ​ Java代码

        %>

      2. 代码脚本的作用:可以在JSP页面中编写自己需要的功能(Java语句)。

  3. JSP中的三种注释

    1. HTML注释

    2. Java注释

    3. JSP注释

      1
      <%-- 这是JSP注释 --%>

JSP九大内置对象

  1. JSP内置对象是指Tomcat在翻译JSP页面成为Servlet源代码之后,内部提供的九大对象,叫内置对象。
    1. request:请求对象
    2. response:响应对象
    3. pageContext:JSP的上下文对象
    4. session:会话对象
    5. application:ServletContext对象
    6. config:ServletConfig对象
    7. out:JSP输出流对象
    8. page:指向当前JSP的对象
    9. exception:异常对象

JSP四大域对象

  1. pageContext(PageContextImpl类):当前JSP页面范围内有效。
  2. request(HttpServletRequest类):一次请求有效。
  3. session(HttpSession类):一个会话范围有效(打开浏览器访问服务器,直到关闭浏览器)。
  4. application(ServletContext类):整个web工程内都有效(只要web工程不停止,数据都在)。

域对象是可以像Map一样存取数据,四个域对象功能一样,不同的是他们的数据存储范围。

JSP中的out和response.getWriter()输出的区别

response表示响应,我们经常用于设置返回给客户端的内容(输出)。

out也是给客户端输出用的。

1
2
3
4
5
6
7
8
<body>
<%
out.write("out输出1 <br/>");
out.write("out输出2 <br/>");
response.getWriter().write("response输出1 <br/>");
response.getWriter().write("response输出2 <br/>");
%>
</body>

此时,我们可以看到out是要先执行的,但是结果却相反,如下图:

为什么会导致这样的结果?

当JSP页面中所有的代码执行完毕之后,会进行以下的两个操作:

  1. 执行out.flush()操作,会把out缓冲区中所有的数据追加写入到response缓冲区末尾。
  2. 会执行response的刷新操作,把全部数据写给客户端。

由于JSP翻译之后,底层源代码都是使用out进行输出,所以一般情况下,我们在JSP页面中也统一使用out进行输出,避免打乱页面的顺序。

out.write(),只适合输出字符串,输出其他类型的数据会出现错误。

out.print(),适合输出任意数据,都不会出问题(都转换为字符串之后再调用out.write()方法输出)。

总结:在任何情况下使用out.print(),都不会出问题。

JSP中的常用标签

JSP静态包含

应用举例:当有非常多的页面时,且上方、下方或者某一部分的内容是完全相同的,比如导航栏、页脚等,如果需要修改的话,所有页面对应位置都要修改,维护起来非常麻烦。所以我们将这一个相同的部分提取出来,单独写成一个JSP文件,然后在需要显示的位置引入即可。

1
2
3
4
5
6
7
<%--main.jsp的内容--%>
<body>
头部信息 <br>
主体内容 <br>
<%--静态包含--%>
<%@include file="footer.jsp" %>
</body>
1
2
3
4
<%--footer.jsp的内容--%>
<body>
页脚信息 <br>
</body>

项目结构

实际的页面信息

静态包含的特点:

  1. 静态包含不会翻译被包含的JSP页面。
  2. 静态包含实际上就是把被包含的JSP页面的代码拷贝到包含的位置进行输出。
JSP动态包含
1
2
3
4
5
<%--动态包含--%>
<jsp:include page="footer.jsp">
<%--还可以传递参数--%>
<jsp:param name="password" value="root"/>
</jsp:include>

动态包含的特点:

  1. 动态包含会把包含的JSP页面也翻译成Java代码。
  2. 动态包含底层使用代码去调用被包含的JSP页面进行输出。
  3. 动态包含还可以传递参数。
JSP标签-转发

就是重定向到另外一个JSP页面。

1
2
<%--请求转发--%>
<jsp:forward page="footer.jsp"></jsp:forward>

什么是Listener监听器

  1. Listener监听器是JaveWeb的三大组件之一,分别是Servlet程序、Filter过滤器、Listener监听器。
  2. Listener是JavaEE的规范,就是接口。
  3. 监听器的作用:监听某种事物的变化,然后通过回调函数,反馈给用户或者程序去做一些相应的处理。
ServletContextListener监听器

ServletContextListener可以监听ServletContext对象的创建和销毁。

ServletContext在Web工程启动的时候创建,在工程停止的时候销毁。

监听到创建和销毁之后,都会分别调用ServletContextListener监听器的方法反馈。

对应的两个方法如下:

1
2
3
4
5
6
7
8
public interface ServletContextListener extends EventListener {
// 此方法用于在Web工程启动的时候自动调用
default void contextInitialized(ServletContextEvent sce) {
}
// 此方法用于在Web工程销毁之后自动调用
default void contextDestroyed(ServletContextEvent sce) {
}
}

调用方法,首先创建一个实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.geo.listener;

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;

/**
* @author yuxiaohong
* @package com.geo.listener
* @date 2024/1/24 14:51
* @description ServletContextListener的实现类
*/
public class MyServletContextListenerImpl implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext对象被创建了");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext对象被销毁了");
}
}

然后在web.xml中进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<servlet>
<servlet-name>PrintHtml</servlet-name>
<servlet-class>com.geo.servlet.PrintHtml</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PrintHtml</servlet-name>
<url-pattern>/printHtml</url-pattern>
</servlet-mapping>

<listener>
<listener-class>com.geo.listener.MyServletContextListenerImpl</listener-class>
</listener>
</web-app>

接着在启动工程以及终止工程的时候会看到在控制台有相应的打印文字。

EL表达式和JSTL标签库

EL表达式

什么是EL表达式,有什么作用?

EL表达式的全称是Expression Language,是表达式语言。

作用:主要是替代JSP页面中的表达式脚本在JSP页面中的数据输出。

EL表达式的格式是:${表达式}。

EL表达式在输出null值的时候是空串,JSP表达式脚本输出的是null。

对比:

1
2
3
4
5
6
7
8
9
10
<body>
使用表达式脚本设置数据:
<%
request.setAttribute("key", "value");
%><br>
使用表达式脚本读取数据:
<%=request.getAttribute("key")%><br>

使用EL表达式读取数据:${key}
</body>

在页面上的效果:

EL表达式搜索四个域的数据的顺序

当四个域中都有相同的key的数据的时候,和JSP的域中数据读取顺序一样,按照四个域的从小到大的顺序读取,搜索到就输出。

EL表达式输出Bean的普通属性、数组属性、List集合属性、Map集合属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<%
Person person = new Person();
person.setName(12);
person.setPhones(new String[]{"123456", "7777777"});
List<String> list = new ArrayList();
list.add("北京");
list.add("上海");
list.add("张家口");
person.setCities(list);

Map<String, Object> map = new HashMap();
map.put("哈哈", 123);
map.put("lolo", 555);
person.setMap(map);

pageContext.setAttribute("Person", person);
%>

${Person}<br>
</body>
EL表达式-运算
  1. 关系运算

  2. 逻辑运算

  3. 算数运算

  4. empty运算

    empty运算可以用来判断一个数据是否为空,如果为空,输出true,反之,输出false。

    值为null值、空串、Object数组长度为0、list集合元素个数为0、map集合元素个数为0,判定为空。

  5. 三元运算

    表达式1?表达式2:表达式3

    如果表达式1为真,则返回表达式2;反之,返回表达式3。

  6. .运算符和[]运算符

    .运算可以输出Bean对象中某个属性的值。

    []运算可以输出有序集合中某个元素的值,并且还可以输出map集合中key里含有特殊字符key的值。写法:[“a.a.a”]。

EL表达式中11个隐含的对象

EL表达式中11个隐含对象,是EL表达式自己定义的,可以直接使用。

变量 类型 作用
pageContext pageContextImpl 它可以获取JSP九大内置对象
pageScope Map<String,Object> 它可以获取pageContext域中的数据
requestScope Map<String,Object> 它可以获取Request域中的数据
sessionScope Map<String,Object> 它可以获取Session域中的数据
applicationScope Map<String,Object> 它可以获取ServletContext域中的数据
param Map<String,String> 它可以获取请求参数的值
paramValues Map<String,String[]> 它可以获取多个请求参数的值
header Map<String,String> 它可以获取请求头的信息
headerValues Map<String,String[]> 它可以获取多个请求头的信息
cookie Map<String,Cookie> 它可以获取当前请求的Cookie信息
initParam Map<String,String> 它可以获取在web.xml中配置的< context-param >上下文参数
  1. EL获取四个特定域中的属性

    1. pageScope:pageContext域
    2. requestScope:Reques域
    3. sessionScope:Session域
    4. applicationScope:ServletContext域
  2. pageContext对象的使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <body>
    <%
    request.getScheme(); //获取请求的协议
    request.getServerName(); //获取请求的服务器ip或域名
    request.getServerPort(); //获取请求的服务器端口号
    request.getContextPath(); //获取当前的工程路径
    request.getMethod(); //获取请求的方式
    request.getRemoteHost(); //获取客户端的ip
    session.getId(); //获取会话的id编号
    %>
    1. 协议:${pageContext.request.scheme}<br>
    2. 服务器ip:${pageContext.request.serverName}<br>
    3. 服务器端口:${pageContext.request.serverPort}<br>
    4. 获取工程路径:${pageContext.request.contextPath}<br>
    5. 获取请求方法:${pageContext.request.method}<br>
    6. 获取客户端ip地址:${pageContext.request.remoteHost}<br>
    7. 获取会话的id编号:${pageContext.session.id}
    </body>

JSTL标签库

JSTL标签库,全称是:JSP Standard Tag Library,JSP标准标签库,是一个不断完善的开放源代码的JSP标签库。EL表达式主要是为了替换JSP中的表达式脚本,而标签库是为了替换代码脚本。这样使得整个JSP页面更加简洁。

JSTL由五种不同的标签库组成。

功能范围 URI 前缀
核心标签库-重点 http://java.sun.com/jsp/jstl/core c
格式化 http://java.sun.com/jsp/jstl/fmt fmt
函数 http://java.sun.com/jsp/jstl/functions fn
数据库(不使用) http://java.sun.com/jsp/jstl/sql sql
XML(不使用) http://java.sun.com/jsp/jstl/xml x
JSTL标签库的使用步骤
  1. 先引入两个jar包:taglibs-standard-impl-1.2.5.jar、taglibs-standard-spec-1.2.5.jar
  2. 使用taglib指令引入标签库,<%@ taglib prefix=”c” uri=”http://java.sun.com/jsp/jstl/core“ %>
set标签

作用:set标签可以往域中保存数据,scope属性设置保存到哪个域。

page表示PageContext域

request表示Request域

session表示Session域

application表示ServletContext域

var属性设置key是多少

value属性设置值是多少

1
2
3
保存之前:${requestScope.abc}<br>
<c:set scope="request" var="abc" value="123"></c:set>
保存之后:${requestScope.abc}<br>

在运行代码之后报500服务器错误,因为本人使用的Tomcat版本太高(10.0),jar包不匹配导致的,所以引入两个新的jar包jakarta.servlet.jsp.jstl-2.0.0.jar、jakarta.servlet.jsp.jstl-api-2.0.0.jar

问题成功解决。


if标签

if标签用于做if判断。

test属性表示判断的条件(使用EL表达式输出)

1
2
3
<c:if test="${12==12}">
<h1>if语句判断成功了!</h1>
</c:if>
多路判断标签

作用:多路判断,与switch,case,default相似。

choose标签开始选择判断,when标签表示每一种判断情况,tset属性表示当前这种判断情况的值,otherwise标签表示剩余的情况。

注意点:不能在标签之间使用html注释,要使用jsp注释;when标签的父标签一定要是choose标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%
request.setAttribute("height", 128);
%>
<c:choose>
<c:when test="${height>190}">
<h2>高个子</h2>
</c:when>
<c:when test="${height>170}">
<h2>正常</h2>
</c:when>
<c:when test="${height>130}">
<h2>很低</h2>
</c:when>
<c:otherwise>
<h2>身高低于130</h2>
</c:otherwise>
</c:choose>
forEach标签

作用:遍历输出使用。

begin属性设置开始的索引,end属性设置结束的索引,var属性表示循环的变量,step是步长,默认为1(即i++)。

forEach遍历1-10

1
2
3
<c:forEach begin="1" end="100" var="i">
${i}
</c:forEach>

forEach遍历对象数组

1
2
3
4
5
6
<%
request.setAttribute("arr", new String[]{"123", "456", "789"});
%>
<c:forEach items="${requestScope.arr}" var="i">
${i}
</c:forEach>

forEach遍历Map、List集合

1
2
3
4
5
6
7
8
9
10
11
12
<%
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");

request.setAttribute("arr", map);
%>

<c:forEach items="${requestScope.arr}" var="entry">
${entry.key}=${entry.value}
</c:forEach>

下方这些方法可以去掉get之后直接在EL表达式中使用。

接口方法 用法
getCurrent() 获取当前遍历到的数据
getIndex() 获取遍历的索引
getCount() 获取遍历的个数
isFirst() 当前遍历的数据是否是第一条
isLast() 当前遍历的数据是否是最后一条
getBegin() 获取begin的属性值
getEnd() 获取end的属性值
getStep() 获取step属性值

文件的上传和下载

文件的上传介绍

  1. 要有一个form标签,method=post请求。
  2. form标签的encType属性值必须为multipart/form-data值。
  3. 在form标签中使用input type=file添加上传的文件。
  4. 编写服务器代码(Servlet)接收,处理上传的数据。

具体实现步骤:

首先创建一个JSP页面,构建一个表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<%--
Created by IntelliJ IDEA.
User: 13511
Date: 2024/1/25
Time: 10:49
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="http://localhost:8080/_09_EL_JSTL/uploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"><br>
头像:<input type="file" name="photo"><br>
<input type="submit" value="上传">
</form>
</body>
</html>

然后创建一个Servlet程序,先打印一句话,测试是否连通成功。同时在web.xml中配置好servlet的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.geo.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
* @author yuxiaohong
* @package com.geo.servlet
* @date 2024/1/25 10:52
* @description 处理文件上传
*/
public class uploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("文件上传过来了");
}
}

encType = multipart/form-data表示提交的数据,以多段(每一个表单项就是一个数据段)的形式进行拼接,然后以二进制流的形式发送给服务器。

文件上传、HTTP协议的说明

在服务器端以流的方式接收传输过来的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.geo.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jdk.jshell.spi.ExecutionControl;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
* @author yuxiaohong
* @package com.geo.servlet
* @date 2024/1/25 10:52
* @description 处理文件上传
*/
public class uploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("文件上传过来了" + req.getInputStream());
// 因为是以流的方式上传过来的,所以需要以流的方式去接收
ServletInputStream inputStream = req.getInputStream();
byte[] buffer = new byte[1024];
int read = inputStream.read(buffer);
System.out.println(new String(buffer, 0, read));
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("文件上传过来了" + req.getParameter("username"));
}
}

commons-fileupload.jar 常用API介绍说明

commons-fileupload.jar需要依赖commons-io.jar这个包,所以两个都需要引入。

导入的包中,常用的类:

ServletFileUpload类,用于解析上传中的数据。

FileItem类:表示每一个表单项。

判断当前上传的数据格式是否是多段的形式:

boolean ServletFileUpload.isMultipartContent(HttpServletRequest request);

解析上传的数据:

public List< FileItem > parseRequest(HttpServletRequest request)

判断这个表单项,是普通的表单项,还是上传的文件类型。

true表示普通的表单项,false表示上传的文件类型:

boolean FileItem.isFormField()

获取表单项的name属性值:

String FileItem.getFieldName()

获取表单项的值:

String FileItem.getString()

获取上传的文件名:

String FileItem.getName()

将上传的文件写到参数file所指向的磁盘目录:

void FileItem.write(file)

文件上传功能的实现

表单填写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%= "Hello World!" %>
</h1>
<br/>
<a href="hello-servlet">Hello Servlet</a>
一定要设置enctype的属性,否则无法上传文件
<form method="post" action="hello-servlet" enctype="multipart/form-data">
<input type="text" name="name">
<input type="file" name="file">
<input type="submit" value="提交">
</form>
</body>
</html>

上传servlet的设计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package org.example.demo;

import com.sun.istack.internal.Nullable;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import java.io.*;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

有webservelt这个注解就不用特意去web.xml中配置servlet的信息了
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 先判断上传的数据是否是多段的数据
if (ServletFileUpload.isMultipartContent(req)) {
// 创建FileItemFactory工厂实现类
FileItemFactory fileItemFactory = new DiskFileItemFactory();
// 创建用于解析上传数据的工具类ServletFileUpload
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
// 将request的编码转为utf-8,防止上传的中文文件名乱码
req.setCharacterEncoding("utf-8");
// 调用用解析方法
try {
List<FileItem> list = servletFileUpload.parseRequest(req);
list.forEach(item -> {
if (item.isFormField()) {
// 普通表单项
System.out.println("表单项的name属性值是:" + item.getFieldName());
try {
System.out.println("上传的表单项名是:" + item.getString("GBK"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
} else {
// 上传的文件
System.out.println("表单项的name属性值是:" + item.getFieldName());
servletFileUpload.setHeaderEncoding("GBK");
System.out.println("上传的文件名是:" + item.getName());
try {
item.write(new File("d:\\" + item.getName()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
} catch (FileUploadException e) {
...
}
}
}
}

文件下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package org.example.demo;

import org.apache.commons.io.IOUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

@WebServlet(name = "download", value = "/downloadServlet")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.获取下载的文件名
String downloadFilename = "xiaoji.png";
// 2.读取要下载的文件内容
ServletContext servletContext = getServletContext();
// 获取要下载文件的类型
String mimeType = servletContext.getMimeType("/file/" + downloadFilename);
System.out.println("下载的文件的类型:" + mimeType);
// 4.在回传前,通过响应头告诉客户端返回的数据类型
resp.setContentType(mimeType);
// 目前,到这一步,执行之后将图片直接展示到页面上了,没有下载
// 5.还要告诉客户端收到的数据是用于下载使用
// attachment表示附件(下载使用)
// url编码是把汉字转换为%xx%xx的格式,防止下载中文名文件报500错误
resp.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(downloadFilename + "\"", "utf-8"));


InputStream inputStream = servletContext.getResourceAsStream("/file/" + downloadFilename);
byte[] bytes = new byte[1024];
// 获取响应的输出流
OutputStream outputStream = resp.getOutputStream();
// 读取输入流中的全部数据,复制给输出流输出给客户端

// 3.把下载的文件内容回传给客户端
IOUtils.copy(inputStream, outputStream);
}
}

书城项目第三阶段

页面jsp动态化

  1. 在html页面添加page指令。
  2. 修改文件后缀名为:.jsp。
  3. 使用IDEA快捷键(ctrl+shift+r)查找替换,将.html全部替换为.jsp。

抽取jsp页面公共部分

将所有页面内容相同的代码块抽取出来,放到一个单独的jsp文件中,然后在对应位置使用静态引入该jsp页面。

动态的BASE标签值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%--
Created by IntelliJ IDEA.
User: YuHong
Date: 2024/4/26
Time: 下午4:19
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme()
+ "://" + request.getServerName()
+ ":" + request.getServerPort()
+ request.getContextPath()
+ "/";
%>
<%
System.out.println(basePath);
%>
<!-- 写base标签,永远固定相对路径跳转的结果-->
<base href="<%=basePath%>">
<link type="text/css" rel="stylesheet" href="static/css/style.css">
<script type="text/javascript" src="static/script/jquery.js"></script>

表单提交失败的错误回显

先在处理业务的servlet中,将错误信息存入request域中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.geo.web;

import com.geo.pojo.User;
import com.geo.service.UserService;
import com.geo.service.impl.UserServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
* @author yuxiaohong
* @package com.geo.web
* @date 2024/1/23 15:04
* @description 处理用户的登录业务
*/
public class LoginServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//调用userService.login()处理登录业务
User login = userService.login(new User(null, username, password, null));
if (login == null) {
//说明登录失败
// 把错误信息和回显的表单项信息,保存到request域中
req.setAttribute("msg", "用户名或密码错误!");
req.setAttribute("username", username);
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
} else {
//说明登录成功,跳转到登录成功页面
req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
}
}
}

然后在对应的业务提交表单页面插入表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>尚硅谷会员登录页面</title>
<%-- 静态包含,base标签,css样式,jquery--%>
<%@include file="/pages/common/head.jsp" %>
</head>
<body>
<div id="login_header">
<img class="logo_img" alt="" src="static/img/logo.gif">
</div>

<div class="login_banner">

<div id="l_content">
<span class="login_word">欢迎登录</span>
</div>

<div id="content">
<div class="login_form">
<div class="login_box">
<div class="tit">
<h1>尚硅谷会员</h1>
<a href="regist.jsp">立即注册</a>
</div>
<div class="msg_cont">
<b></b>
//错误回显
<span class="errorMsg"><%=request.getAttribute("msg") == null ? "请输入用户名和密码" : request.getAttribute("msg")%></span>
</div>
<div class="form">
<form action="userServlet" , method="post">
<input type="hidden" name="action" value="login"/>
<label>用户名称:</label>
<input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1"
name="username"
<%-- 此value属性用于用户登录失败之后不用重新填写账号--%>
value="<%=request.getAttribute("username")==null?"":request.getAttribute("username")%>"/>
<br/>
<br/>
<label>用户密码:</label>
<input class="itxt" type="password" placeholder="请输入密码" autocomplete="off" tabindex="1"
name="password"/>
<br/>
<br/>
<input type="submit" value="登录" id="sub_btn"/>
</form>
</div>

</div>
</div>
</div>
</div>
<%@include file="/pages/common/foot.jsp" %>
</body>
</html>

代码优化一:合并LoginServlet和RegistServlet为UserServlet

首先在表单提交处中多添加一个hidden属性的input标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form action="userServlet" , method="post">
<input type="hidden" name="action" value="login"/>
<label>用户名称:</label>
<input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1"
name="username"
<%-- 此value属性用于用户登录失败之后不用重新填写账号--%>
value="<%=request.getAttribute("username")==null?"":request.getAttribute("username")%>"/>
<br/>
<br/>
<label>用户密码:</label>
<input class="itxt" type="password" placeholder="请输入密码" autocomplete="off" tabindex="1"
name="password"/>
<br/>
<br/>
<input type="submit" value="登录" id="sub_btn"/>
</form>

然后在servlet中进行判断是登录业务还是注册业务,再分别进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.geo.web;

import com.geo.pojo.User;
import com.geo.service.UserService;
import com.geo.service.impl.UserServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "userServlet", value = "/userServlet")
public class UserServlet extends HttpServlet {
UserService userService = new UserServiceImpl();

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
System.out.println(action);
if (action.equals("login")) {
login(req, resp);
} else if (action.equals("regist")) {
regist(req, resp);
}
}

public void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理登录的需求");
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//调用userService.login()处理登录业务
User login = userService.login(new User(null, username, password, null));
if (login == null) {
//说明登录失败
// 把错误信息和回显的表单项信息,保存到request域中
req.setAttribute("msg", "用户名或密码错误!");
req.setAttribute("username", username);
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
} else {
//说明登录成功,跳转到登录成功页面
req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
}
}

public void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理注册的需求");
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");

// 2.检查验证码是否正确,写死,要求:abcde
if ("abcde".equalsIgnoreCase(code)) {
// 检查用户名是否可用
if (userService.existsUsername(username)) {
req.setAttribute("msg", "用户名已存在!");
req.setAttribute("email", email);
// 返回true说明用户名已存在
System.out.println("用户名" + username + "已存在");
// 跳回注册页面
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
} else {
// 表示此用户名可用,调用UserService方法存放到数据库当中
userService.registerUser(new User(null, username, password, email));
// 跳转到注册成功的页面
req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
}
} else {
// 回显信息,存放到request域中
req.setAttribute("msg", "验证码错误!");
req.setAttribute("username", username);
req.setAttribute("email", email);
System.out.println("验证码错误,验证码是" + code);
// 验证码错误,跳回注册页面
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
}
}
}

代码优化二:使用反射优化业务逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.geo.web;

import com.geo.pojo.User;
import com.geo.service.UserService;
import com.geo.service.impl.UserServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.lang.reflect.Method;

@WebServlet(name = "userServlet", value = "/userServlet")
public class UserServlet extends HttpServlet {
UserService userService = new UserServiceImpl();

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");

try {
// 参数1:指明获取的方法名。参数2:指明获取的方法的形参列表
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}

public void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理登录的需求");
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//调用userService.login()处理登录业务
User login = userService.login(new User(null, username, password, null));
if (login == null) {
//说明登录失败
// 把错误信息和回显的表单项信息,保存到request域中
req.setAttribute("msg", "用户名或密码错误!");
req.setAttribute("username", username);
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
} else {
//说明登录成功,跳转到登录成功页面
req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
}
}

public void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理注册的需求");
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");

// 2.检查验证码是否正确,写死,要求:abcde
if ("abcde".equalsIgnoreCase(code)) {
// 检查用户名是否可用
if (userService.existsUsername(username)) {
req.setAttribute("msg", "用户名已存在!");
req.setAttribute("email", email);
// 返回true说明用户名已存在
System.out.println("用户名" + username + "已存在");
// 跳回注册页面
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
} else {
// 表示此用户名可用,调用UserService方法存放到数据库当中
userService.registerUser(new User(null, username, password, email));
// 跳转到注册成功的页面
req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
}
} else {
// 回显信息,存放到request域中
req.setAttribute("msg", "验证码错误!");
req.setAttribute("username", username);
req.setAttribute("email", email);
System.out.println("验证码错误,验证码是" + code);
// 验证码错误,跳回注册页面
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
}
}
}

代码优化三:抽取模块业务处理doPost请求,继承自BaseServlet

BaseServlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.geo.web;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.lang.reflect.Method;

public abstract class BaseServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");

try {
// 参数1:指明获取的方法名。参数2:指明获取的方法的形参列表
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}

其他例如UserServlet程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.geo.web;

import com.geo.pojo.User;
import com.geo.service.UserService;
import com.geo.service.impl.UserServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.lang.reflect.Method;

@WebServlet(name = "userServlet", value = "/userServlet")
public class UserServlet extends BaseServlet {
UserService userService = new UserServiceImpl();
//直接把dopost方法删除就可以了,直接使用继承自BaseServlet程序的doPost方法。

public void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理登录的需求");
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//调用userService.login()处理登录业务
User login = userService.login(new User(null, username, password, null));
if (login == null) {
//说明登录失败
// 把错误信息和回显的表单项信息,保存到request域中
req.setAttribute("msg", "用户名或密码错误!");
req.setAttribute("username", username);
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
} else {
//说明登录成功,跳转到登录成功页面
req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
}
}

public void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理注册的需求");
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");

// 2.检查验证码是否正确,写死,要求:abcde
if ("abcde".equalsIgnoreCase(code)) {
// 检查用户名是否可用
if (userService.existsUsername(username)) {
req.setAttribute("msg", "用户名已存在!");
req.setAttribute("email", email);
// 返回true说明用户名已存在
System.out.println("用户名" + username + "已存在");
// 跳回注册页面
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
} else {
// 表示此用户名可用,调用UserService方法存放到数据库当中
userService.registerUser(new User(null, username, password, email));
// 跳转到注册成功的页面
req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
}
} else {
// 回显信息,存放到request域中
req.setAttribute("msg", "验证码错误!");
req.setAttribute("username", username);
req.setAttribute("email", email);
System.out.println("验证码错误,验证码是" + code);
// 验证码错误,跳回注册页面
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
}
}
}

数据的封装和抽取以及BeanUtils的使用(由于使用JakartaEE,无法实现此步骤)

BeanUtils工具类可以一次性把所有请求的参数注入到JavaBean中。

首先去Maven仓库中下载commons-beanutils.jar和commons-logging.jar,然后导入到lib文件夹中。

书城项目第四阶段:使用EL表达式修改回显值

修改login.jsp中相应的部分:

1
2
3
4
 <span class="errorMsg">
<%--<%=request.getAttribute("msg") == null ? "请输入用户名和密码" : request.getAttribute("msg")%>--%>
${empty requestScope.msg ? "请输入用户名和密码" : requestScope.msg}
</span>

书城项目第五阶段

MVC

MVC全称:Model 模型、View 视图、Controller 控制器。

View视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作。

Controller控制器:只负责接受请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者“角色。

Model模型:将与业务逻辑相关的数据封装为具体的JavaBean类,其中不掺杂任何与数据处理相关的代码。

MVC的理念是将软件代码拆分成为组件,单独开发,组合使用(目的还是为了解耦)。

图书模块

编写图书模块的数据库表

1
2
3
4
5
6
7
8
9
create table t_book(
`id` int PRIMARY key auto_increment,
`name` VARCHAR(100),
`price` DECIMAL(11,2),
`author` VARCHAR(100),
`salse` INT,
`stock` INT,
`img_path` VARCHAR(200)
)

编写图书模块的JavaBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.geo.pojo;

import java.math.BigDecimal;

public class Book {
private Integer id;
private String name;
private String author;
private BigDecimal price;
private Integer sales;
private Integer stock;
private String imgPath = "static/img/default.jpg";

public Book() {
}

public Book(Integer id, String name, String author, BigDecimal price, Integer sales, Integer stock, String imgPath) {
this.id = id;
this.name = name;
this.author = author;
this.price = price;
this.sales = sales;
this.stock = stock;
if (imgPath != null && !imgPath.equals("")) {
this.imgPath = imgPath;
}
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public BigDecimal getPrice() {
return price;
}

public void setPrice(BigDecimal price) {
this.price = price;
}

public Integer getSales() {
return sales;
}

public void setSales(Integer sales) {
this.sales = sales;
}

public Integer getStock() {
return stock;
}

public void setStock(Integer stock) {
this.stock = stock;
}

public String getImgPath() {
return imgPath;
}

public void setImgPath(String imgPath) {
this.imgPath = imgPath;
}

@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", sales=" + sales +
", stock=" + stock +
", imgPath='" + imgPath + '\'' +
'}';
}
}

编写图书模块的DAO和测试

DAO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.geo.dao;

import com.geo.pojo.Book;

import java.util.List;

public interface BookDao {
public int addBook(Book book);

public int deleteBookById(Integer id);

public int updateBook(Book book);

public Book queryBookById(Integer id);

public List<Book> queryAllBook();
}

DAO实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.geo.dao.impl;

import com.geo.dao.BookDao;
import com.geo.pojo.Book;

import java.util.List;

public class BookDaoImpl extends BaseDao implements BookDao {
@Override
public int addBook(Book book) {
String sql = "insert into t_book(`name`,`author`,`price`,`sales`,`stock`,`image_path`) values(?,?,?,?,?,?)";
return update(sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath());
}

@Override
public int deleteBookById(Integer id) {
String sql = "delete from t_book where id = ?";
return update(sql, id);
}

@Override
public int updateBook(Book book) {
String sql = "update t_book set `name`=?,`author`=?,`price`=?,`sales`=?,`stock`=?,`image_path`=?";
return update(sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath());
}

@Override
public Book queryBookById(Integer id) {
String sql = "select `name`,`author`,`price`,`sales`,`stock`,`image_path` imgPath from t_book where id = ?";
return queryForOne(Book.class, sql, id);
}

@Override
public List<Book> queryAllBook() {
String sql = "select `name`,`author`,`price`,`sales`,`stock`,`image_path` imgPath from t_book";
return queryForList(Book.class, sql);
}
}

使用IDEA生成测试类

首先选中DAO文件,然后按下CTRL+SHIFT+T,点击创建新测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.geo.test;

import com.geo.dao.BookDao;
import com.geo.dao.impl.BookDaoImpl;
import com.geo.pojo.Book;
import org.junit.Test;

import java.math.BigDecimal;

import static org.junit.Assert.*;

public class BookDaoTest {
private BookDao bookDao = new BookDaoImpl();

@Test
public void addBook() {
bookDao.addBook(new Book(null, "海底两万里", "李晓杰", new BigDecimal(23), 400, 20, null));
}

@Test
public void deleteBookById() {
bookDao.deleteBookById(1);
}

@Test
public void updateBook() {
bookDao.updateBook(new Book(2, "海底两万里", "李晓杰", new BigDecimal(23), 800, 20, null), 2);
}

@Test
public void queryBookById() {
System.out.println(bookDao.queryBookById(3));
}

@Test
public void queryAllBook() {
System.out.println(bookDao.queryAllBook());
}
}

编写图书模块的Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.geo.service.impl;

import com.geo.dao.BookDao;
import com.geo.dao.impl.BookDaoImpl;
import com.geo.pojo.Book;
import com.geo.service.BookService;

import java.util.List;

public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();

@Override
public void addBook(Book book) {
bookDao.addBook(book);
}

@Override
public void deleteBook(Integer id) {
bookDao.deleteBookById(id);
}

@Override
public void updateBook(Book book, Integer id) {
bookDao.updateBook(book, id);
}

@Override
public Book queryBook(Integer id) {
return bookDao.queryBookById(id);
}

@Override
public List<Book> queryBooks() {
return bookDao.queryAllBook();
}
}

图书列表功能的实现

因为要使用到JSTL,所以需要导包jakarta.servlet.jsp.jstl-2.0.0.jar、jakarta.servlet.jsp.jstl-api-2.0.0.jar(Tomcat10.0)

BookServlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.geo.web;

import com.geo.pojo.Book;
import com.geo.service.BookService;
import com.geo.service.impl.BookServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.List;

@WebServlet(name = "bookServlet", value = "/manager/bookServlet")
public class BookServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();

protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

}

protected void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}

protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}

protected void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.通过BookService查询全部图书
List<Book> books = bookService.queryBooks();
// 2.把全部图书保存到request域中
req.setAttribute("books", books);
// 3。请求转发到/pages/manager/book_manager.jsp页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req, resp);
}
}

因为将图书的数据存在了request域中,所以使用JSTL循环遍历,将图书的信息循环显示在页面上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
<%-- 静态包含,base标签,css样式,jquery--%>
<%@include file="/pages/common/head.jsp" %>
</head>
<body>

<div id="header">
<img class="logo_img" alt="" src="../../static/img/logo.gif">
<span class="wel_word">图书管理系统</span>
<%-- 静态包含manager--%>
<%@include file="/pages/common/manager_menu.jsp" %>
</div>

<div id="main">
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<c:forEach items="${requestScope.books}" var="book">
<tr>
<td>${book.name}</td>
<td>${book.price}</td>
<td>${book.author}</td>
<td>${book.sales}</td>
<td>${book.stock}</td>
<td colspan="2">操作</td>
</tr>
</c:forEach>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><a href="book_edit.jsp">添加图书</a></td>
</tr>
</table>
</div>

<%@include file="/pages/common/foot.jsp" %>
</body>
</html>

然后在点击图书管理跳转的标签的链接进行改动:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%--
Created by IntelliJ IDEA.
User: YuHong
Date: 2024/4/26
Time: 下午4:26
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div>
<a href="manager/bookServlet?action=list">图书管理</a>
<a href="/pages/manager/order_manager.jsp">订单管理</a>
<a href=../index.jsp">返回商城</a>
</div>

action后的参数意味着跳转过去之后调用list方法,但因为点击a标签是get请求,所以需要在baseServlet中添加一个get操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.geo.web;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.lang.reflect.Method;

public abstract class BaseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");

try {
// 参数1:指明获取的方法名。参数2:指明获取的方法的形参列表
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}

只需要在doget方法中调用dopost就可以了。

所有功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.geo.web;

import com.geo.pojo.Book;
import com.geo.service.BookService;
import com.geo.service.impl.BookServiceImpl;
import com.oracle.wls.shaded.org.apache.bcel.generic.SWAP;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.BeanUtils;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.List;

@WebServlet(name = "bookServlet", value = "/manager/bookServlet")
public class BookServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();

protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, InvocationTargetException, IllegalAccessException {
// 获取请求的参数,封装为book对象
String name = req.getParameter("name");
String author = req.getParameter("author");
BigDecimal price = new BigDecimal(req.getParameter("price"));
Integer sales = Integer.valueOf(req.getParameter("sales"));
Integer stock = Integer.valueOf(req.getParameter("stock"));
Book book = new Book(null, name, author, price, sales, stock, null);
// 调用BookService.addBook保存图书
bookService.addBook(book);
// 跳转到图书列表页面
// req.getRequestDispatcher("/manager/bookServlet?action=list").forward(req, resp);
// 使用请求转发会导致重复提交的bug,所以使用重定向,但是重定向是路径是基于端口号的,所以前面要加上工程名
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list");
}

protected void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的图书id
String id = req.getParameter("id");
// 调用BookService.delete方法,删除图书
bookService.deleteBook(Integer.valueOf(id));
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list");
}

protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter("id");
// 获取请求的参数,封装为book对象
String name = req.getParameter("name");
String author = req.getParameter("author");
BigDecimal price = new BigDecimal(req.getParameter("price"));
Integer sales = Integer.valueOf(req.getParameter("sales"));
Integer stock = Integer.valueOf(req.getParameter("stock"));
Book book = new Book(null, name, author, price, sales, stock, null);
bookService.updateBook(book, Integer.valueOf(id));
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list");
}

protected void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.通过BookService查询全部图书
List<Book> books = bookService.queryBooks();
// 2.把全部图书保存到request域中
req.setAttribute("books", books);
// 3。请求转发到/pages/manager/book_manager.jsp页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req, resp);
}

// 获取图书信息,用于修改图书页面的图书信息显示
protected void getBook(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int id = Integer.parseInt(req.getParameter("id"));
Book book = bookService.queryBook(id);
req.setAttribute("book", book);
req.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(req, resp);
}
}

相应jsp页面修改:

bookManager.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
<%-- 静态包含,base标签,css样式,jquery--%>
<%@include file="/pages/common/head.jsp" %>
<script>
$(function () {
$("a.deleteBook").click(function () {
// 确定就是true,继续向下执行,取消就是false,终止执行
return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() + "】吗?");
})
});
</script>
</head>
<body>

<div id="header">
<img class="logo_img" alt="" src="../../static/img/logo.gif">
<span class="wel_word">图书管理系统</span>
<%-- 静态包含manager--%>
<%@include file="/pages/common/manager_menu.jsp" %>
</div>

<div id="main">
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<c:forEach items="${requestScope.books}" var="book">
<tr>
<td>${book.name}</td>
<td>${book.price}</td>
<td>${book.author}</td>
<td>${book.sales}</td>
<td>${book.stock}</td>
跳转链接附带上图书的参数,方便于更新或删除
<td><a href="manager/bookServlet?action=getBook&id=${book.id}&method=update">修改</a></td>
<td><a class="deleteBook" href="manager/bookServlet?action=delete&id=${book.id}&method=add">删除</a>
</td>
</tr>
</c:forEach>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><a href="pages/manager/book_edit.jsp">添加图书</a></td>
</tr>
</table>
</div>

<%@include file="/pages/common/foot.jsp" %>
</body>
</html>

book_edit.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>编辑图书</title>
<%-- 静态包含,base标签,css样式,jquery--%>
<%@include file="/pages/common/head.jsp" %>
<style type="text/css">
h1 {
text-align: center;
margin-top: 200px;
}

h1 a {
color: red;
}

input {
text-align: center;
}
</style>
</head>
<body>
<div id="header">
<img class="logo_img" alt="" src="../../static/img/logo.gif">
<span class="wel_word">编辑图书</span>
<%-- 静态包含manager--%>
<%@include file="/pages/common/manager_menu.jsp" %>
</div>

<div id="main">
<form action="manager/bookServlet" method="get">
隐藏域的值通过传参的参数进行决定调用servlet中的哪个方法
<input type="hidden" name="action" value="${param.method}">
这个隐藏域用于传递id,方便修改图书时通过id查找到对应的图书
<input type="hidden" name="id" value="${param.id}">
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<tr>
<td><input name="name" type="text" value="${requestScope.book.name}"/></td>
<td><input name="price" type="text" value="${requestScope.book.price}"/></td>
<td><input name="author" type="text" value="${requestScope.book.author}"/></td>
<td><input name="sales" type="text" value="${requestScope.book.sales}"/></td>
<td><input name="stock" type="text" value="${requestScope.book.stock}"/></td>
<td><input type="submit" value="提交"/></td>
</tr>
</table>
</form>


</div>

<%@include file="/pages/common/foot.jsp" %>
</body>
</html>

图书的分页处理

整体概述:

先创建分页模型Page对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.geo.pojo;

import java.util.List;

public class Page<T> {
public static final Integer PAGE_SIZE = 4;
// 当前页码
private Integer pageNo;
// 当前页显示数量
private Integer pageSize = PAGE_SIZE;
// 总页数
private Integer pageTotal;
// 总记录数
private Integer pageTotalCount;
// 当前页数据
private List<T> items;

public Integer getPageNo() {
return pageNo;
}

public void setPageNo(Integer pageNo) {
this.pageNo = pageNo;
}

public Integer getPageTotal() {
return pageTotal;
}

public void setPageTotal(Integer pageTotal) {
this.pageTotal = pageTotal;
}

public List<T> getItems() {
return items;
}

public void setItems(List<T> items) {
this.items = items;
}

public Integer getPageTotalCount() {
return pageTotalCount;
}

public void setPageTotalCount(Integer pageTotalCount) {
this.pageTotalCount = pageTotalCount;
}

@Override
public String toString() {
return "Page{" +
"pageNo=" + pageNo +
", pageSize=" + pageSize +
", pageTotal=" + pageTotal +
", pageTotalCount=" + pageTotalCount +
", items=" + items +
'}';
}
}

然后在BookServiceImpl中添加实现方法(BookService也要写对应方法体)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public Page<Book> page(int pageNo, int pageSize) {
Page<Book> page = new Page<Book>();
// 求总记录数
Integer pageTotalCount = bookDao.queryForPageTotalCount();
// 设置总记录数
page.setPageTotalCount(pageTotalCount);
// 求总页码
int pageTotal = pageTotalCount / pageSize;
if (pageTotalCount % pageSize > 0) {
pageTotal += 1;
}
// 设置总页码
page.setPageTotal(pageTotal);
// 数据边界的有效性检查
if (pageNo < 1) {
pageNo = 1;
}
if (pageNo > pageTotal) {
pageNo = pageTotal;
}
// 设置当前页码
page.setPageNo(pageNo);
// 求当前页数据的开始索引
int begin = (page.getPageNo() - 1) * pageSize;
// 求当前页数据
List<Book> items = bookDao.queryForPageItems(begin, pageSize);
// 设置当前页面数据
page.setItems(items);

return page;
}

增加分页条:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
<%-- 静态包含,base标签,css样式,jquery--%>
<%@include file="/pages/common/head.jsp" %>
<script>
$(function () {
$("a.deleteBook").click(function () {
// 确定就是true,继续向下执行,取消就是false,终止执行
return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() + "】吗?");
})
});
</script>
</head>
<body>

<div id="header">
<img class="logo_img" alt="" src="../../static/img/logo.gif">
<span class="wel_word">图书管理系统</span>
<%-- 静态包含manager--%>
<%@include file="/pages/common/manager_menu.jsp" %>
</div>

<div id="main">
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<c:forEach items="${requestScope.page.items}" var="book">
<tr>
<td>${book.name}</td>
<td>${book.price}</td>
<td>${book.author}</td>
<td>${book.sales}</td>
<td>${book.stock}</td>
<td><a href="manager/bookServlet?action=getBook&id=${book.id}&method=update">修改</a></td>
<td><a class="deleteBook" href="manager/bookServlet?action=delete&id=${book.id}&method=add">删除</a>
</td>
</tr>
</c:forEach>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><a href="pages/manager/book_edit.jsp">添加图书</a></td>
</tr>
</table>
<div id="page_nav">
<c:if test="${requestScope.page.pageNo>1}">
<a href="manager/bookServlet?action=page&pageNo=1">首页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">上一页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">${requestScope.page.pageNo-1}</a>
</c:if>

【${requestScope.page.pageNo}】

<c:if test="${requestScope.page.pageNo<requestScope.page.pageTotal}">
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">${requestScope.page.pageNo+1}</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">下一页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
到第<input value="${requestScope.page.pageNo}" name="pn"
id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
<script>
// 跳转到输入的指定页码
$(function () {
$("#searchPageBtn").click(function () {
let pageNo = $("#pn_input").val();
// 此处的basePath需要在head.jsp的代码中存入服务器地址
location.href = "${pageScope.basePath}manager/bookServlet?action=page&pageNo=" + pageNo;
})
})
</script>
</div>
</div>

<%@include file="/pages/common/foot.jsp" %>
</body>
</html>

存服务器地址的位置:

head.jsp

1
2
3
4
5
6
7
8
<%
String basePath = request.getScheme()
+ "://" + request.getServerName()
+ ":" + request.getServerPort()
+ request.getContextPath()
+ "/";
pageContext.setAttribute("basePath", basePath);
%>

对原本功能进行优化:

添加图书之后跳转到最后一页(新添加图书的那页)

删除图书之后跳转到当前页

首先在添加、删除、更新跳转链接处增加参数(总页数\当前页数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
<%-- 静态包含,base标签,css样式,jquery--%>
<%@include file="/pages/common/head.jsp" %>
<script>
$(function () {
$("a.deleteBook").click(function () {
// 确定就是true,继续向下执行,取消就是false,终止执行
return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() + "】吗?");
})
});
</script>
</head>
<body>

<div id="header">
<img class="logo_img" alt="" src="../../static/img/logo.gif">
<span class="wel_word">图书管理系统</span>
<%-- 静态包含manager--%>
<%@include file="/pages/common/manager_menu.jsp" %>
</div>

<div id="main">
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<c:forEach items="${requestScope.page.items}" var="book">
<tr>
<td>${book.name}</td>
<td>${book.price}</td>
<td>${book.author}</td>
<td>${book.sales}</td>
<td>${book.stock}</td>
<td>
<a href="manager/bookServlet?action=getBook&id=${book.id}&method=update&pageNo=${requestScope.page.pageNo}">修改</a>
</td>
<td><a class="deleteBook"
href="manager/bookServlet?action=delete&id=${book.id}&method=delete&pageNo=${requestScope.page.pageNo}">删除</a>
</td>
</tr>
</c:forEach>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><a href="pages/manager/book_edit.jsp?method=add&pageNo=${requestScope.page.pageTotal}">添加图书</a></td>
</tr>
</table>
<div id="page_nav">
<c:if test="${requestScope.page.pageNo>1}">
<a href="manager/bookServlet?action=page&pageNo=1">首页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">上一页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">${requestScope.page.pageNo-1}</a>
</c:if>

【${requestScope.page.pageNo}】

<c:if test="${requestScope.page.pageNo<requestScope.page.pageTotal}">
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">${requestScope.page.pageNo+1}</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">下一页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
到第<input value="${requestScope.page.pageNo}" name="pn"
id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
<script>
// 跳转到输入的指定页码
$(function () {
$("#searchPageBtn").click(function () {
let pageNo = $("#pn_input").val();
location.href = "${pageScope.basePath}manager/bookServlet?action=page&pageNo=" + pageNo;
})
})
</script>
</div>
</div>

<%@include file="/pages/common/foot.jsp" %>
</body>
</html>

然后在BookServlet中修改添加、删除、更新后的重定向页面(同时将所有的list方法均改为跳转到page方法)

为了防止增加图书之后溢出到下一页,但是跳转到原本的最后一页,可以在add方法中传过来的页数+1,这样就永远不会小于最后一页。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.geo.web;

import com.geo.pojo.Book;
import com.geo.pojo.Page;
import com.geo.service.BookService;
import com.geo.service.impl.BookServiceImpl;
import com.oracle.wls.shaded.org.apache.bcel.generic.SWAP;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.BeanUtils;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.List;

@WebServlet(name = "bookServlet", value = "/manager/bookServlet")
public class BookServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();

// 处理分页功能
protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, InvocationTargetException, IllegalAccessException {
// 1.获取请求的参数pageNo和pageSize
int pageNo = req.getParameter("pageNo") != null ? Integer.parseInt(req.getParameter("pageNo")) : 1;
int pageSize = req.getParameter("pageSize") != null ? Integer.parseInt(req.getParameter("pageSize")) : Page.PAGE_SIZE;
// 2.调用BookService.page方法转成page对象
Page<Book> page = bookService.page(pageNo, pageSize);
// 3.保存page对象到request域中
req.setAttribute("page", page);
// 4.请求转发到book_manager.jsp页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req, resp);
}


protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, InvocationTargetException, IllegalAccessException {
// 接受的页数加一,防止新加的溢出到下一页
int pageNo = Integer.parseInt(req.getParameter("pageNo")) + 1;
// 获取请求的参数,封装为book对象
String name = req.getParameter("name");
String author = req.getParameter("author");
BigDecimal price = new BigDecimal(req.getParameter("price"));
Integer sales = Integer.valueOf(req.getParameter("sales"));
Integer stock = Integer.valueOf(req.getParameter("stock"));
Book book = new Book(null, name, author, price, sales, stock, null);
// 调用BookService.addBook保存图书
bookService.addBook(book);
// 跳转到图书列表页面
// req.getRequestDispatcher("/manager/bookServlet?action=list").forward(req, resp);
// 使用请求转发会导致重复提交的bug,所以使用重定向,但是重定向是路径是基于端口号的,所以前面要加上工程名
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + pageNo);
}

protected void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的图书id
String id = req.getParameter("id");
// 调用BookService.delete方法,删除图书
bookService.deleteBook(Integer.valueOf(id));
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + req.getParameter("pageNo"));
}

protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter("id");
// 获取请求的参数,封装为book对象
String name = req.getParameter("name");
String author = req.getParameter("author");
BigDecimal price = new BigDecimal(req.getParameter("price"));
Integer sales = Integer.valueOf(req.getParameter("sales"));
Integer stock = Integer.valueOf(req.getParameter("stock"));
Book book = new Book(null, name, author, price, sales, stock, null);
bookService.updateBook(book, Integer.valueOf(id));
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + req.getParameter("pageNo"));
}

protected void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.通过BookService查询全部图书
List<Book> books = bookService.queryBooks();
// 2.把全部图书保存到request域中
req.setAttribute("books", books);
// 3。请求转发到/pages/manager/book_manager.jsp页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req, resp);
}

// 获取图书信息,用于修改图书页面的图书信息显示
protected void getBook(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int id = Integer.parseInt(req.getParameter("id"));
Book book = bookService.queryBook(id);
req.setAttribute("book", book);
req.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(req, resp);
}
}

首页价格区间筛选符合条件的图书

首先在接口BookService中添加对应方法:pageByPrice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.geo.service;

import com.geo.pojo.Book;
import com.geo.pojo.Page;

import java.util.List;

public interface BookService {
public void addBook(Book book);

public void deleteBook(Integer id);

public void updateBook(Book book, Integer id);

public Book queryBook(Integer id);

public List<Book> queryBooks();

public Page<Book> page(int pageNo, int pageSize);

Page<Book> pageByPrice(int pageNo, int pageSize, int min, int max);
}

然后在BookServiceImpl实现方法体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public Page<Book> pageByPrice(int pageNo, int pageSize, int min, int max) {
Page<Book> page = new Page<Book>();
// 求总记录数
Integer pageTotalCount = bookDao.queryForPageTotalCountByPrice(min, max);
// 设置总记录数
page.setPageTotalCount(pageTotalCount);
// 求总页码
int pageTotal = pageTotalCount / pageSize;
if (pageTotalCount % pageSize > 0) {
pageTotal += 1;
}
// 设置总页码
page.setPageTotal(pageTotal);
// 数据边界的有效性检查
if (pageNo < 1) {
pageNo = 1;
}
if (pageNo > pageTotal) {
pageNo = pageTotal;
}
// 设置当前页码
page.setPageNo(pageNo);
// 求当前页数据的开始索引
int begin = (page.getPageNo() - 1) * pageSize;
// 求当前页数据
List<Book> items = bookDao.queryForPageItemsByPrice(begin, pageSize, min, max);
// 设置当前页面数据
page.setItems(items);

return page;
}

在BookDaoImpl中实现数据库查询的操作

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public Integer queryForPageTotalCountByPrice(int min, int max) {
String sql = "select count(*) from t_book where `price` between ? and ?";
Number count = (Number) queryForSingleValue(sql, min, max);
return count.intValue();
}

@Override
public List<Book> queryForPageItemsByPrice(int begin, int pageSize, int min, int max) {
String sql = "select `id`,`name`,`author`,`price`,`sales`,`stock`,`image_path` imgPath from t_book where `price` between ? and ? limit ?,?";
return queryForList(Book.class, sql, min, max, begin, pageSize);
}

然后在点击搜索的按钮上绑定参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>书城首页</title>
<%-- 静态包含,base标签,css样式,jquery--%>
<%@include file="/pages/common/head.jsp" %>
</head>
<body>
<div id="header">
<img class="logo_img" alt="" src="static/img/logo.gif">
<span class="wel_word">网上书城</span>
<div>
<a href="pages/user/login.jsp">登录</a> |
<a href="pages/user/regist.jsp">注册</a> &nbsp;&nbsp;
<a href="pages/cart/cart.jsp">购物车</a>
<a href="pages/manager/manager.jsp">后台管理</a>
</div>
</div>
<div id="main">
<div id="book">
<div class="book_cond">
<form action="client/bookServlet" method="get">
<!--在隐藏域中添加参数-->
<input type="hidden" name="action" value="pageByPrice">
<!--param参数用于搜索完成之后对价格区间进行回显操作-->
价格:<input id="min" type="text" name="min" value="${param.min}"> 元 -
<input id="max" type="text" name="max" value="${param.max}"> 元
<input type="submit" value="查询"/>
</form>
</div>
<div style="text-align: center">
<span>您的购物车中有3件商品</span>
<div>
您刚刚将<span style="color: red">时间简史</span>加入到了购物车中
</div>
<c:forEach items="${requestScope.page.items}" var="book">
<div class="b_list">
<div class="img_div">
<img class="book_img" alt="" src="${book.imgPath}"/>
</div>
<div class="book_info">
<div class="book_name">
<span class="sp1">书名:</span>
<span class="sp2">${book.name}</span>
</div>
<div class="book_author">
<span class="sp1">作者:</span>
<span class="sp2">${book.author}</span>
</div>
<div class="book_price">
<span class="sp1">价格:</span>
<span class="sp2">¥ ${book.price}</span>
</div>
<div class="book_sales">
<span class="sp1">销量:</span>
<span class="sp2">${book.sales}</span>
</div>
<div class="book_amount">
<span class="sp1">库存:</span>
<span class="sp2">${book.stock}</span>
</div>
<div class="book_add">
<button>加入购物车</button>
</div>
</div>
</div>
</c:forEach>
</div>
<div id="page_nav" style="clear: both">

<c:if test="${requestScope.page.pageNo > 1}">
<a href="client/bookServlet?action=page&pageNo=1">首页</a>
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">上一页</a>
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">${requestScope.page.pageNo-1}</a>
</c:if>

【${requestScope.page.pageNo}】

<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">${requestScope.page.pageNo+1}</a>
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">下一页</a>
<a href="client/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
到第<input value="${requestScope.page.pageNo}" name="pn"
id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
<script>
// 跳转到输入的指定页码
$(function () {
$("#searchPageBtn").click(function () {
let pageNo = $("#pn_input").val();
location.href = "${pageScope.basePath}client/bookServlet?action=page&pageNo=" + pageNo;
})
})
</script>
</div>
</div>
</div>

<%@include file="/pages/common/foot.jsp" %>
</body>
</html>

Cookie

什么是Cookie

  1. Cookie是由服务器通知客户端保存键值对的一种技术。
  2. 客户端有了Cookie后,每次请求都发送给服务器。
  3. 每个Cookie的大小不能超过4kb。

如何创建Cookie

在CookieServlet程序中写好方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.geo.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "CookieServlet", value = "/cookieServlet")
public class CookieServlet extends BaseServlet {

protected void createCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.创建Cookie对象
Cookie cookie = new Cookie("key1", "value1");
// 2.通知客户端接收Cookie
resp.addCookie(cookie);
resp.getWriter().println("Cookie创建成功!");

}
}

创建好Cookie之后,可以在浏览器控制台中的Application选项中查看:

服务器如何获取Cookie

服务器获取客户端的Cookie只需要一行代码:req.getCookies():Cookie[],会返回一个Cookie对象数组。

如果要寻找一个特定名字的Cookie,则可以封装成为一个工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.geo.util;

import jakarta.servlet.http.Cookie;

public class CookieUtils {
/**
* @param name
* @param cookies
* @return
*/
public static Cookie findCookie(String name, Cookie[] cookies) {
if (name == null || cookies == null || cookies.length == 0) {
return null;
}
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
System.out.println("找到了需要的cookie");
return cookie;
}
}
return null;
}
}

如何修改Cookie值

方案一

  1. 先创建一个要修改的同名的Cookie对象。
  2. 在构造器,同时赋予新的Cookie值。
  3. 调用response.addCookie(Cookie);
1
2
3
4
5
6
7
protected void updateCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//方案1,先创建一个同名的Cookie
Cookie cookie = new Cookie("key1", "value2");
// 调用response.addCookie通知客户端保存修改
resp.addCookie(cookie);
System.out.println("key1的值已经修改了");
}

方案二

  1. 先查找需要修改的Cookie对象。
  2. 调用setValue方法赋予新的Cookie值。
  3. 调用response.addCookie通知客户端保存数据。
1
2
3
4
5
6
7
8
9
10
protected void updateCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 方案2:先查找需要修改的Cookie对象
Cookie cookie = CookieUtils.findCookie("key1", req.getCookies());
if (cookie != null) {
// 调用setValue方法赋予新的Cookie值
cookie.setValue("newValue");
// 调用response.addCookie通知客户端保存修改
resp.addCookie(cookie);
}
}

Cookie的生命控制

Cookie的生命控制指的是如何管理Cookie什么时候被销毁。

setMaxAge():正数表示在指定的秒数后过期,负值表示浏览器关闭Cookie就会被删除,0表示马上删除Cookie。默认值是-1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void life3600(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("default", "defaultLife");
cookie.setMaxAge(3600); //设置存活时间
resp.addCookie(cookie);
}

protected void defaultLife(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("default", "defaultLife");
cookie.setMaxAge(-1); //直到浏览器关闭销毁
resp.addCookie(cookie);
}

protected void deleteNow(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("default", "defaultLife");
cookie.setMaxAge(0); //立马销毁cookie
resp.addCookie(cookie);
}

Cookie有效路径Path的设置

举例:

CookieA: path=/工程路径

CookieB:path=/工程路径/abc

请求地址如下:http://ip:port/工程路径/a.html

CookieA发送

CookieB不发送

请求地址如下:http://ip:root/工程路径/abc/a.html

CookieA发送

CookieB发送

1
2
3
4
5
protected void testPath(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("default", "defaultLife");
cookie.setPath(req.getContextPath() + "/abc");
resp.addCookie(cookie);
}

Cookie练习-免输入用户名登录

首先创建一个用于获取输入的表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="http://localhost:8080/cs/loginServlet" method="get">
用户名:<input type="text" name="username" value="${cookie.username.value}"><br>
密码:<input type="password" name="password" value="${cookie.password.value}"><br>
<input type="submit" value="提交">
</form>
</body>
</html>

然后在LoginServlet中处理数据,发送Cookie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.geo.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
* @author YuHong
* #Description LoginServlet
* #Date: 2024/5/14 17:43
*/
@WebServlet(name = "LoginServlet", value = "/loginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
if (username.equals("admin") && password.equals("admin")) {
// 登录成功
Cookie cookie = new Cookie("username", username);
cookie.setMaxAge(7 * 60 * 60); //当前cookie一周内有效
Cookie cookie1 = new Cookie("password", password);
cookie1.setMaxAge(7 * 60 * 60); //当前cookie一周内有效
resp.addCookie(cookie);
resp.addCookie(cookie1);
System.out.println("登录成功");
} else {
// 登录失败
System.out.println("登录失败");

}
}
}

只要登录成功,而且不超时,下次就会自动填充账号和密码。

Session

什么是Session会话?

  1. Session就是一个接口(HttpSession)
  2. Session就是会话。它是用来维护客户端和服务器之间关联的一种技术。
  3. 每个客户端都有自己的一个Session会话。
  4. Session会话中经常用来保存用户登录的信息。

如何创建Session和获取

request.getSession()

第一次调用是创建Session会话,之后每次调用都是获取Session会话。

isNew()判断Session是不是刚创建出来的。

每个会话都有一个身份证号,也就是ID值,是唯一的。

getId()得到Session的id值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet(name = "SessionServlet", value = "/sessionServlet")
public class SessionServlet extends BaseServlet {
protected void createOrGetSession(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 创建Session会话对象
HttpSession session = req.getSession();
// 判断Session是不是新创建出来的
boolean aNew = session.isNew();
System.out.println(aNew);
// 获取Session会话对象的id
String id = session.getId();
System.out.println(id);
resp.getWriter().write("得到的Session的id是" + id + "<br>");
resp.getWriter().write("这个Session是否是新创建的" + aNew + "<br>");
}
}

Session域数据的存取

1
2
3
4
5
6
7
8
9
10
//    往Session域中存放数据
protected void setAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().setAttribute("key1", "value1");
}

// 往Session域中读取数据
protected void getAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object key1 = req.getSession().getAttribute("key1");
resp.getWriter().write("" + key1);
}

Session生命周期

值为正数的时候设置超时时长,值为负数的时候,永不超时。

public void setMaxIactiveInterval(int interval):设置Session的超时时间(以秒为单位),超过指定的时长,Session就会销毁。

public int getMaxIactiveInterval():获取Session的超时时间。默认时长为30分钟。

public void invalidate():让当前Session会话立即销毁。

1
2
3
4
protected void defaultLife(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int maxInactiveInterval = req.getSession().getMaxInactiveInterval();
resp.getWriter().write("Session会话的默认超时时长为" + maxInactiveInterval + "秒"); //1800秒
}

如果要在web工程中,将默认的超时时长为其他时长,可以在web.xml中进行配置,这样就变成了20分钟。如下:

1
2
3
<session-config>
<session-timeout>20</session-timeout>
</session-config>

如果要改动单独的Session会话,则使用上方的方法。

[!IMPORTANT]

Session超时的时长的意思是指,客户端两次请求服务端最大的时间间隔。

浏览器和Session之间关联的技术内幕

底层其实是由cookie技术来实现的。

项目第六阶段

登录-显示用户名

首先,用户在登录之后将 用户信息保存到Session域中。

在UserServlet处理login业务中,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理登录的需求");
// 1.获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//调用userService.login()处理登录业务
User login = userService.login(new User(null, username, password, null));
if (login == null) {
//说明登录失败
// 把错误信息和回显的表单项信息,保存到request域中
req.setAttribute("msg", "用户名或密码错误!");
req.setAttribute("username", username);
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
} else {
// 保存用户登录的信息到Session域中
req.getSession().setAttribute("user", login);
//说明登录成功,跳转到登录成功页面
req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
}
}

然后在页面显示的jsp中,将显示位置改为${sessionScope.user.username}。

同时在页头使用JSTL加上if判断,如果登录了就显示用户名,如果未登录就显示登录、注册两个链接。

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div>
<c:if test="${empty sessionScope.user}">
<%-- 如果用户未登录显示登录和注册--%>
<a href="pages/user/login.jsp">登录</a> |
<a href="pages/user/regist.jsp">注册</a> &nbsp;&nbsp;
</c:if>
<c:if test="${not empty sessionScope.user}">
<%-- 如果用户已经登录则显示用户名--%>
<span>欢迎<span class="um_span">${sessionScope.user.username}</span>光临尚硅谷书城</span>
</c:if>
<a href="pages/cart/cart.jsp">购物车</a>
<a href="pages/manager/manager.jsp">后台管理</a>
</div>

登出-注销用户

1、销毁Session中用户的登录信息,或者销毁session。

2、重定向到首页或者登录页面。

1
2
3
4
public void logOut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().invalidate();
resp.sendRedirect(req.getContextPath());
}

在jsp页面中进行相应的修改。

1
2
3
4
5
6
<div>
<span>欢迎<span class="um_span">${sessionScope.user.username}</span>光临尚硅谷书城</span>
<a href="pages/order/order.jsp">我的订单</a>
<a href="userServlet?action=logOut">注销</a>&nbsp;&nbsp;
<a href="index.jsp">返回</a>
</div>

表单重复提交之验证码

表单重复提交有三种情况:

  1. 提交完表单,服务器使用请求转发的方式进行页面的跳转。这个时候,用户按下F5功能键,就会发起最后一次请求,造成表单重复提交问题。解决办法:使用重定向而不是请求转发来进行跳转。
  2. 用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候用户以为提交失败,然后多点了几次提交,也会造成表单重复提交。
  3. 用户正常提交服务器,服务器也没有延迟,正常提交之后,用户回退浏览器,重新提交也在造成表单重复提交。

第一种情况可以使用重定向来解决问题。

后两种就只能使用验证码来解决。

验证码的底层原理

谷歌kaptcha图片验证码的使用

使用步骤:

  1. 导入谷歌验证码的jar包:Kaptcha.jar

  2. 在web.xml中配置用于生成验证码的servlet程序。

    1
    2
    3
    4
    5
    6
    7
    8
    <servlet>
    <servlet-name>KaptchaServlet</servlet-name>
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    <servlet-name>KaptchaServlet</servlet-name>
    <url-pattern>/kaptcha</url-pattern>
    </servlet-mapping>
  3. 在表单中使用img标签去显示验证码的图片并使用

    1
    2
    3
    4
    5
    6
    7
    <form>
    用户名:<input type="text"><br>
    密码:<input type="text"><br>
    验证码:<input type="text" name="code"><img src="http://localhost:8080/tmp/kaptcha">
    </form>


  4. 从Session域中获取验证码的值:KAPTCHA_SESSION_KEY

Filter过滤器

什么是过滤器

  1. Filter过滤器是JavaWeb三大组件之一:Servlet程序、Listener监听器、Filter过滤器。
  2. 过滤器是JavaEE的规范,也就是接口。
  3. 作用是拦截请求,过滤响应。

拦截请求常见的应用场景:权限检查、日记操作、事务管理。

Filter的初体验

要求:在工程下有一个admin目录,这个目录下的所有资源都必须用户登录之后才可以访问。

不使用Filter,可以在jsp文件中使用java代码控制:如果session域中没有用户的信息,就跳转到登录页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
Object user = session.getAttribute("user");
if (user == null) {
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
%>
我是a.jsp文件
</body>
</html>

使用过滤器来实现:

首先新建一个Filter类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.geo;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;
public class AdminFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
if (user == null) {
servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest, servletResponse);
} else {
// 让程序继续往下访问用户的目标资源
filterChain.doFilter(servletRequest, servletResponse);
}
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void destroy() {

}
}

一定要实现init和destroy方法。

然后去web.xml中配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--    filter标签用于配置一个filter过滤器-->
<filter>
<!-- 给filter起一个别名-->
<filter-name>AdminFilter</filter-name>
<!-- 配置filter的全类名-->
<filter-class>com.geo.AdminFilter</filter-class>
</filter>
<!-- 配置filter过滤器的拦截路径-->
<filter-mapping>
<!-- 表示当前的拦截路径给哪个filter使用-->
<filter-name>AdminFilter</filter-name>
<!-- 配置拦截路径,斜杠表示:http://ip:port/工程路径/ 映射到web目录-->
<!-- 表示web目录下的admin目录下的全部资源-->
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>

可能由于使用的JDK版本(JDK19)太高,导致每次都启动失败,所以使用了注解的方式注册Filter,成功启动。

先将xml中的信息删掉,然后在Filter类名上加上。

1
@WebFilter(urlPatterns = "/*")

其中的路径就是要进行权限判断的位置。

Filter的生命周期

Filter的生命周期包含以下方法:

  1. 构造器方法(web工程启动的时候执行)
  2. init初始化方法(web工程启动的时候执行)
  3. doFilter过滤方法(每次只要拦截到就会执行)
  4. destroy销毁方法(web共工程停止的时候就会执行)

FilterConfig类

FilterConfig类是filter过滤器的配置文件类。

Tomcat每次创建Filter的时候就会创建一个FilterConfig类,这里包含了Filter配置文件的配置信息。

FilterConfig类的作用是为了获取Filter过滤器的配置内容:

  1. 获取Filter的别名filter-name
  2. 获取在Filter中配置的init-param初始化参数
  3. 获取ServletContext对象

Filter的拦截路径

  1. 精确匹配

    1
    2
    <url-pattern>/target.jsp</url-pattern>
    以上路径必须在:http://ip:port/工程路径/target.jsp
  2. 目录匹配

    1
    <url-pattern>/admin/*</url-pattern>
  3. 类型匹配(不能斜杠开头)

    1
    <url-pattern>*.html</url-pattern>

Filter过滤器只关心地址是否匹配,而不关心请求的资源是否存在,所以即使资源不存在也会照样拦截。

JSON

什么是JSON?

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人的阅读和编写。同时也易于机器解析和生成。

JSON在JavaScript中的使用。

JSON的定义

json是由键值对组成,由{}包围,每个键由引号引起来,键和值之间使用冒号进行分割,多组键值对之间使用逗号进行分割。

JSON的访问

json本身是一个对象。

json中的key可以理解为对象中的一个属性。

json中的key访问就跟访问对象的属性一样:json对象.key

JSON的两个常用方法

json的存在有两种形式。

一种是对象的形式存在,称为json对象。

另一种是字符串的形式存在,称为json字符串。

JSON.stringify()把json对象转换为json字符串。

JSON.parse()把json字符串转换为json对象。

JavaBean和JSON的相互转换

首先导入包:gson.jar。

然后创建好一个JavaBean类。

然后创建好一个对象:

1
2
3
4
5
6
7
8
9
@Test
public void test() {
Person person = new Person(1, "ihi");
Gson gson = new Gson();
String json = gson.toJson(person);
System.out.println(json);
Person person1 = gson.fromJson(json, Person.class);
System.out.println(person1);
}

List和JSON的互转

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test2() {
List<Person> persons = new ArrayList<Person>();
persons.add(new Person(1, "ihi"));
persons.add(new Person(2, "ihi"));
Gson gson = new Gson();
String json = gson.toJson(persons);
System.out.println(json);
List<Person> persons1 = gson.fromJson(json, persons.getClass());
System.out.println(persons1);
}

Map和JSON的互转

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test3() {
Map<Integer, Person> persons = new HashMap<Integer, Person>();
persons.put(1, new Person(1, "ihi"));
persons.put(2, new Person(2, "ihi"));

Gson gson = new Gson();
String json = gson.toJson(persons);
System.out.println(json);
Map<Integer, Person> persons1 = gson.fromJson(json, persons.getClass());
System.out.println(persons1);
}

AJAX请求

什么是AJAX请求

AJAX即“Asynchronous JavaScript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。

AXAX是一种浏览器通过js异步发起请求。局部更新页面的技术。

原生AJAX请求

在html页面进行请求的发送:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="pragma" content="no-cache"/>
<meta http-equiv="cache-control" content="no-cache"/>
<meta http-equiv="Expires" content="0"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
// 在这里使用javaScript语言发起Ajax请求,访问服务器AjaxServlet中javaScriptAjax
function ajaxRequest() {
// 1、我们首先要创建XMLHttpRequest
var xmlhttprequest = new XMLHttpRequest();
// 2、调用open方法设置请求参数
xmlhttprequest.open("GET", "http://localhost:8080/json/ajaxServlet?action=javaScriptAjax", true);

// 4、在send方法前绑定onreadystatechange事件,处理请求完成后的操作。
xmlhttprequest.onreadystatechange = function () {
if (xmlhttprequest.readyState == 4 && xmlhttprequest.status == 200) {
// alert("收到服务器返回的数据:" + xmlhttprequest.responseText);
var jsonObj = JSON.parse(xmlhttprequest.responseText);
// 把响应的数据显示在页面上
document.getElementById("div01").innerHTML = "编号:" + jsonObj.id + " , 姓名:" + jsonObj.name;
}
}
// 3、调用send方法发送请求
xmlhttprequest.send();
// alert("我是最后一行的代码");
}
</script>
</head>
<body>
<!-- <a href="http://localhost:8080/16_json_ajax_i18n/ajaxServlet?action=javaScriptAjax">非Ajax</a>-->
<button onclick="ajaxRequest()">ajax request</button>
<button onclick="ajaxRequest()">ajax request</button>
<button onclick="ajaxRequest()">ajax request</button>
<button onclick="ajaxRequest()">ajax request</button>
<button onclick="ajaxRequest()">ajax request</button>
<div id="div01">
</div>
<table border="1">
<tr>
<td>1.1</td>
<td>1.2</td>
</tr>
<tr>
<td>2.1</td>
<td>2.2</td>
</tr>
</table>
</body>
</html>

服务端servlet返回请求的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.geo;

import com.google.gson.Gson;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "AjaxServlet", value = "/ajaxServlet")
public class AjaxServlet extends BaseServlet {
protected void javaScriptAjax(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Ajax请求过来了");
Person person = new Person(2, "小杰");
Gson gson = new Gson();
String json = gson.toJson(person);
resp.getWriter().write(json);
}
}

JQuery中的AJAX请求

$.ajax方法

参数 功能
url 表示请求的地址
type 表示请求的类型是GET还是POST
data 表示发送给服务器的数据
success 请求响应,响应的回调函数
dataType 响应的数据类型,text、xml、json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$(function () {
// ajax请求
$("#ajaxBtn").click(function () {
$.ajax({
url: "http://localhost:8080/json/ajaxServlet",
// data:"action=jQueryAjax",
data: {action: "JQueryAjax"},
type: "GET",
success: function (data) {
// alert("服务器返回的数据是:" + data);
// var jsonObj = JSON.parse(data);
$("#msg").html(" ajax 编号:" + data.id + " , 姓名:" + data.name);
},
dataType: "json"
});
});
)

$.get,这是一个简单的get请求功能,用于取代$.ajax。

参数 功能
url 请求的url地址
data 发送的数据
callback 成功的回调函数
type 返回的数据类型
1
2
3
4
5
6
7
// ajax--get请求
$("#getBtn").click(function () {

$.get("http://localhost:8080/16_json_ajax_i18n/ajaxServlet", "action=JQueryGet", function (data) {
$("#msg").html(" get 编号:" + data.id + " , 姓名:" + data.name);
}, "json");
});
1
2
3
4
5
6
7
// ajax--post请求
$("#postBtn").click(function () {
// post请求
$.post("http://localhost:8080/16_json_ajax_i18n/ajaxServlet", "action=JQueryPost", function (data) {
$("#msg").html(" post 编号:" + data.id + " , 姓名:" + data.name);
}, "json");
});

$.getJson

参数 功能
url 请求的url地址
data 发送给服务器的数据
callback 成功的回调函数
1
2
3
4
5
6
// ajax--getJson请求
$("#getJSONBtn").click(function () {
$.getJSON("http://localhost:8080/16_json_ajax_i18n/ajaxServlet", "action=jQueryGetJSON", function (data) {
$("#msg").html(" getJSON 编号:" + data.id + " , 姓名:" + data.name);
});
});

表单序列化serialize()

serialize()可以把表单中所有的的表单项的内容都获取到,并以name=value&name=value的形式进行拼接。

1
2
3
4
5
6
7
// ajax请求
$("#submit").click(function () {
// 把参数序列化
$.getJSON("http://localhost:8080/16_json_ajax_i18n/ajaxServlet", "action=jQuerySerialize&" + $("#form01").serialize(), function (data) {
$("#msg").html(" Serialize 编号:" + data.id + " , 姓名:" + data.name);
});
});

i18n国际化

.. ... ...