课程视频地址:https://www.bilibili.com/video/BV1AS4y177xJ?p=1

课程文档地址:https://heavy_code_industry.gitee.io/code_heavy_industry/pro001-javaweb/lecture/

编写静态页面

使用react脚手架搭建项目

1
create-react-app demo01

使用 Semi UI框架

1
yarn add @douyinfe/semi-ui

编写 App.js 组件内容

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/* eslint-disable no-useless-constructor */
import React, { useEffect } from 'react';
import { Table, Col, Row } from '@douyinfe/semi-ui';
import { IconMember } from '@douyinfe/semi-icons';
import { Button } from '@douyinfe/semi-ui';
import { Form, useFormApi } from '@douyinfe/semi-ui';
import "./App.css"

const { Column } = Table;


function App() {
// 设置表格初始值
const [data, setData] = React.useState([])
// 设置表单默认值
const [formval, setFormVal] = React.useState({})

// 页面加载时触发
useEffect(() => {
setData(() => {
return [
{
id: 1,
name: "苹果",
price: '20',
number: '5',
count: '100'
}, {
id: 2,
name: "香蕉",
price: '10',
number: '6',
count: '60'
},
]
})
}, [])


// 删除按钮
const DeleteBtn = (text, record, index) => {
return (
<Button onClick={() => deleteNowRow(record, index)} theme='solid' type='danger'>删除</Button>
);
};

// 删除方法
const deleteNowRow = (row, index) => {
setData(olddata => {
olddata.splice(index, 1)
return [...olddata]
})
}

// 表单验证
const syncValidate = (values) => {
const errors = {};
if (!values.name) {
errors.name = '请输入名称';
}
if (!values.price) {
errors.price = '请输入价格';
}
if (!values.number) {
errors.number = '请输入数量';
}
return errors;
}

// 表单下面的操作按钮
const ComponentUsingFormApi = () => {
// 获取formApi
const formApi = useFormApi();
// 添加数据
const addVal = () => {
// 添加前先添加表单验证
let error = syncValidate(formval)

// 验证通过后再加入
if (!error.name) {
// 计算小计
formval.count = +formval.price * +formval.number
// 往表中插入输入,要返回一个新的对象才能修改数据
setData(oldval => [...oldval, { id: oldval.length + 1, ...formval }])
// 添加成功后清空表单
resetVal()
}

}
// 重置数据
const resetVal = () => {
formApi.reset()
};
return (
<>
<Button theme='solid' htmlType="submit" type='secondary' onClick={addVal}>添加</Button>
<Button theme='solid' type='warning' onClick={resetVal} style={{ marginLeft: 8 }}>重置</Button>
</>
);
};


return (
<>
<Row>
<Col span={20} offset={2} className="app-title">
<IconMember />
<h3>商品管理系统</h3>
</Col>
</Row>

<Row>
<Col span={20} offset={2}>
<Table dataSource={data} pagination={false}>
<Column title="名称" dataIndex="name" key="name" />
<Column title="价格" dataIndex="price" key="price" />
<Column title="数量" dataIndex="number" key="number" />
<Column title="小计" dataIndex="count" key="count" />
<Column title="操作" dataIndex="operate" key="operate" render={DeleteBtn} />
</Table>
</Col>
</Row>

<Row style={{ marginTop: "15px" }}>
<Col span={20} offset={2}>
<Form initValues={formval} validateFields={syncValidate} onValueChange={value => setFormVal(() => value)} >
<Form.Input field='name' label='名称' style={{ width: 266 }} />
<Form.Input field='price' label='价格' style={{ width: 266 }} />
<Form.Input field='number' label='数量' style={{ width: 266 }} />
{/* 表单操作按钮 */}
<ComponentUsingFormApi />
</Form>
</Col>
</Row>
</>
);
}

export default App;

然后再 index.js 中引入 App.js

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';


ReactDOM.render(
<App />,
document.getElementById('root')
);

reportWebVitals();

启动项目

1
yarn start

效果展示

javaweb2.gif

Tomcat 安装

首先进入官网进行下载

https://tomcat.apache.org/

这里我选择 8 版本

微信截图_20220317100144.png

点击后页面往下滑,选择 zip 版本下载,选择压缩包下载不用安装就可使用

微信截图_20220317100301.png

下载后解压到文件夹中,注意解压的目录不能有中文和空格,解压内容如下

微信截图_20220317100412.png

接着点开bin目录,点击 startup.bat 运行即可

微信截图_20220317100518.png

微信截图_20220317100625.png

打开浏览器,输入 localhost:8080 查看,看到如下页面表示启动成功

微信截图_20220317100730.png

部署项目到Tomcat

打开 Tomcat 解压目录中的 webapps 文件夹

微信截图_20220317101414.png

打开 ROOT 文件夹

微信截图_20220317101506.png

保留 WEB-INF 文件夹,其余文件全部替换为我们要部署的前端项目文件

微信截图_20220317101648.png

重新运行 startup.bat,再次输入 localhost:8080

微信截图_20220317101736.png

下载 Tomcat9

由于我使用的 java jdk版本是 java17,所以继续使用 tomcat8 的话会有问题

在 idea 中使用 tomcat

首先新建 Java 项目,默认创建 Java 项目不包含 Web 工程,我们需要手动创建。在项目名称上右键选择 Add Framework Support

微信截图_20220317144529.png

选择 Web Applocation,之后点击OK

微信截图_20220317145719.png

点击OK后,项目会自动创建一个 WEB 文件夹

微信截图_20220317145830.png

然后在和 WEB-INF 平级新建一个 hello.html,同时删除 index.jsp

微信截图_20220317150036.png

接着点击右上角的 Add Configuration

微信截图_20220317150136.png

在弹出的框中点击左上角 + 号

微信截图_20220317150230.png

点开后往下滑选择 Tomcat Server 下面的 Local

微信截图_20220317150320.png

在打开的弹框中点击 Configure

微信截图_20220317150541.png

选择 tomcat9

微信截图_20220317150709.png

接着点击 Deployment

微信截图_20220317150835.png

然后再点回 Server

微信截图_20220317151136.png

配置完成后会有一个可用的tomcat服务,点击启动debug模式运行

微信截图_20220317151434.png

等待服务启动

微信截图_20220317151539.png

启动成功自动打开浏览器,发现出现 404 错误,这是因为我们创建的 html 文件名字是 hello.html,需要手动的在地址后面添加 hello.html 即可

微信截图_20220317151510.png

现在我们手动的在地址后面添加 hello.html 再次访问

微信截图_20220317151718.png

修改代码。刷新页面即可实时查看改动效果

javaweb3.gif

那么怎么样能自动打开就是加上 hello.html 后缀呢。点击右上角的编辑配置

微信截图_20220317151813.png

修改启动地址即可

微信截图_20220317151911.png

重新启动即可自动打开 hello.html

使用 HttpServlet 接收表单参数

编写 form 表单

首先修改 hello.html 代码,添加 from 表单提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="add">
名称:<input type="text" name="name"/> <br/>
价格:<input type="text" name="price"/> <br/>
数量:<input type="text" name="number"/> <br/>
<input type="submit" value="添加"/>
</form>
</body>
</html>

导入 tomcat 依赖

接着引入 HttpServlet 包,这个包不在 java的 jdk 中,需要额外引入,点击 File,选择 Project Structure

微信截图_20220317152558.png

如图

微信截图_20220317153058.png

点开 + 号,选择 Library

微信截图_20220317153159.png

选择 tomcat9 导入

微信截图_20220317153515.png

点击应用

微信截图_20220317153621.png

此时项目中就有 tomcat9 的依赖包

微信截图_20220317153707.png

编写接收方法

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.songzx.javaweb01;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @author songzx
* @create 2022-03-17 12:02
*/
public class addServer extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
String price = req.getParameter("price");
String number = req.getParameter("number");
System.out.println(name);
System.out.println(price);
System.out.println(number);
}
}

配置 xml 参数

微信截图_20220317155308.png

执行过程

  1. 用户发送请求,action = add
  2. 项目中 web.xml 中找到 url-pattern = /add,对应第12行
  3. 找到第11行的 servlet-name = addServer
  4. 找到和 servlet-mapping 中 servlet-name 一致的 servlet,对应第7行
  5. 然后找到第8行地址所对应的 addServer 类
  6. 接收用户发送过来的请求

效果演示

启动项目后点击表单提交,可能出现如下错误

微信截图_20220317161006.png

这时我们要检查环境变量 JAVA_HOME 对应的值是否是 java jdk 的低版本,我这里原来是 jdk8.0,修改为 jdk17

微信截图_20220317160846.png

然后修改 tomcat 配置,把 jre 同步修改为 jdk17 版本

微信截图_20220317161212.png

再次启动查看效果

javaweb4.gif

导入jdbc完成数据插入到数据库

首先在 web -> WEB-INF 文件夹下新建 lib 文件夹,依次导入如下 jar 包

  • commons-dbutils-1.7.jar (操作数据库的第三jar包)
  • druid-1.1.10.jar (Druid连接池)
  • mysql-connector-java-8.0.28.jar(MySQL数据库驱动)

微信截图_20220318133646.png

然后再项目上右键新建文件夹 resource,文件夹的名称固定为这个,然后再该文件夹上右键选择 Mark Directory –> Sources Root

微信截图_20220318133856.png

接着在此文件夹下新建配置文件 druid.properties,

微信截图_20220318134143.png

编写连接数据库的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JdbcUtils {
public static DataSource source = null;
static {
try {
InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
Properties prop = new Properties();
prop.load(is);
source = DruidDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection conn = source.getConnection();
return conn;
}

public static void closeConnection(Connection conn, Statement sta){
DbUtils.closeQuietly(conn);
DbUtils.closeQuietly(sta);
}
}

修改 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
public class exer01 extends HttpServlet {
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Connection conn = null;
try {
String name = req.getParameter("name");
String price = req.getParameter("price");
String number = req.getParameter("number");
// 转换数据格式
double aDouble = Double.parseDouble(price);
int anInt = Integer.parseInt(number);
// 获取连接
conn = JdbcUtils.getConnection();
// 编写SQL
String sql = "insert into commodity(name,price,number) values (?,?,?)";
// 执行SQL方法
QueryRunner runner = new QueryRunner();
int isOk = runner.update(conn, sql, name, aDouble, anInt);
// 打印结果
System.out.println(isOk > 0 ? "插入成功" : "插入失败");
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
}
}

重新启动,填写表单后点击提交

微信截图_20220318134829.png

查询数据库,数据成功进入到表中

微信截图_20220318134951.png

处理post接收中文乱码问题

如图,当表单填写中文后会出现乱码问题

微信截图_20220319154242.png

解决方法:在 doPost 方法首行添加如下代码

1
req.setCharacterEncoding("utf-8");

设置字符串的格式为 utf-8 的格式,再次发送中文信息

微信截图_20220319154435.png

微信截图_20220319154538.png

Server 的生命周期

  • init 初始化

  • service 服务中

  • destroy 销毁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class exer02 extends HttpServlet {
public exer02(){
System.out.println("创建实例...");
}

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

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("正在服务...");
}

@Override
public void destroy() {
System.out.println("实例销毁");
}
}

启动Tomcat服务器后,Tomcat会通过返回帮助我们创建类的实例对象,然后先调用 init 方法进行初始化,然后调用 service 方法判断请求方式,当关闭Tomcat服务器后调用 destory 方法销毁实例

微信截图_20220319165104.png

默认情况下,会在第一次请求时同时进行初始化和服务操作,第二次请求开始之后不会再次初始化。所以这种模式是单例的,但同时也是线程不安全的

再第一次请求时进行初始化的优缺点:

  • 优点:提高程序启动的速度
  • 缺点:第一次请求时效率低。可以通过修改 servlet 的初始化时机来提交第一次访问的效率

修改 Servlet 的初始化时机

在 web.xml 中添加 load-on-startup,这个值越小,实例创建的时机越早.最小不能低于0

Snipaste_2022-03-20_17-13-17.png

启动服务后,观察控制台输出

Snipaste_2022-03-20_17-14-56.png

当进行第一次访问时直接就是正在服务,从而不会再第一步请求时创建实例和初始化

Http的请求和响应

介绍

Http 被称为超文本传输协议

请求

请求包含下面三个部分

  • 请求行
    • 请求的方式
    • 请求的 URL
    • 请求的协议
  • 请求头
    • 浏览器版本号
    • 浏览器型号
    • 浏览器可以接收的数据类型
    • ……
  • 请求主体
    • get 方式:没有请求体,但是有一个 queryString
    • post方式:有请求体,form data
    • json格式,有请求体,request payload

响应

响应包含下面的三个信息

  • 响应行
    • 协议
    • 响应状态码
      • 200
      • 404
      • 500
    • 响应状态
  • 响应头
    • 服务器信息
    • 服务器发给浏览器的内容(内容媒体类型、编码、长度等)
  • 响应体
    • 响应的实际内容

Http无状态

服务器无法判断两次请求是否来自不同的浏览器或者来自同一个浏览器,这种情况称为Http无状态。可以通过回话跟踪来解决

会话跟踪

简单概述:当浏览器第一次访问服务器时,服务器会自动分配一个 session id 给这个请求。当这个请求第二次来访问时,会携带者服务器分配给自己的 session id。服务器根据这个 session id 来区分每一个请求

可以通过代码来演示

1
2
3
4
5
6
7
8
9
public class exer03 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 获取 session id
String id = session.getId();
System.out.println(id);
}
}

当浏览器进行第一次访问时,服务器会自动分配一个 session id

Snipaste_2022-03-20_18-09-14.png

当浏览器继续访问时,会将这个 session id 传给服务器

Snipaste_2022-03-20_18-10-54.png

常用的 API

1
2
3
4
5
6
7
8
9
10
// 获取会话创建时间
long creationTime = session.getCreationTime();
// 获取会话是否是新的
boolean aNew = session.isNew();
// 获取会话最长有效期
int maxInactiveInterval = session.getMaxInactiveInterval();
// 设置会话最长有效期
session.setMaxInactiveInterval(2000);
// 让会话立刻失效
session.invalidate();

会话保存

在同一个会话中,我们可以保存数据。

分别使用两个方法:

  • req.getSession().setAttribute
  • req.getSession().getAttribute

添加数据

1
2
3
4
5
6
7
public class exer04 extends HttpServlet {
// 往session中添加数据
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().setAttribute("uname","lina");
}
}

获取数据

1
2
3
4
5
6
7
8
public class exer05 extends HttpServlet {
// 读取session中的数据
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object uname = req.getSession().getAttribute("uname");
System.out.println(uname);
}
}

此时我们启动 Tomcat,依次访问 exer04 和 exer05

当访问 exer04 后系统会帮我们保存 uname 的值为 lina

然后访问 exer05

Snipaste_2022-03-23_21-48-02.png

成功获取到 uname 的值

然后换个浏览器访问 exer05

Snipaste_2022-03-23_21-51-00.png

由于会话不同,所以获取不到 uname 的值

内部转发和重定向

内部转发

当浏览器访问服务器时,服务器在内部转发这个请求给到另外一个服务器去处理。此时浏览器是不知道内部转发了多少次的。

首先编写exer06,在这个里面吧请求转发给 exer07

1
2
3
4
5
6
7
8
public class exer06 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("exer06.....");
// 内部转发给exer07,浏览器是不知道的
req.getRequestDispatcher("exer07").forward(req,resp);
}
}

exer07 里面只打印一语句话

1
2
3
4
5
6
public class exer07 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("exer07.....");
}
}

然后启动 Tomcat,访问exer06 观察结果。

结果可以看到 exer06 和 exer07 都会执行

Snipaste_2022-03-23_22-19-37.png

浏览器地址没有变化,并且只有一次请求

Snipaste_2022-03-23_22-22-07.png

浏览器重定向

使用浏览器重定向浏览器的地址会发生变化,并且会发送两次请求

使用 resp.sendRedirect("exer07"); 重定向

1
2
3
4
5
6
7
8
public class exer06 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("exer06.....");
// 客户端重定向到exer07
resp.sendRedirect("exer07");
}
}

在 exer07 打印信息

1
2
3
4
5
6
public class exer07 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("exer07.....");
}
}

启动Tomcat,访问exer06

Snipaste_2022-03-23_22-25-50.png

此时浏览器的地址栏会发生改变,并且发送两次请求

Snipaste_2022-03-23_22-27-49.png

thymeleaf 入门

导包

首先导入需要用到的包

  • attoparser-2.0.5.RELEASE.jar
  • javassist-3.20.0-GA.jar
  • ognl-3.1.26.jar
  • slf4j-api-1.7.25.jar
  • thymeleaf-3.0.14.RELEASE.jar
  • unbescape-1.1.6.RELEASE.jar

Snipaste_2022-03-30_13-38-20.png

然后再 lib_thymeleaf 文件夹右键,选择 add as library

Snipaste_2022-03-30_13-39-40.png

接着吧这个 lib 包添加到 module 中

Snipaste_2022-03-30_13-40-39.png

接着在 problems 中将所有内容添加到 web 中

Snipaste_2022-03-30_13-41-32.png

引入文件

直接复制下面的代码到 myssm.ViewBaseServlet

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
package com.songzx.myssm;

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @author songzx
* @create 2022-03-30 13:45
*/
public class ViewBaseServlet extends HttpServlet {
private TemplateEngine templateEngine;

@Override
public void init() throws ServletException {
// 1.获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);

}
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException, IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
}

修改 demo2.java 代码,继承 ViewBaseServlet 类

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.songzx.demo01;

import com.songzx.comm.Comm;
import com.songzx.dao.CommServer;
import com.songzx.myssm.ViewBaseServlet;
import com.songzx.utils.JdbcUtils;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
* @author songzx
* @create 2022-03-28 10:03
*/
@WebServlet("/index")
public class demo2 extends ViewBaseServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp){
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
CommServer commServer = new CommServer();
List<Comm> commList = commServer.getAllComm(conn);
HttpSession session = req.getSession();
session.setAttribute("commlist",commList);
super.processTemplate("index",req,resp);
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
JdbcUtils.closeConnection(conn,null);
}
}

配置 xml

配置一个前缀,一个后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<context-param>
<param-name>view-prefix</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>

</web-app>

编写 index.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>

启动 Tomcat,可以看到页面虽然访问的是 index,但实际看到的是 index.html 文件

Snipaste_2022-03-30_15-00-51.png

动态遍历页面数据

编写 Index.html 页面。

  • th:if="${#lists.isEmpty(session.commlist)}";判断session.commlist 是否为空
  • th:unless="${#lists.isEmpty(session.commlist)}";判断session.commlist 是否不为空
  • th:each="comm : ${session.commlist}";遍历session.commlist ,并且命名一个临时变量 comm
  • th:text="${comm.name}";设置标签的内容
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1" style="border-collapse:collapse">
<tr>
<th style="width: 200px">名称</th>
<th style="width: 200px">价格</th>
<th style="width: 200px">数量</th>
<th style="width: 200px">操作</th>
</tr>
<tr th:if="${#lists.isEmpty(session.commlist)}">
<td colspan="4">没有库存</td>
</tr>
<tr th:unless="${#lists.isEmpty(session.commlist)}" th:each="comm : ${session.commlist}">
<th th:text="${comm.name}"></th>
<th th:text="${comm.price}"></th>
<th th:text="${comm.number}"></th>
<th>删除</th>
</tr>
</table>
</body>
</html>

运行 tomcat。数据库中的数据在页面中被渲染出来

Snipaste_2022-03-30_16-53-42.png

Servlet 保存作用域

request 保存数据

只在这一次的响应中有效,使用浏览器重定向时会获取不到数据,例如:

定义 demo01 往 request 中保存数据

1
2
3
4
5
6
7
8
9
10
@WebServlet("/demo01")
public class demo1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 向 req 中保存数据
req.setAttribute("uname","lili");
// 客户端重定向
resp.sendRedirect("demo02");
}
}

定义 demo02 读取 request 中的值

1
2
3
4
5
6
7
8
9
@WebServlet("/demo02")
public class demo2 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求中保存的数据
Object uname = req.getAttribute("uname");
System.out.println("uname="+uname);
}
}

启动项目,控制台输出 null

Snipaste_2022-04-07_20-16-45.png

然后我们使用服务器内部转发,在另外一个接口中读取数据

定义 demo03

1
2
3
4
5
6
7
8
9
10
@WebServlet("/demo03")
public class demo03 extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 往当前响应中添加数据
request.setAttribute("uname","lili");
// 服务端内部转发
request.getRequestDispatcher("demo04").forward(request,response);
}
}

定义 demo04

1
2
3
4
5
6
7
8
@WebServlet("/demo04")
public class demo04 extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object uname = request.getAttribute("uname");
System.out.println("uname=" + uname);
}
}

启动服务,可以发现获取到的 uname 的值

Snipaste_2022-04-07_20-26-37.png

session 保存作用域

在 session 中保存的作用域在这一次的请求中都有效

定义 demo05

1
2
3
4
5
6
7
8
9
@WebServlet("/demo05")
public class demo05 extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 往 session 中保存数据
request.getSession().setAttribute("uname","lili");
response.sendRedirect("demo06");
}
}

定义 demo06

1
2
3
4
5
6
7
8
@WebServlet("/demo06")
public class demo06 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object uname = req.getSession().getAttribute("uname");
System.out.println("uname=" + uname);
}
}

启动

Snipaste_2022-04-07_20-35-10.png

context 保存数据

context 保存的数据在项目运行期间都有效

编写 demo07

1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet("/demo07")
public class demo07 extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取请求上下文
ServletContext context = request.getServletContext();
// 往上下文中保存数据
context.setAttribute("uname","lili");
// 浏览器重定向
response.sendRedirect("demo08");
}
}

编写 demo08

1
2
3
4
5
6
7
8
9
10
11
@WebServlet("/demo08")
public class demo08 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取上下文
ServletContext context = req.getServletContext();
// 从上下文中读取数据
Object uname = context.getAttribute("uname");
System.out.println(uname);
}
}

通过上下文,只要在服务运行期间,不管那个请求进来都可以获取到 uname 的值

使用 thymeleaf 完成增删改查案例

实现数据编辑功能

首先添加跳转并且传参

在 a 标签上添加 th:href="@{/edit.do(id=${comm.id})}"

语法:@{} 表示根目录,(id=${comm.id},xxxxx) 表示传递参数,需要传递多个时用逗号隔开

1
2
3
4
5
6
<tr th:unless="${#lists.isEmpty(session.commlist)}" th:each="comm : ${session.commlist}">
<td > <a th:href="@{/edit.do(id=${comm.id})}" th:text="${comm.name}">aa</a> </td>
<td th:text="${comm.price}"></td>
<td th:text="${comm.number}"></td>
<td>删除</td>
</tr>

添加 EditService ,在这个页面中通过 id 查询该数据,然后显示 edit 页面

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
@WebServlet("/edit.do")
public class EditService extends ViewBaseServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Connection conn = null;
try {
// 从路径中获取参数
String id = req.getParameter("id");
int anInt = Integer.parseInt(id);
CommServer server = new CommServer();
conn = JdbcUtils.getConnection();
// 根据id获取数据
Comm comm = server.getCommById(conn, anInt);
// 把数据保存在session中
req.getSession().setAttribute("commEdit",comm);
// 显示edit页面
super.processTemplate("edit",req,resp);
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
}
}

编写 edit.html

使用 th:object="${session.commEdit}" 读取在 session 中保存的 commEdit ,然后下面的都通过 th:value="*{id}" 方式显示数据

点击提交触发表单的 post 方法,请求接口 update.do

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<table border="1" style="border-collapse:collapse" th:object="${session.commEdit}">
<form method="post" action="update.do">
<input type="hidden" th:value="*{id}" name="id">
<tr>
<th style="width: 200px">名称</th>
<th><input th:value="*{name}" name="name"></th>
</tr>
<tr>
<th style="width: 200px">价格</th>
<th><input th:value="*{price}" name="price"></th>
</tr>
<tr>
<th style="width: 200px">数量</th>
<th><input th:value="*{number}" name="number"></th>
</tr>
<tr>
<th style="width: 200px" colspan="2">
<input type="submit" value="提交">
</th>
</tr>
</form>
</table>

添加 UpdateService,在这个方法中获取表单提交方法中传递过来的表单数据,然后调用 updateCommById 方法完成数据更新。更新完成后使用浏览器重定向到 index,完成数据的重新渲染

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
@WebServlet("/update.do")
public class UpdateService extends ViewBaseServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.设置编码类型
req.setCharacterEncoding("utf-8");
Connection conn = null;
try {
// 获取参数id
String id = req.getParameter("id");
int anInt = Integer.parseInt(id);
// 获取参数name
String name = req.getParameter("name");
// 获取参数price
String price = req.getParameter("price");
double aDouble = Double.parseDouble(price);
// 获取参数number
String number = req.getParameter("number");
int aNumber = Integer.parseInt(number);
// 获取连接
conn = JdbcUtils.getConnection();
// 封装comm对象
Comm comm = new Comm(anInt, name, aDouble, aNumber);
// 实例化调用接口类
CommServer server = new CommServer();
// 执行更新方法
server.updateCommById(conn,comm);
// 让浏览器重定向到index,重新渲染页面
resp.sendRedirect("index");

} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
}
}

效果展示

javaweb5.gif

数据删除

修改 index.html

使用 th:onclick="|deleteComm(${comm.id})|" 两个竖线表示内部可以写 ${} 符号,会自动识别里面的内容

1
2
3
4
5
6
<tr th:unless="${#lists.isEmpty(session.commlist)}" th:each="comm : ${session.commlist}">
<td > <a th:href="@{/edit.do(id=${comm.id})}" th:text="${comm.name}">aa</a> </td>
<td th:text="${comm.price}"></td>
<td th:text="${comm.number}"></td>
<td th:onclick="|deleteComm(${comm.id})|">删除</td>
</tr>

添加 js/index.js

1
2
3
4
5
function deleteComm(id){
if(confirm("确认删除吗")){
window.location.href = "delete.do?id=" + id;
}
}

添加 deleteCommById 删除方法

1
2
3
4
5
6
7
8
9
@Override
public void deleteCommById(Connection conn, int id) {
String sql = "delete from commodity where id = ?";
try {
super.execUpdate(conn,sql,id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}

新增 DeleteService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WebServlet("/delete.do")
public class DeleteService extends ViewBaseServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取参数删除带过来的参数id
String id = req.getParameter("id");
if(StringUtils.isNotEmpty(id)){
int aId = Integer.parseInt(id);
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
CommServer server = new CommServer();
// 执行删除方法
server.deleteCommById(conn,aId);
// 浏览器重定向到首页重新渲染
resp.sendRedirect("index");
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}

效果展示

javaweb6.gif

数据新增

在 index 中添加新增按钮

1
<button th:onclick="addComm()">添加水果</button>

实现 addComm 方法

1
2
3
function addComm(){
window.location.href = "add.do";
}

添加 AddService 在这个方法中展示 add.html 页面

1
2
3
4
5
6
7
@WebServlet("/add.do")
public class AddService extends ViewBaseServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.processTemplate("add",req,resp);
}
}

编写 add.html,点击提交触发 post 方法,调用 saveAdd.do 接口

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1" style="border-collapse:collapse">
<form method="post" action="saveAdd.do">
<tr>
<th style="width: 200px">名称</th>
<th><input name="name"></th>
</tr>
<tr>
<th style="width: 200px">价格</th>
<th><input name="price"></th>
</tr>
<tr>
<th style="width: 200px">数量</th>
<th><input name="number"></th>
</tr>
<tr>
<th style="width: 200px" colspan="2">
<input type="submit" value="提交">
</th>
</tr>
</form>
</table>
</body>
</html>

编写 SaveService

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
@WebServlet("/saveAdd.do")
public class SaveService extends ViewBaseServlet {
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置字符串编码
req.setCharacterEncoding("utf-8");
// 获取表单参数
String name = req.getParameter("name");
String price = req.getParameter("price");
double dprice = Double.parseDouble(price);
String number = req.getParameter("number");
int inumber = Integer.parseInt(number);
// 创建 comm 实例
Comm comm = new Comm(name, dprice, inumber);
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
CommServer server = new CommServer();
// 执行添加方法
server.addComm(conn,comm);
// 浏览器重定向到首页
resp.sendRedirect("index");
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
}
}

添加方法 addComm

1
2
3
4
5
6
7
8
9
@Override
public void addComm(Connection conn, Comm comm) {
String sql = "insert into commodity(name,price,number) values(?,?,?)";
try {
super.execUpdate(conn,sql,comm.getName(),comm.getPrice(),comm.getNumber());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}

效果展示

javaweb7.gif

数据分页显示

首先添加一个查询总数的方法,在 baseDAO 中添加如下方法

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 获取数据总和
* @author Songzx
* @date 2022/4/12
*/

public long execQuerySize(Connection conn,String sql) throws SQLException {
QueryRunner runner = new QueryRunner();
ScalarHandler<Long> scalarHandler = new ScalarHandler<>();
Long query = runner.query(conn, sql, scalarHandler);
return query;
}

然后再 commServer 中添加实现方法

1
2
3
4
5
6
7
8
9
10
11
@Override
public Long getCommSize(Connection conn) {
String sql = "select count(*) from commodity";
long l = 0;
try {
l = super.execQuerySize(conn, sql);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return l;
}

修改分页查询的SQL

1
2
3
4
5
6
7
8
9
10
11
@Override
public List<Comm> getAllComm(Connection conn,int pageNumber) {
String sql = "SELECT * FROM commodity limit ?,5";
List<Comm> commList = null;
try {
commList = super.execQuery(Comm.class, conn, sql,(pageNumber-1) * 5);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return commList;
}

修改 indexServer,添加分页逻辑

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
@WebServlet("/index")
public class demo2 extends ViewBaseServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp){
Connection conn = null;
try {
// 获取连接
conn = JdbcUtils.getConnection();
// 实例化方法类
CommServer commServer = new CommServer();
int pageNumber = 1;
// 获取第几页
if(StringUtils.isNotEmpty(req.getParameter("page"))){
pageNumber = Integer.parseInt(req.getParameter("page"));
}
// 计算总页数
Long commSize = commServer.getCommSize(conn);
int pageTotal = (int)(commSize / 5 + 1);

System.out.println(pageTotal+"pageTotal");

// 获取全部数据时添加一个分页
List<Comm> commList = commServer.getAllComm(conn,pageNumber);
HttpSession session = req.getSession();
// 保存數據
session.setAttribute("commlist",commList);
// 保存当前是第几页
session.setAttribute("page",pageNumber);
// 保存总页数
session.setAttribute("pageTotal",pageTotal);
super.processTemplate("index",req,resp);
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
JdbcUtils.closeConnection(conn,null);
}
}

在 index.html 中添加分页按钮

1
2
3
4
5
6
<div>
<button th:onclick="pageNub(1)" th:disabled="|${session.page == 1}|">首页</button>
<button th:onclick="|pageNub(${session.page-1})|" th:disabled="|${session.page <= 1}|">上一页</button>
<button th:onclick="|pageNub(${session.page+1})|" th:disabled="|${session.page >= session.pageTotal}|">下一页</button>
<button th:onclick="|pageNub(${session.pageTotal})|" th:disabled="|${session.page == session.pageTotal}|">尾页</button>
</div>

实现 js 方法

1
2
3
function pageNub(page){
window.location.href = "index?page=" + page;
}

效果展示

javaweb8.gif

添加模糊查询功能

首先在 index.html 中添加查询表单

1
2
3
4
5
<form method="post" th:action="@{/index}">
<input type="hidden" name="searchtag" value="search">
<input placeholder="请输入名称查询" name="name" th:value="${session.name}">
<input type="submit" value="查询">
</form>

修改 indexService

表单提交的是 post 方法,然后再 doPost 方法中去请求 doGet 方法

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
@WebServlet("/index")
public class demo2 extends ViewBaseServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
doGet(req,resp);
}

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp){
Connection conn = null;
try {
// 获取session
HttpSession session = req.getSession();
// 初始化页码
int pageNumber = 1;
String name = "";
// 如果是通过查询按钮则进入这里
String searchtag = req.getParameter("searchtag");
// 判断name输入框是否为空
if(StringUtils.isNotEmpty(searchtag)){
name = req.getParameter("name");
session.setAttribute("name",name);
pageNumber = 1;
}else{
name = (String) session.getAttribute("name");
}
// 获取连接
conn = JdbcUtils.getConnection();
// 实例化方法类
CommServer commServer = new CommServer();

// 获取第几页
if(StringUtils.isNotEmpty(req.getParameter("page"))){
pageNumber = Integer.parseInt(req.getParameter("page"));
}
// 计算总页数
Long commSize = commServer.getCommSize(conn,name);
int pageTotal = (int)((commSize - 1) / 5 + 1);
System.out.println("总页数是" + pageTotal);

// 获取全部数据时添加一个分页
List<Comm> commList = commServer.getAllComm(conn,pageNumber,name);

// 保存數據
session.setAttribute("commlist",commList);
// 保存当前是第几页
session.setAttribute("page",pageNumber);
// 保存总页数
session.setAttribute("pageTotal",pageTotal);
super.processTemplate("index",req,resp);
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
JdbcUtils.closeConnection(conn,null);
}
}

因为添加了查询字段,所以要同时修改 SQL 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 查询所有数据
* @author Songzx
* @date 2022/4/12
*/
@Override
public List<Comm> getAllComm(Connection conn,int pageNumber,String keyword) {
String sql = "SELECT * FROM commodity where name like ? limit ?,5";
List<Comm> commList = null;
try {
commList = super.execQuery(Comm.class, conn, sql,"%" + keyword + "%",(pageNumber-1) * 5);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return commList;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 查询数据总条数
* @author Songzx
* @date 2022/4/12
*/

@Override
public Long getCommSize(Connection conn,String keyword) {
String sql = "select count(*) from commodity where name like ?";
long l = 0;
try {
l = super.execQuerySize(conn, sql,"%" + keyword + "%");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return l;
}

效果展示

Snipaste_2022-04-12_16-32-08.png

Servlet Mvc

优化1

我们通过上面的案例可以看到每次实现一个功能是都要新建一个 Servlet,当我们功能特别多时就要新建很多的文件。造成代码不好维护

我们可以吧多个 servlet 合并到一个文件中,实现代码如下

在这个方法类中我们只有一个 WebServlet,地址为 comm.do ,然后通过获取请求的参数 sercode 的值来调用不同的方法来实现一个接口完成多个功能

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
@WebServlet("/comm.do")
public class commService extends ViewBaseServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
// 首先什么一个字符串默认指向index
String sercode = "index";
// 从请求中获取参数判断请求的是那个方法
String sercode1 = req.getParameter("sercode");
// 判断是否为空
if(StringUtils.isNotEmpty(sercode1)){
sercode = sercode1;
}
// 根据请求参数来调用不同的方法
switch (sercode){
case "index":
index(req,resp);
break;
case "delete":
delete(req,resp);
break;
case "edit":
edit(req,resp);
break;
case "update":
update(req,resp);
break;
case "showadd":
showadd(req,resp);
break;
case "saveadd":
saveadd(req,resp);
break;
}
}

/**
* 首页方法
* @author Songzx
* @date 2022/4/13
*/

private void index(HttpServletRequest req, HttpServletResponse resp){
Connection conn = null;
try {
// 获取session
HttpSession session = req.getSession();
// 初始化页码
int pageNumber = 1;
String name = "";
// 如果是通过查询按钮则进入这里
String searchtag = req.getParameter("searchtag");
// 判断name输入框是否为空
if(StringUtils.isNotEmpty(searchtag)){
name = req.getParameter("name");
session.setAttribute("name",name);
pageNumber = 1;
}else{
Object name1 = session.getAttribute("name");
if(name1 != null){
name = (String) name1;
}
}
// 获取连接
conn = JdbcUtils.getConnection();
// 实例化方法类
CommServer commServer = new CommServer();

// 获取第几页
if(StringUtils.isNotEmpty(req.getParameter("page"))){
pageNumber = Integer.parseInt(req.getParameter("page"));
}
// 计算总页数
Long commSize = commServer.getCommSize(conn,name);
int pageTotal = (int)((commSize - 1) / 5 + 1);
System.out.println("总页数是" + pageTotal);

// 获取全部数据时添加一个分页
List<Comm> commList = commServer.getAllComm(conn,pageNumber,name);

// 保存數據
session.setAttribute("commlist",commList);
// 保存当前是第几页
session.setAttribute("page",pageNumber);
// 保存总页数
session.setAttribute("pageTotal",pageTotal);

super.processTemplate("index",req,resp);
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
JdbcUtils.closeConnection(conn,null);
}

/**
* 删除方法
* @author Songzx
* @date 2022/4/13
*/

private void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取参数删除带过来的参数id
String id = req.getParameter("id");
if(StringUtils.isNotEmpty(id)){
int aId = Integer.parseInt(id);
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
CommServer server = new CommServer();
// 执行删除方法
server.deleteCommById(conn,aId);
// 浏览器重定向到首页重新渲染
resp.sendRedirect("comm.do");
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

/**
* 查看详情
* @author Songzx
* @date 2022/4/13
*/

private void edit(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Connection conn = null;
try {
// 从路径中获取参数
String id = req.getParameter("id");
int anInt = Integer.parseInt(id);
CommServer server = new CommServer();
conn = JdbcUtils.getConnection();
// 根据id获取数据
Comm comm = server.getCommById(conn, anInt);
// 把数据保存在session中
req.getSession().setAttribute("commEdit",comm);
// 显示edit页面
super.processTemplate("edit",req,resp);
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
}


/**
* 修改数据
* @author Songzx
* @date 2022/4/13
*/

private void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1.设置编码类型
req.setCharacterEncoding("utf-8");
Connection conn = null;
try {
// 获取参数id
String id = req.getParameter("id");
int anInt = Integer.parseInt(id);
// 获取参数name
String name = req.getParameter("name");
// 获取参数price
String price = req.getParameter("price");
double aDouble = Double.parseDouble(price);
// 获取参数number
String number = req.getParameter("number");
int aNumber = Integer.parseInt(number);
// 获取连接
conn = JdbcUtils.getConnection();
// 封装comm对象
Comm comm = new Comm(anInt, name, aDouble, aNumber);
// 实例化调用接口类
CommServer server = new CommServer();
// 执行更新方法
server.updateCommById(conn,comm);
// 让浏览器重定向到index,重新渲染页面
resp.sendRedirect("comm.do");

} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
}

/**
* 显示添加页面
* @author Songzx
* @date 2022/4/13
*/
private void showadd(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.processTemplate("add",req,resp);
}

/**
* 确认添加方法
* @author Songzx
* @date 2022/4/13
*/

private void saveadd(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置字符串编码
req.setCharacterEncoding("utf-8");
// 获取表单参数
String name = req.getParameter("name");
String price = req.getParameter("price");
double dprice = Double.parseDouble(price);
String number = req.getParameter("number");
int inumber = Integer.parseInt(number);
// 创建 comm 实例
Comm comm = new Comm(name, dprice, inumber);
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
CommServer server = new CommServer();
// 执行添加方法
server.addComm(conn,comm);
// 浏览器重定向到首页
resp.sendRedirect("comm.do");
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}

}
}

优化2

上面我们使用的是 Switch case 来判断参数调用方法,我们可以通过反射来获取,避免大量的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 通过反射来动态调用方法名
Method[] declaredMethods = this.getClass().getDeclaredMethods();
for (Method method : declaredMethods) {
String name = method.getName();
if(sercode.equals(name)){
try {
method.invoke(this,req,resp);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}

最终优化版本

我们将 commService 当成一个普通的类来使用

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package com.songzx.demo01;

import com.songzx.comm.Comm;
import com.songzx.dao.CommServer;
import com.songzx.utils.JdbcUtils;
import com.songzx.utils.StringUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
* @author songzx
* @create 2022-04-13 12:01
*/

public class commService {
/**
* 首页方法
* @author Songzx
* @date 2022/4/13
*/

public String index(String searchtag,String name,Integer page, HttpServletRequest req){

Connection conn = null;
try {
// 如果传进来的页码是null,则默认为1
if(page == null){
page = 1;
}
// 获取session
HttpSession session = req.getSession();

// 判断name输入框是否为空
if(StringUtils.isNotEmpty(searchtag)){
session.setAttribute("name",name);
page = 1;
}else{
Object name1 = session.getAttribute("name");
if(name1 != null){
name = (String) name1;
}
}
// 获取连接
conn = JdbcUtils.getConnection();
// 实例化方法类
CommServer commServer = new CommServer();

// 计算总页数
Long commSize = commServer.getCommSize(conn,name);
int pageTotal = (int)((commSize - 1) / 5 + 1);
System.out.println("总页数是" + pageTotal);

// 获取全部数据时添加一个分页
List<Comm> commList = commServer.getAllComm(conn,page,name);

// 保存數據
session.setAttribute("commlist",commList);
// 保存当前是第几页
session.setAttribute("page",page);
// 保存总页数
session.setAttribute("pageTotal",pageTotal);

return "index";
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
return "error";
}

/**
* 删除方法
* @author Songzx
* @date 2022/4/13
*/

public String delete(String id) {
// 获取参数删除带过来的参数id
if(StringUtils.isNotEmpty(id)){
int aId = Integer.parseInt(id);
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
CommServer server = new CommServer();
// 执行删除方法
server.deleteCommById(conn,aId);
// 浏览器重定向到首页重新渲染
// resp.sendRedirect("comm.do");
return "direct:comm.do";
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return "error";
}

/**
* 查看详情
* @author Songzx
* @date 2022/4/13
*/

public String edit(String id, HttpServletRequest req) {
System.out.println("id = " + id);
Connection conn = null;
try {
int anInt = Integer.parseInt(id);
CommServer server = new CommServer();
conn = JdbcUtils.getConnection();
// 根据id获取数据
Comm comm = server.getCommById(conn, anInt);
// 把数据保存在session中
req.getSession().setAttribute("commEdit",comm);
// 显示edit页面
// super.processTemplate("edit",req,resp);
return "edit";
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
return "error";
}


/**
* 修改数据
* @author Songzx
* @date 2022/4/13
*/

public String update(String id,String name,String price,String number, HttpServletRequest req) throws IOException {
// 1.设置编码类型
req.setCharacterEncoding("utf-8");
Connection conn = null;
try {
int anInt = Integer.parseInt(id);
double aDouble = Double.parseDouble(price);
int aNumber = Integer.parseInt(number);
// 获取连接
conn = JdbcUtils.getConnection();
// 封装comm对象
Comm comm = new Comm(anInt, name, aDouble, aNumber);
// 实例化调用接口类
CommServer server = new CommServer();
// 执行更新方法
server.updateCommById(conn,comm);
// 让浏览器重定向到index,重新渲染页面
// resp.sendRedirect("comm.do");
return "direct:comm.do";

} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
return "error";
}

/**
* 显示添加页面
* @author Songzx
* @date 2022/4/13
*/
public String showadd(){
// super.processTemplate("add",req,resp);
return "add";
}

/**
* 确认添加方法
* @author Songzx
* @date 2022/4/13
*/

public String saveadd(String name,String price,String number, HttpServletRequest req) throws IOException {
// 设置字符串编码
req.setCharacterEncoding("utf-8");
double dprice = Double.parseDouble(price);
int inumber = Integer.parseInt(number);
// 创建 comm 实例
Comm comm = new Comm(name, dprice, inumber);
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
CommServer server = new CommServer();
// 执行添加方法
server.addComm(conn,comm);
// 浏览器重定向到首页
// resp.sendRedirect("comm.do");
return "direct:comm.do";
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(conn,null);
}
return "error";
}
}

编写核心控制器,通过读取 xml 配置文件动态匹配调用的类

编写 xml 配置文件 applicationConentext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<!--
说明:xml是可扩展的文本标记语言

映射关系:
1.定义一个父标签 beans
2.子标签中有id和class,一个id对应一个class对应的server

功能:通过请求 xxx.do 中的 xxx,找到对应的 com.songzx.demo01.xxxService
-->

<benas>
<bean id="comm" class="com.songzx.demo01.commService"></bean>
</benas>

新建 DispatcherServlet.java,这里主要步骤

  • 使用 @WebServlet("*.do") 拦截到所有后缀是 .do 的请求,然后根据请求名称去 xml 中找到对应的类
  • 然后再 service 中获取映射类中的所有方法
  • 通过 req.getParameter("sercode"); 来判断当前调用的是这个类中的那个方法
  • 之后通过 method.getParameters(); 获取这个方法中接收的参数值和参数类型,遍历参数列表从 request 中获取请求中传递过来的值
  • 然后通过 method.invoke(contentObje, parValues);调用这个方法,并传递已经获取到的所有参数值
  • method.invoke 会吧方法的返回值返回,我们根据返回的值做出跳转逻辑判断
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package com.songzx.myssm;

import com.songzx.utils.StringUtils;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

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 javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
* @author songzx
* @create 2022-04-14 21:29
*/
@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet {
// 声明一个map存储bean的映射
private Map<String,Object> beanMap = new HashMap<>();

public DispatcherServlet(){
// 解析读取xml文件
try {
// 获取xml文件流
InputStream stream = this.getClass().getClassLoader().getResourceAsStream("applicationConentext.xml");
// 创建DocumentBuilderFactory对象
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 创建newDocumentBuilder对象
DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
// 创建document对象
Document document = documentBuilder.parse(stream);
// 获取所有的bean标签
NodeList nodeList = document.getElementsByTagName("bean");
for (int i = 0; i < nodeList.getLength(); i++){
// 获取单个的bean标签
Node bean = nodeList.item(i);
// 判断是否是一个元素标签
if(bean.getNodeType() == Node.ELEMENT_NODE){
Element elementNode = (Element) bean;
String id = elementNode.getAttribute("id");
String aClass = elementNode.getAttribute("class");
// 创建出id影射得到的实例
Object beanObj = Class.forName(aClass).newInstance();
// 往beanMap中添加数据
beanMap.put(id,beanObj);
}
}
} catch (ParserConfigurationException | SAXException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("请求进来");
// 设置编码
req.setCharacterEncoding("utf-8");
// 获取请求地址 => /hello.do -> hello
String path = parseStr(req.getServletPath());
// 根据请求的路径获取对应的实例
Object contentObje = beanMap.get(path);
// 获取请求方法的标识
String sercode = req.getParameter("sercode");
// 判断标识符是否是空
if(StringUtils.isEmpty(sercode)){
sercode = "index";
}

try {
// 获取实例上的所有方法
Method[] methods = contentObje.getClass().getDeclaredMethods();
for (Method method : methods){
// 确定调用那个方法
if(sercode.equals(method.getName())){
method.setAccessible(true);
// 获取当前方法的参数集合
Parameter[] parameters = method.getParameters();
// 遍历参数集合获取具体的值
Object[] parValues = new Object[parameters.length];

for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String name = parameter.getName();
// 特殊值判断
if("req".equals(name)){
parValues[i] = req;
}else if("resp".equals(name)){
parValues[i] = resp;
}else if("session".equals(name)){
parValues[i] = req.getSession();
}else{
String typeName = parameter.getType().getName();
String paraName = req.getParameter(name);
parValues[i] = paraName;
if("java.lang.Integer".equals(typeName) && paraName != null){
parValues[i] = Integer.parseInt(paraName);
}
}
}


// 获取要重定向的地址
String directStr = (String) method.invoke(contentObje, parValues);
// 如果返回的地址是以 direct: 开头的表示浏览器重定向
if(directStr.startsWith("direct:")){
String newDirect = directStr.substring("direct:".length());
// 重新向
resp.sendRedirect(newDirect);
}else{
// 如果不是以direct:开头的表示内部重定向
super.processTemplate(directStr,req,resp);
}
}
}
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

/**
* 将 /hello.do 中的 hello 提取出来
* @author Songzx
* @date 2022/4/14
*/
private String parseStr(String str){
int lastIndex = str.lastIndexOf(".do");
String newstr = str.substring(1,lastIndex);
return newstr;
}
}

注意,在java jdk1.8 以后,通过 method.getParameters(); 可以获取到参数的具体名称,不再是 arg0,但是要设置一下,具体设置参数如下

添加 -parameters

Snipaste_2022-04-20_17-05-22.png

Servlet常用APi

读取初始化参数 getInitParameter

获取初始化参数,getServletConfig 使用方法

配置 XML 文件代码

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>Demo01</servlet-name>
<servlet-class>com.songzx.Servlet.demo01</servlet-class>
<!-- 初始化 param 参数 -->
<init-param>
<param-name>uname</param-name>
<param-value>张三</param-value>
</init-param>

<init-param>
<param-name>height</param-name>
<param-value>188</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>Demo01</servlet-name>
<url-pattern>/demo01</url-pattern>
</servlet-mapping>
</web-app>

编写java代码,继承 HttpServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class demo01 extends HttpServlet {
@Override
public void init() throws ServletException {
// 1.获取 config 对象
ServletConfig config = getServletConfig();
// 2.读取配置的参数 key
String uname = config.getInitParameter("uname");
System.out.println("uname = " + uname);

// 2 可以读取多个
String height = config.getInitParameter("height");
System.out.println("height = " + height);
}
}

上面除了通过 XML 配置外也可以通过注解的方式来配置初始化参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WebServlet(urlPatterns = {"/demo01"},initParams = {
@WebInitParam(name = "uname",value = "张三"),
@WebInitParam(name = "height",value = "188")
})
public class demo01 extends HttpServlet {
@Override
public void init() throws ServletException {
// 1.获取 config 对象
ServletConfig config = getServletConfig();
// 2.读取配置的参数 key
String uname = config.getInitParameter("uname");
System.out.println("uname = " + uname);

// 2 可以读取多个
String height = config.getInitParameter("height");
System.out.println("height = " + height);

// 3.读取 Context
ServletContext context = getServletContext();
String contextName = context.getInitParameter("contextName");
// contextName = classpath:com.songzx.Servlet.demo01
System.out.println("contextName = " + contextName);
}
}

运行获取

Snipaste_2022-04-20_18-23-42.png

获取上下文 getServletContext

获取对象上下文,在初始化 init 方法中调用 getServletContext 方法

配置 XML 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 初始化 context 参数 -->
<context-param>
<param-name>contextName</param-name>
<param-value>classpath:com.songzx.Servlet.demo01</param-value>
</context-param>

<servlet>
<servlet-name>Demo01</servlet-name>
<servlet-class>com.songzx.Servlet.demo01</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>Demo01</servlet-name>
<url-pattern>/demo01</url-pattern>
</servlet-mapping>
</web-app>

编写java代码获取

1
2
3
4
5
6
7
8
9
10
public class demo01 extends HttpServlet {
@Override
public void init() throws ServletException {
// 3.读取 Context
ServletContext context = getServletContext();
String contextName = context.getInitParameter("contextName");
//=> contextName = classpath:com.songzx.Servlet.demo01
System.out.println("contextName = " + contextName);
}
}

初识MVC

MVC:Model(模型),View(视图),Controller(控制器)

视图层:用于做数据展示以及和用户交互的界面

控制层:能够接收客户端的请求,具体的业务功能呢还要借助模型组件来完成

模型层:模型分为很多种,有比较简单的pojo,vo(value,object),有业务模型组件,有数据访问层

  • pojo/vo:值对象
  • DAO:数据访问对象
  • BO:业务对象

区分业务对象和数据访问对象

  • BAO中的方法都是单精度的或者称为细粒度的。什么是单精度:一个方法只考虑一个操作,例如增删改查方法,只考虑层增删改查
  • BO中的方法属于业务方法,属于粗粒度的。一个业务方法中会包含多个BAO方法:例如注册业务
    • 首先要检查该用户是否已经注册,调用BAO中的select方法
    • 然后往用户表中增加一条信息,调用BAO中的insert方法
    • 往用户积分表中增加一条数据,新用户默认100积分,调用BAO中的insert方法
    • 其他方法……

改造水果管理系统,增加业务层

添加一个接口 commServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface commServer {
/**
* 获取所有数
* @param keyword
* @param pageNub
* @return
*/
List<Comm> getCommList(String keyword,Integer pageNub);

Comm getCommById(Integer id);

void addComm(Comm comm);

void deleteCommById(Integer id);

void updateCommById(Comm comm);

Integer getPageCount(String keyword);
}

添加这个接口的实现类 package com.songzx.service.imp.commServiceImp;

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
public class commServiceImp implements commServer {

// 声明数据访问对象
private CommDao commDao = new CommDao();

// 获取连接的conn
private Connection conn = JdbcUtils.getConnection();

public commServiceImp() throws SQLException, ClassNotFoundException {
}

@Override
public List<Comm> getCommList(String keyword, Integer pageNub) {
return commDao.getAllComm(conn,pageNub,keyword);
}

@Override
public Comm getCommById(Integer id) {
try {
return commDao.getCommById(conn,id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}

@Override
public void addComm(Comm comm) {
commDao.addComm(conn,comm);
}

@Override
public void deleteCommById(Integer id) {
commDao.deleteCommById(conn,id);
}

@Override
public void updateCommById(Comm comm) {
commDao.updateCommById(conn,comm);
}

@Override
public Integer getPageCount(String keyword) {
Long commSize = commDao.getCommSize(conn, keyword);
return (int)((commSize - 1) / 5 + 1);
}
}

然后将原本的 commService 重命名为 commController,并引入 commServiceImp

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
public class commController {

private commServiceImp server = new commServiceImp();

/**
* 首页方法
* @author Songzx
* @date 2022/4/13
*/

public String index(String searchtag,String name,Integer page, HttpServletRequest req){
// 如果传进来的页码是null,则默认为1
if(page == null){
page = 1;
}
// 获取session
HttpSession session = req.getSession();

// 判断name输入框是否为空
if(StringUtils.isNotEmpty(searchtag)){
session.setAttribute("name",name);
page = 1;
}else{
Object name1 = session.getAttribute("name");
if(name1 != null){
name = (String) name1;
}
}

// 计算总页数
int pageTotal = server.getPageCount(name);
System.out.println("总页数是" + pageTotal);

// 获取全部数据时添加一个分页
List<Comm> commList = server.getCommList(name,page);

// 保存數據
session.setAttribute("commlist",commList);
// 保存当前是第几页
session.setAttribute("page",page);
// 保存总页数
session.setAttribute("pageTotal",pageTotal);

return "index";
}

/**
* 删除方法
* @author Songzx
* @date 2022/4/13
*/

public String delete(String id) {
int aId = Integer.parseInt(id);
// 执行删除方法
server.deleteCommById(aId);
// 浏览器重定向到首页重新渲染
return "direct:comm.do";
}

/**
* 查看详情
* @author Songzx
* @date 2022/4/13
*/

public String edit(String id, HttpServletRequest req) {
int anInt = Integer.parseInt(id);
// 根据id获取数据
Comm comm = server.getCommById(anInt);
// 把数据保存在session中
req.getSession().setAttribute("commEdit",comm);
// 显示edit页面
// super.processTemplate("edit",req,resp);
return "edit";
}


/**
* 修改数据
* @author Songzx
* @date 2022/4/13
*/

public String update(String id,String name,String price,String number, HttpServletRequest req) throws IOException {
// 1.设置编码类型
req.setCharacterEncoding("utf-8");
int anInt = Integer.parseInt(id);
double aDouble = Double.parseDouble(price);
int aNumber = Integer.parseInt(number);
// 封装comm对象
Comm comm = new Comm(anInt, name, aDouble, aNumber);

// 执行更新方法
server.updateCommById(comm);
// 让浏览器重定向到index,重新渲染页面
// resp.sendRedirect("comm.do");
return "direct:comm.do";
}

/**
* 显示添加页面
* @author Songzx
* @date 2022/4/13
*/
public String showadd(){
// super.processTemplate("add",req,resp);
return "add";
}

/**
* 确认添加方法
* @author Songzx
* @date 2022/4/13
*/

public String saveadd(String name,String price,String number, HttpServletRequest req) throws IOException {
// 设置字符串编码
req.setCharacterEncoding("utf-8");
double dprice = Double.parseDouble(price);
int inumber = Integer.parseInt(number);
// 创建 comm 实例
Comm comm = new Comm(name, dprice, inumber);
// 执行添加方法
server.addComm(comm);
// 浏览器重定向到首页
// resp.sendRedirect("comm.do");
return "direct:comm.do";
}
}

这样我们的业务层就添加进来了

实现控制翻转和依赖注入

上面的代码中我们在 commController 中引用了 commServiceImp

Snipaste_2022-04-21_17-44-41.png

然后再 commServiceImp 中已用了 commDao

Snipaste_2022-04-21_17-44-14.png

这种情况在实际开发中并不推荐

在实际开发中我们追求高内聚,低耦合,一个类只处理和自己本身业务相关的事情,尽量不和别的模块发生关系,下面我们就通过配置XML的方式来改变这种耦合

在 applicationConentext.xml 文件中添加代码,在xml中声明了每个类的地址和引用属性名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<benas>

<bean id="comm" class="com.songzx.controller.commController">
<!-- property 声明了这个bean中引用的依赖,name表示声明的属性名,ref表示bean的id名 -->
<property name="server" ref="commService"></property>
</bean>

<bean id="commService" class="com.songzx.service.imp.commServiceImp">
<property name="commDao" ref="commDao"></property>
</bean>

<bean id="commDao" class="com.songzx.dao.CommDao"/>

</benas>

然后新建 com.songzx.io.beanFactory 接口,根据benaid获取对应的class实例

1
2
3
4
public interface beanFactory {
// 根据benaid获取对应的class实例
Object getBean(String id);
}

创建接口实现类 BeanFactoryImp

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
public class BeanFactoryImp implements beanFactory{
private HashMap<String,Object> benaMap = new HashMap<>();

public BeanFactoryImp(){
// 解析读取xml文件
try {
// 获取xml文件流
InputStream stream = this.getClass().getClassLoader().getResourceAsStream("applicationConentext.xml");
// 创建DocumentBuilderFactory对象
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 创建newDocumentBuilder对象
DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
// 创建document对象
Document document = documentBuilder.parse(stream);
// 获取所有的bean标签
NodeList nodeList = document.getElementsByTagName("bean");

// 获取所有的bean对象
for (int i = 0; i < nodeList.getLength(); i++){
// 获取单个的bean标签
Node bean = nodeList.item(i);
// 判断是否是一个元素标签
if(bean.getNodeType() == Node.ELEMENT_NODE){
Element elementNode = (Element) bean;
String id = elementNode.getAttribute("id");
String aClass = elementNode.getAttribute("class");
// 创建出id影射得到的实例
Object beanObj = Class.forName(aClass).newInstance();
// 将bena对象的实例保存到map容器中
benaMap.put(id,beanObj);
}
}

// 处理bena中间的依赖,并且实施依赖注入
for (int i =0; i < nodeList.getLength(); i++){
// 获取单个的bean标签
Node bean = nodeList.item(i);
// 判断是否是一个元素标签
if(bean.getNodeType() == Node.ELEMENT_NODE){
// 将benaNode强转为一个元素
Element elementNode = (Element) bean;
// 获取当前bena的id
String id = elementNode.getAttribute("id");
// 获取 bena 标签的子元素
NodeList childNodes = elementNode.getChildNodes();
// 遍历子元素
for (int j = 0; j < childNodes.getLength(); j++) {
Node childItem = childNodes.item(j);
if(childItem.getNodeType() == Node.ELEMENT_NODE){
Element eleChildNode = (Element) childItem;
// 获取引用属性的属性值
String proName = eleChildNode.getAttribute("name");
// 获取引用组件的ref名
String proRef = eleChildNode.getAttribute("ref");
// 获取引用组件的对象
Object proObj = benaMap.get(proRef);
// 获取自身组件对象
Object beanObj = benaMap.get(id);
// 通过反射获取声明的属性名
Field beanKeyField = beanObj.getClass().getDeclaredField(proName);
beanKeyField.setAccessible(true);
// 将引用组件的实例赋值给属性
beanKeyField.set(beanObj,proObj);
}
}
}
}
} catch (ParserConfigurationException | SAXException | ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}

@Override
public Object getBean(String id) {
return benaMap.get(id);
}
}

然后把核心控制器中关系解析 xml 的代码删除掉,修改 DispatcherServlet 类,去掉初始化的代码

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
@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet {
// 创建读取xml和依赖注入的类
private BeanFactoryImp beanMap = new BeanFactoryImp();

@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("请求进来");
// 设置编码
req.setCharacterEncoding("utf-8");
// 获取请求地址 => /hello.do -> hello
String path = parseStr(req.getServletPath());
// 根据请求的路径获取对应的实例
Object contentObje = beanMap.getBean(path);
// 获取请求方法的标识
String sercode = req.getParameter("sercode");
// 判断标识符是否是空
if(StringUtils.isEmpty(sercode)){
sercode = "index";
}

try {
// 获取实例上的所有方法
Method[] methods = contentObje.getClass().getDeclaredMethods();
for (Method method : methods){
// 确定调用那个方法
if(sercode.equals(method.getName())){
method.setAccessible(true);
// 获取当前方法的参数集合
Parameter[] parameters = method.getParameters();
// 遍历参数集合获取具体的值
Object[] parValues = new Object[parameters.length];

for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String name = parameter.getName();
// 特殊值判断
if("req".equals(name)){
parValues[i] = req;
}else if("resp".equals(name)){
parValues[i] = resp;
}else if("session".equals(name)){
parValues[i] = req.getSession();
}else{
String typeName = parameter.getType().getName();
String paraName = req.getParameter(name);
parValues[i] = paraName;
if("java.lang.Integer".equals(typeName) && paraName != null){
parValues[i] = Integer.parseInt(paraName);
}
}
}


// 获取要重定向的地址
String directStr = (String) method.invoke(contentObje, parValues);
// 如果返回的地址是以 direct: 开头的表示浏览器重定向
if(directStr.startsWith("direct:")){
String newDirect = directStr.substring("direct:".length());
// 重新向
resp.sendRedirect(newDirect);
}else{
// 如果不是以direct:开头的表示内部重定向
super.processTemplate(directStr,req,resp);
}
}
}
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

/**
* 将 /hello.do 中的 hello 提取出来
* @author Songzx
* @date 2022/4/14
*/
private String parseStr(String str){
int lastIndex = str.lastIndexOf(".do");
String newstr = str.substring(1,lastIndex);
return newstr;
}
}

过滤器

过滤的作用会在请求时进行拦截一次,然后放行,响应回来后再次拦截

  • 请求拦截
  • 放行
  • 响应拦截

要实现过滤器,需要在类中实现 Filter 接口,Filter 有三个方法,表示过滤器的声明周期

  • init 初始化
  • doFilter 拦截服务
  • destroy 销毁

实现过滤器

添加 service1 类,作为我们的 WebServlet

1
2
3
4
5
6
7
8
9
@WebServlet("/demo1.do")
public class service1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("demo01 Service...");
// 跳转到 hello.html 页面
req.getRequestDispatcher("hello.html").forward(req,resp);
}
}

创建 Filter 的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebFilter("/demo1.do")
public class filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 请求时拦截
System.out.println("请求拦截");
// 放行
filterChain.doFilter(servletRequest,servletResponse);
// 详情回来后拦截
System.out.println("响应拦截");
}

@Override
public void destroy() {

}
}

这里的注解声明为 @WebFilter("/demo1.do") ,要和 @WebServlet("/demo1.do") 同名,这样才能起到这个接口的拦截作用

然后启动服务,查看打印的顺序

Snipaste_2022-04-21_18-12-32.png

除了设置单个过滤器,也可以设置过滤器链,表示一个接口有多个过滤器

分别新建 filter2,filter3,编写文件代码

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.szx.filters;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* @author songzx
* @create 2022-04-21 18:14
*/
@WebFilter("*.do")
public class filter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("A");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("AO");
}

@Override
public void destroy() {

}
}

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
package com.szx.filters;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* @author songzx
* @create 2022-04-21 18:15
*/
@WebFilter("*.do")
public class filter3 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("B");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("BO");
}

@Override
public void destroy() {

}
}

说明:

  • @WebFilter("*.do") 也可以使用通配符,这里表示匹配所有以.do 结尾的请求
  • 如果使用注解的方法添加过滤器,则过滤器链的调用顺序会按照文件名的字母顺序调用,如果首字母相同则按照文件顺序调用

在启动服务之前将 filter1 中的 @WebFilter("/demo1.do") 注释掉,观察控制台打印的顺序

Snipaste_2022-04-21_18-20-42.png

过滤器的使用

添加过滤器,设置请求编码为 UTF-8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@WebFilter("*.do")
public class requestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 在请求进来时设置一下请求编码
HttpServletRequest request = (HttpServletRequest) servletRequest;
request.setCharacterEncoding("utf-8");
filterChain.doFilter(servletRequest,servletResponse);
}

@Override
public void destroy() {
Filter.super.destroy();
}
}

然后就可以去掉 DispatcherServlet 中关于设置编码的操作

Snipaste_2022-04-21_18-46-31.png

使用过滤器添加事务管理

新建 com.songzx.trans.Transmanager 类,这个类有三个方法,专门负责事务的开启和提交

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
public class Transmanager {

/**
* 开启事务
* @author Songzx
* @date 2022/4/23
*/
public static void beginTrans() throws SQLException, ClassNotFoundException {
JdbcUtils.getConnection().setAutoCommit(false);
}

/**
* 提交事务
* @author Songzx
* @date 2022/4/23
*/
public static void commit() throws SQLException, ClassNotFoundException {
JdbcUtils.getConnection().commit();
}

/**
* 回滚事务
* @author Songzx
* @date 2022/4/23
*/
public static void rollback() throws SQLException, ClassNotFoundException {
JdbcUtils.getConnection().rollback();
}
}

因为事务要使用同一个连接,所以要修改 JdbcUtils,使用 ThreadLocal 保存全局唯一的 Connection

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
public class JdbcUtils {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

public static Connection getConnection() throws SQLException, ClassNotFoundException {
// 优先从本地线程获取连接
Connection conn = threadLocal.get();

if(conn != null){
return conn;
}else{
// 1.连接的三个基本信息
String url = "jdbc:mysql://127.0.0.1:3306/javaweb";
String user = "root";
String password = "abc123";

// 2.获取 driver 运行时类
Class.forName("com.mysql.cj.jdbc.Driver");

// 3.获取连接
Connection connection = DriverManager.getConnection(url, user, password);

threadLocal.set(connection);

return threadLocal.get();
}
}

public static void closeConnection(Connection conn, Statement state){
DbUtils.closeQuietly(conn);
DbUtils.closeQuietly(state);
}

}

然后新建过滤器 com.songzx.filter.connectionFilter,拦截所有以为 .do 结尾的请求

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.songzx.filter;


import com.songzx.trans.Transmanager;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* @author songzx
* @create 2022-04-23 22:27
*/
@WebFilter("*.do")
public class connectionFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
// 开启事务
Transmanager.beginTrans();
System.out.println("开启事务");
// 放行
filterChain.doFilter(servletRequest,servletResponse);
// 提交
Transmanager.commit();
System.out.println("提交事务");
} catch (Exception e) {
e.printStackTrace();
// 程序出错回滚
try {
Transmanager.rollback();
System.out.println("回滚事务");
} catch (Exception classNotFoundException) {
classNotFoundException.printStackTrace();
}
}
}

@Override
public void destroy() {
Filter.super.destroy();
}
}

注意:我们在过滤器中使用 try catch 来监听内部错误,这就要求我们需要在方法内部抛出异常,不能在方法里面处理异常,要不然过滤器监听不到错误,无法执行回滚操作

监听器

ServletContextListener 可以监听上下文的创建和销毁

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
package com.szx.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
* @author songzx
* @create 2022-04-24 14:15
*/
@WebListener
public class MyServerContextListener implements ServletContextListener {
/**
* 上下文被创建时触发
* @author Songzx
* @date 2022/4/24
*/
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("上下文被初始化");
}

@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("上下文销毁");
}
}

添加jar包

Snipaste_2022-04-30_19-42-46.png

按照文件目录创建文件夹,然后把对应文件夹内的文件加载到其中

Snipaste_2022-05-01_15-20-26.png

然后选择 Build Artifact

Snipaste_2022-05-01_15-22-08.png

选择刚刚打包的文件进行build

Snipaste_2022-05-01_15-22-59.png

随后会生成一个 jar 包,我们引用即可

Snipaste_2022-05-01_15-24-07.png

IDEA导出jar包插件

https://blog.csdn.net/zhyhang/article/details/89060408

使用过滤器处理登录验证

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
package com.szx.z_filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
* @author songzx
* @create 2022-05-04 17:15
*/

@WebFilter(
// 需要拦截的页面
urlPatterns = {"*.do","*.html"},
initParams = {
// 配置访问白名单
@WebInitParam(
name = "bai",
value =
"/pro11/router.do?operate=page&path=pages/user/login," +
"/pro11/user.do?null")
}
)
public class SessionFilter implements Filter {
private List<String> baiList = null;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 获取配置的初始化参数
String pathval = filterConfig.getInitParameter("bai");
// 使用逗号分隔,获取一个字符串数组
String[] split = pathval.split(",");
// 将数组转成字符串集合
baiList = Arrays.asList(split);
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;

// 封装当前请求的路径
String uri = request.getRequestURI();
String query = request.getQueryString();
String paht= uri + "?" + query;

System.out.println("paht = " + paht);

// 判断路径是否在白名单中,如果在白名单中,则放行
if(baiList.contains(paht)){
filterChain.doFilter(servletRequest,servletResponse);
return;
}else{
// 如果不在白名单中,则去session中判断是否存在登录信息
HttpSession session = request.getSession();
Object userInfo = session.getAttribute("userInfo");
// 如果登录信息为空,则强制跳转到登录页面
if(userInfo == null){
response.sendRedirect("router.do?operate=page&path=pages/user/login");
}else{
// 如果存在,表示用户已经登录,正常放行
filterChain.doFilter(servletRequest,servletResponse);
}
}
}

@Override
public void destroy() {

}
}

java处理浮点数精度问题

首先看问题

Snipaste_2022-05-04_17-40-35.png

当我们直接使用Double类型和一个整数类型做乘法运算时,可能会出现精度问题,如上图所示,有很多的小数位

解决办法,使用 BigDecimal,BigDecimal 只接受字符串类型,所以我们要把 Doubel 和 int 都转成字符串来计算

Snipaste_2022-05-04_17-44-04.png

使用 kaptcha 生成验证码

首先导入jar包: kaptcha-2.3.2.jar,下载地址:https://www.jb51.net/softs/546820.html

然后在 web.xml 中添加配置代码

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.jpg</url-pattern>
</servlet-mapping>

index.html 页面中使用

1
2
3
<body>
<img src="kaptcha.jpg"/>
</body>

kaptcha 在生成验证码的同时,会在 session 中保存一份当前的验证码数据,session 的 key 为:KAPTCHA_SESSION_KEY ,我们可以通过读取 session 来校验前端输入的验证码是否正确

1
2
3
4
5
6
7
8
9
@WebServlet("/verificationCode.do")
public class VerificationCode extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
Object kaptcha_session_key = session.getAttribute("KAPTCHA_SESSION_KEY");
System.out.println("kaptcha_session_key = " + kaptcha_session_key);
}
}

运行代码效果

Snipaste_2022-05-08_17-58-55.png

关于 kaptcha 的更多配置属性

Constant 描述 默认值
kaptcha.border 图片边框,合法值:yes , no yes
kaptcha.border.color 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. black
kaptcha.border.thickness 边框厚度,合法值:>0 1
kaptcha.image.width 图片宽 200
kaptcha.image.height 图片高 50
kaptcha.producer.impl 图片实现类 com.google.code.kaptcha.impl.DefaultKaptcha
kaptcha.textproducer.impl 文本实现类 com.google.code.kaptcha.text.impl.DefaultTextCreator
kaptcha.textproducer.char.string 文本集合,验证码值从此集合中获取 abcde2345678gfynmnpwx
kaptcha.textproducer.char.length 验证码长度 5
kaptcha.textproducer.font.names 字体 Arial, Courier
kaptcha.textproducer.font.size 字体大小 40px
kaptcha.textproducer.font.color 字体颜色,合法值: r,g,b 或者 white,black,blue. black
kaptcha.textproducer.char.space 文字间隔 2
kaptcha.noise.impl 干扰实现类 com.google.code.kaptcha.impl.DefaultNoise
kaptcha.noise.color 干扰颜色,合法值: r,g,b 或者 white,black,blue. black
kaptcha.obscurificator.impl 图片样式: 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy com.google.code.kaptcha.impl.WaterRipple
kaptcha.background.impl 背景实现类 com.google.code.kaptcha.impl.DefaultBackground
kaptcha.background.clear.from 背景颜色渐变,开始颜色 light grey
kaptcha.background.clear.to 背景颜色渐变,结束颜色 white
kaptcha.word.impl 文字渲染器 com.google.code.kaptcha.text.impl.DefaultWordRenderer
kaptcha.session.key session key KAPTCHA_SESSION_KEY
kaptcha.session.date session date KAPTCHA_SESSION_DATE

配置属性的使用方法,以设置文字大小为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<servlet>
<servlet-name>KaptchaServlet</servlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
<init-param>
<!-- 边框颜色 -->
<param-name>kaptcha.border.color</param-name>
<param-value>blue</param-value>
</init-param>
<init-param>
<!-- 边框厚度 -->
<param-name>kaptcha.border.thickness</param-name>
<param-value>3</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>KaptchaServlet</servlet-name>
<url-pattern>/kaptcha.jpg</url-pattern>
</servlet-mapping>

效果:

Snipaste_2022-05-08_18-08-43.png

使用axios发送数据响应普通文本

首先前端发送请求

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script src="./js/vue.js"></script>
<script src="./js/axios.min.js"></script>

<body>
<div id="app">
<input v-model="name" placeholder="姓名" /><br>
<input v-model="pwd" placeholder="密码" /><br>
<button v-on:click="submt">提交</button>
</div>
<script>
window.onload = function () {
var app = new Vue({
el: '#app',
data: {
name: '',
pwd: '',
},
methods: {
submt: function () {
axios.post('getUserInfo', {
name: this.name,
pwd: this.pwd
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
}
}
})
}
</script>
</body>

</html>

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
package com.szx.axios;

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.PrintWriter;

/**
* @author songzx
* @create 2022-05-08 21:21
*/
@WebServlet("/getUserInfo")
public class demo01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("接收到值");
}
}

接收和返回JSON数据

首先前端发送请求

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script src="./js/vue.js"></script>
<script src="./js/axios.min.js"></script>

<body>
<div id="app">
<input v-model="name" placeholder="姓名" /><br>
<input v-model="pwd" placeholder="密码" /><br>
<button v-on:click="submt">提交</button>
</div>
<script>
window.onload = function () {
var app = new Vue({
el: '#app',
data: {
name: '',
pwd: '',
},
methods: {
submt: function () {
axios.post('demo02', {
name: this.name,
pwd: this.pwd
})
.then(function (response) {
console.log(response.data);
app.name = response.data.name
app.pwd = response.data.pwd
})
.catch(function (error) {
console.log(error);
});
}
}
})
}
</script>
</body>

</html>

后端创建一个 User 的 pojo 类,属性名和前端传递过来的一样

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.szx.pojo;

/**
* @author songzx
* @create 2022-05-08 22:51
*/
public class User {
String name;
String pwd;

public String getName() {
return name;
}

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

public String getPwd() {
return pwd;
}

public void setPwd(String pwd) {
this.pwd = pwd;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}

后端接收请求:

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
package com.szx.axios;

import com.google.gson.Gson;
import com.szx.pojo.User;

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.BufferedReader;
import java.io.IOException;

/**
* @author songzx
* @create 2022-05-08 22:40
*/
@WebServlet("/demo02")
public class demo02 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuffer stringBuffer = new StringBuffer("");
BufferedReader bufferedReader = req.getReader();
String jsonstr = "";
while ((jsonstr = bufferedReader.readLine()) != null){
stringBuffer.append(jsonstr);
}
jsonstr = stringBuffer.toString();
System.out.println("jsonstr = " + jsonstr);

// 将获取到的JSON字符串转成Java对象
Gson gson = new Gson();
User user = gson.fromJson(jsonstr, User.class);
System.out.println(user);

// 将Java对象转成前端需要的JSON
user.setName("鸠摩智");
user.setPwd("123456");
String userJson = gson.toJson(user);
resp.setCharacterEncoding("utf-8");
resp.setContentType("application/json;utf-8");
resp.getWriter().write(userJson);
}
}

上面代码中使用到了 gson-2.8.6.jar,下载地址:https://search.maven.org/artifact/com.google.code.gson/gson/2.8.6/jar

Gson 有两个核心方法:

  • gson.fromJson ,把 JSON 字符串转成 Java 类
  • gson.toJson ,把 Java 类转成 JSON 字符串传递给前端,再次之前要设置 resp.setContentType("application/json;utf-8");,来明确告诉浏览器发送的是一个 JSON 数据,这样浏览器会自动的把 JSON 字符串格式转成 JSON 数据,前端就不需要手动去格式化 JSON 字符串