课程视频地址:https://www.bilibili.com/video/BV1AS4y177xJ?p=1
课程文档地址:https://heavy_code_industry.gitee.io/code_heavy_industry/pro001-javaweb/lecture/
编写静态页面
使用react脚手架搭建项目
create-react-app demo01
使用 Semi UI框架
yarn add @douyinfe/semi-ui
编写 App.js 组件内容
/* 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
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<App />,
document.getElementById('root')
);
reportWebVitals();
启动项目
yarn start
效果展示
Tomcat 安装
首先进入官网进行下载
https://tomcat.apache.org/
这里我选择 8 版本
点击后页面往下滑,选择 zip 版本下载,选择压缩包下载不用安装就可使用
下载后解压到文件夹中,注意解压的目录不能有中文和空格,解压内容如下
接着点开bin目录,点击 startup.bat 运行即可
打开浏览器,输入 localhost:8080 查看,看到如下页面表示启动成功
部署项目到Tomcat
打开 Tomcat 解压目录中的 webapps 文件夹
打开 ROOT 文件夹
保留 WEB-INF 文件夹,其余文件全部替换为我们要部署的前端项目文件
重新运行 startup.bat,再次输入 localhost:8080
下载 Tomcat9
由于我使用的 java jdk版本是 java17,所以继续使用 tomcat8 的话会有问题
在 idea 中使用 tomcat
首先新建 Java 项目,默认创建 Java 项目不包含 Web 工程,我们需要手动创建。在项目名称上右键选择 Add Framework Support
选择 Web Applocation,之后点击OK
点击OK后,项目会自动创建一个 WEB 文件夹
然后在和 WEB-INF 平级新建一个 hello.html,同时删除 index.jsp
接着点击右上角的 Add Configuration
在弹出的框中点击左上角 + 号
点开后往下滑选择 Tomcat Server 下面的 Local
在打开的弹框中点击 Configure
选择 tomcat9
接着点击 Deployment
然后再点回 Server
配置完成后会有一个可用的tomcat服务,点击启动debug模式运行
等待服务启动
启动成功自动打开浏览器,发现出现 404 错误,这是因为我们创建的 html 文件名字是 hello.html,需要手动的在地址后面添加 hello.html 即可
现在我们手动的在地址后面添加 hello.html 再次访问
修改代码。刷新页面即可实时查看改动效果
那么怎么样能自动打开就是加上 hello.html 后缀呢。点击右上角的编辑配置
修改启动地址即可
重新启动即可自动打开 hello.html
使用 HttpServlet 接收表单参数
编写 form 表单
首先修改 hello.html 代码,添加 from 表单提交
<!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
如图
点开 + 号,选择 Library
选择 tomcat9 导入
点击应用
此时项目中就有 tomcat9 的依赖包
编写接收方法
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 参数
执行过程
- 用户发送请求,action = add
- 项目中 web.xml 中找到 url-pattern = /add,对应第12行
- 找到第11行的 servlet-name = addServer
- 找到和 servlet-mapping 中 servlet-name 一致的 servlet,对应第7行
- 然后找到第8行地址所对应的 addServer 类
- 接收用户发送过来的请求
效果演示
启动项目后点击表单提交,可能出现如下错误
这时我们要检查环境变量 JAVA_HOME 对应的值是否是 java jdk 的低版本,我这里原来是 jdk8.0,修改为 jdk17
然后修改 tomcat 配置,把 jre 同步修改为 jdk17 版本
再次启动查看效果
导入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数据库驱动)
然后再项目上右键新建文件夹 resource
,文件夹的名称固定为这个,然后再该文件夹上右键选择 Mark Directory –> Sources Root
接着在此文件夹下新建配置文件 druid.properties
,
编写连接数据库的方法
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 方法
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);
}
}
}
重新启动,填写表单后点击提交
查询数据库,数据成功进入到表中
处理post接收中文乱码问题
如图,当表单填写中文后会出现乱码问题
解决方法:在 doPost 方法首行添加如下代码
req.setCharacterEncoding("utf-8");
设置字符串的格式为 utf-8 的格式,再次发送中文信息
Server 的生命周期
-
init 初始化
-
service 服务中
-
destroy 销毁
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 方法销毁实例
默认情况下,会在第一次请求时同时进行初始化和服务操作,第二次请求开始之后不会再次初始化。所以这种模式是单例的,但同时也是线程不安全的
再第一次请求时进行初始化的优缺点:
- 优点:提高程序启动的速度
- 缺点:第一次请求时效率低。可以通过修改 servlet 的初始化时机来提交第一次访问的效率
修改 Servlet 的初始化时机
在 web.xml 中添加 load-on-startup
,这个值越小,实例创建的时机越早.最小不能低于0
启动服务后,观察控制台输出
当进行第一次访问时直接就是正在服务,从而不会再第一步请求时创建实例和初始化
Http的请求和响应
介绍
Http 被称为超文本传输协议
请求
请求包含下面三个部分
- 请求行
- 请求的方式
- 请求的 URL
- 请求的协议
- 请求头
- 浏览器版本号
- 浏览器型号
- 浏览器可以接收的数据类型
- …
- 请求主体
- get 方式:没有请求体,但是有一个 queryString
- post方式:有请求体,form data
- json格式,有请求体,request payload
响应
响应包含下面的三个信息
- 响应行
- 协议
- 响应状态码
- 200
- 404
- 500
- …
- 响应状态
- 响应头
- 服务器信息
- 服务器发给浏览器的内容(内容媒体类型、编码、长度等)
- 响应体
- 响应的实际内容
Http无状态
服务器无法判断两次请求是否来自不同的浏览器或者来自同一个浏览器,这种情况称为Http无状态。可以通过回话跟踪来解决
会话跟踪
简单概述:当浏览器第一次访问服务器时,服务器会自动分配一个 session id 给这个请求。当这个请求第二次来访问时,会携带者服务器分配给自己的 session id。服务器根据这个 session id 来区分每一个请求
可以通过代码来演示
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
当浏览器继续访问时,会将这个 session id 传给服务器
常用的 API
// 获取会话创建时间
long creationTime = session.getCreationTime();
// 获取会话是否是新的
boolean aNew = session.isNew();
// 获取会话最长有效期
int maxInactiveInterval = session.getMaxInactiveInterval();
// 设置会话最长有效期
session.setMaxInactiveInterval(2000);
// 让会话立刻失效
session.invalidate();
会话保存
在同一个会话中,我们可以保存数据。
分别使用两个方法:
req.getSession().setAttribute
req.getSession().getAttribute
添加数据
public class exer04 extends HttpServlet {
// 往session中添加数据
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getSession().setAttribute("uname","lina");
}
}
获取数据
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
成功获取到 uname
的值
然后换个浏览器访问 exer05
由于会话不同,所以获取不到 uname
的值
内部转发和重定向
内部转发
当浏览器访问服务器时,服务器在内部转发这个请求给到另外一个服务器去处理。此时浏览器是不知道内部转发了多少次的。
首先编写exer06,在这个里面吧请求转发给 exer07
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 里面只打印一语句话
public class exer07 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("exer07.....");
}
}
然后启动 Tomcat,访问exer06 观察结果。
结果可以看到 exer06 和 exer07 都会执行
浏览器地址没有变化,并且只有一次请求
浏览器重定向
使用浏览器重定向浏览器的地址会发生变化,并且会发送两次请求
使用 resp.sendRedirect("exer07");
重定向
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 打印信息
public class exer07 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("exer07.....");
}
}
启动Tomcat,访问exer06
此时浏览器的地址栏会发生改变,并且发送两次请求
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
然后再 lib_thymeleaf 文件夹右键,选择 add as library
接着吧这个 lib 包添加到 module 中
接着在 problems 中将所有内容添加到 web 中
引入文件
直接复制下面的代码到 myssm.ViewBaseServlet
中
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 类
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
配置一个前缀,一个后缀
<?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
<!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 文件
动态遍历页面数据
编写 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 ,并且命名一个临时变量 commth:text="${comm.name}"
;设置标签的内容
<!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。数据库中的数据在页面中被渲染出来
Servlet 保存作用域
request 保存数据
只在这一次的响应中有效,使用浏览器重定向时会获取不到数据,例如:
定义 demo01 往 request 中保存数据
@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 中的值
@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
然后我们使用服务器内部转发,在另外一个接口中读取数据
定义 demo03
@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
@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 的值
session 保存作用域
在 session 中保存的作用域在这一次的请求中都有效
定义 demo05
@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
@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);
}
}
启动
context 保存数据
context 保存的数据在项目运行期间都有效
编写 demo07
@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
@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)
表示传递参数,需要传递多个时用逗号隔开
<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 页面
@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
<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,完成数据的重新渲染
@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);
}
}
}
效果展示
数据删除
修改 index.html
使用 th:onclick="|deleteComm(${comm.id})|"
两个竖线表示内部可以写 ${}
符号,会自动识别里面的内容
<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
function deleteComm(id){
if(confirm("确认删除吗")){
window.location.href = "delete.do?id=" + id;
}
}
添加 deleteCommById
删除方法
@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
@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();
}
}
}
}
效果展示
数据新增
在 index 中添加新增按钮
<button th:onclick="addComm()">添加水果</button>
实现 addComm
方法
function addComm(){
window.location.href = "add.do";
}
添加 AddService
在这个方法中展示 add.html 页面
@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
接口
<!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
@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
@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();
}
}
效果展示
数据分页显示
首先添加一个查询总数的方法,在 baseDAO 中添加如下方法
/**
* 获取数据总和
* @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 中添加实现方法
@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
@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,添加分页逻辑
@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 中添加分页按钮
<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 方法
function pageNub(page){
window.location.href = "index?page=" + page;
}
效果展示
添加模糊查询功能
首先在 index.html 中添加查询表单
<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 方法
@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 语句
/**
* 查询所有数据
* @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;
}
/**
* 查询数据总条数
* @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;
}
效果展示
Servlet Mvc
优化1
我们通过上面的案例可以看到每次实现一个功能是都要新建一个 Servlet,当我们功能特别多时就要新建很多的文件。造成代码不好维护
我们可以吧多个 servlet 合并到一个文件中,实现代码如下
在这个方法类中我们只有一个 WebServlet,地址为 comm.do ,然后通过获取请求的参数 sercode 的值来调用不同的方法来实现一个接口完成多个功能
@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 来判断参数调用方法,我们可以通过反射来获取,避免大量的判断
// 通过反射来动态调用方法名
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 当成一个普通的类来使用
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
<?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
会吧方法的返回值返回,我们根据返回的值做出跳转逻辑判断
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
Servlet常用APi
读取初始化参数 getInitParameter
获取初始化参数,getServletConfig
使用方法
配置 XML 文件代码
<?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
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 配置外也可以通过注解的方式来配置初始化参数
@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);
}
}
运行获取
获取上下文 getServletContext
获取对象上下文,在初始化 init 方法中调用 getServletContext 方法
配置 XML 文件
<?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代码获取
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
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;
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
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
然后再 commServiceImp 中已用了 commDao
这种情况在实际开发中并不推荐
在实际开发中我们追求高内聚,低耦合,一个类只处理和自己本身业务相关的事情,尽量不和别的模块发生关系,下面我们就通过配置XML的方式来改变这种耦合
在 applicationConentext.xml 文件中添加代码,在xml中声明了每个类的地址和引用属性名
<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实例
public interface beanFactory {
// 根据benaid获取对应的class实例
Object getBean(String id);
}
创建接口实现类 BeanFactoryImp
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
类,去掉初始化的代码
@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
@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
的实现类
@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")
同名,这样才能起到这个接口的拦截作用
然后启动服务,查看打印的顺序
除了设置单个过滤器,也可以设置过滤器链,表示一个接口有多个过滤器
分别新建 filter2
,filter3
,编写文件代码
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() {
}
}
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")
注释掉,观察控制台打印的顺序
过滤器的使用
添加过滤器,设置请求编码为 UTF-8
@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
中关于设置编码的操作
使用过滤器添加事务管理
新建 com.songzx.trans.Transmanager
类,这个类有三个方法,专门负责事务的开启和提交
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
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 结尾的请求
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
可以监听上下文的创建和销毁
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包
按照文件目录创建文件夹,然后把对应文件夹内的文件加载到其中
然后选择 Build Artifact
选择刚刚打包的文件进行build
随后会生成一个 jar 包,我们引用即可
IDEA导出jar包插件
https://blog.csdn.net/zhyhang/article/details/89060408
使用过滤器处理登录验证
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处理浮点数精度问题
首先看问题
当我们直接使用Double类型和一个整数类型做乘法运算时,可能会出现精度问题,如上图所示,有很多的小数位
解决办法,使用 BigDecimal,BigDecimal 只接受字符串类型,所以我们要把 Doubel 和 int 都转成字符串来计算
使用 kaptcha 生成验证码
首先导入jar包: kaptcha-2.3.2.jar
,下载地址:https://www.jb51.net/softs/546820.html
然后在 web.xml
中添加配置代码
<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
页面中使用
<body>
<img src="kaptcha.jpg"/>
</body>
kaptcha 在生成验证码的同时,会在 session 中保存一份当前的验证码数据,session 的 key 为:KAPTCHA_SESSION_KEY
,我们可以通过读取 session 来校验前端输入的验证码是否正确
@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);
}
}
运行代码效果
关于 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 |
配置属性的使用方法,以设置文字大小为例
<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>
效果:
使用axios发送数据响应普通文本
首先前端发送请求
<!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接收请求并响应
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数据
首先前端发送请求
<!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 类,属性名和前端传递过来的一样
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 + '\'' +
'}';
}
}
后端接收请求:
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 字符串
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/47013.html