JavaWeb¶
约 7769 个字 303 行代码 预计阅读时间 30 分钟
在 Idea 中的一些配置:
- 修改在浏览器中项目的名字:将
http://localhost:8080/demo2_war_exploded/
修改为http://localhost:8080/day09/
- 不用改 html 还要重启 server。
Html¶
查看标签作用:HTML Cheat Sheet & Quick Reference (cheatsheets.zip)
常用标签
- 表格跨行效果:
rowspan
和colspan
- 加粗:
b
,删除线:s
,下划线:s
,斜体:i
- 原样输出:
pre
- 在一行显示:
span
表单¶
id
属性用于为 HTML 元素提供一个唯一的标识符。它在 CSS 和 JavaScript 中非常有用。id
也用于<label>
标签的for
属性,以建立标签和对应输入框之间的关联。这样,当用户点击标签时,与之关联的输入控件会获得焦点。name
属性的主要作用是在表单提交时,标识表单控件的数据,并成为数据的一部分发送到服务器。action
提交的地址
<form method="POST" action="api/login">
<label for="mail">Email: </label>
<input type="email" id="mail" name="mail">
<br/>
<label for="pw">Password: </label>
<input type="password" id="pw" name="pw">
<br/>
<label for="city">City:</label>
<select name="city" id="city">
<option value="1">Sydney</option>
<option value="2">Melbourne</option>
<option value="3">Cromwell</option>
</select>
<br/>
<input type="submit" value="Login">
<br/>
<input type="checkbox" id="ck" name="ck">
<label for="ck">Remember me</label>
</form>
效果:
CSS¶
优先级
- 由上到下,由外到内,优先级从低到高。
style
(直接在标签列表写)>id
选择器 >class
选择器 > 标签名选择器
选择器¶
-
使用 标签名 作为选择器的名称
-
class
选择器 :<div class="abc></div>"
-
id
选择器:<div id="efg"></div>
-
关联选择器:
<div><p> 123 </p></div>
-
组合选择器
-
伪元素选择器, 例如超链接的状态
<a href="https://www.bing.com/"> 超链接 </a>
盒子模型¶
边框:border
, 内边距:padding
,外边距:margin
布局的定位¶
布局的漂浮:float
left
: 后面的 div 到右边right
: 后面的 div 到左边
布局的定位:position
absolute
:- 从文档流中脱出:元素完全脱离文档流,不占据空间。
- 定位方式:通过
top
、bottom
、left
、right
属性进行定位。其位置是相对于最近的已定位祖先元素(relative
、absolute
、fixed
)。
relative
:- 不脱离文档流:元素仍然占据空间,但可以通过偏移属性移动。
- 定位方式:通过
top
、bottom
、left
、right
属性进行偏移。其位置是相对于其正常位置进行偏移。
JavaScript¶
语法
-
JS 的特点:交互性,安全性(不能访问本地的文件),跨平台性
-
JS 的类型:
string,number,boolean,null,undefined
-
声明变量:
var a = 10
,定义数组:var arr = [1,"4",true]
(是 Array 数组,类型可以不同) -
==
比较的只是值,===
比较的是类型和值。 -
JS 的函数 ,不支持重载。(如果有同名函数,则实际执行的是最后一个(顺序上)
-
全局变量:在函数外面定义的变量; 局部变量:函数内部定义的变量。
-
嵌入 js 文件放在
</body>
后面。
BOM¶
浏览器对象模型:browser object model
- navigator 对象:获取浏览器的信息
- screen 对象:获取屏幕的信息
- location 对象:请求和设置 URL 地址:
location.href
- history 对象:请求的 URL 的历史记录:
- 回退:
history.back();
或histroy(-1);
- 前进:
history.forward();
或history(1);
- 回退:
- window 对象:
- 页面弹框:
window.alert()
,简写alert()
- 确认框:
window.confirm()
- 打开新窗口:
window.open()
- 关闭窗口:
window.close()
- 做定时器:
setInterval("js代码", 毫秒数)
每几秒执行一次 - 倒计时:
setTimeout()
只会执行一次。
- 页面弹框:
DOM¶
文档对象模型:document object model
- Element 对象:即标签对象:
span
- 属性对象:标签下的属性,即
id
- 文本对象:即
hahaha
DOM 解析 HTML¶
根据 HTML 的层级结构,在内存中分配一个树形结构。
- 根结点为
html
document
对象:整个 html 文件element
对象:标签对象- 属性对象
- 文本对象
Node
节点对象:是所有对象的父对象
document 对象¶
查找节点:
- 指定 id 的第一个对象:
getElementById()
- 指定名称的对象 集合(数组):
getElementsByName()
- 指定标签的对象 集合(数组):
getElementsByTagName()
节点的方法
- 创建指定标签的元素:
creatElement("tagname")
- 创建文本:
creatTextNode("message")
- 添加子节点到末尾:
appendChild()
可以实现剪贴的效果(把上个文本的内容弄到别处) - 在节点之前插入节点:
insertBefore(Node,NewNode)
- 替换节点:
replaceChild(newNode, oldNode)
通过父节点替换 - 复制节点:
cloneNode(true)
- 删除节点:
removeChild()
通过父节点删除。
新创建一个对象过程:
- 使用
document.createElement()
方法创建元素。 - 使用
setAttribute()
方法给元素添加属性。 - 使用
document.createTextNode()
创建文本节点。 - 使用
appendChild()
方法将文本节点添加到元素中。
element 对象¶
element 对象:标签对象。
常用方法
-
获取属性的值:
getAttribute()
-
设置属性的值:
setAttribute()
-
删除属性:
removeAttribute()
,但不能删除value
Node 对象¶
任何元素都可以看作是一个 Node,比如document对象,element 对象,属性对象,文本对象。
属性:
- 类型:
nodeType
- 名称:
nodeName
- 值:
nodeValue
- 父节点:
parentNode
- 子节点:
childNodes
- 同辈节点:
nextSibling
和previousSibling
interHTML¶
作用
- 获取文本内容
- 想标签里面设置内容
- 常和
div
和span
搭配来用
XML¶
可扩展标记型语言:eXtensible Markup Language
经常用在文件配置,标签可以自己定义。
文档声明¶
文档声明必须放在第一行。
属性:
- version:xml 的版本。
1.0
或1.1
- encoding:xml 的编码。
gbk
或utf-8
- standalone: 是否需要其他依赖文件
中文乱码问题
encoding
中的编码和保存文件的编码格式不同。(读和写的格式不同)
xml文件中空格和换行都会当成内容和来解析。
xml 中 注释不能嵌套
特殊符号如:<
,>
,&
需要特殊字符转义才能正常使用。
<
对应的是:<
>
对应的是:>
&
对应的是:&
xml 的约束¶
引入方式
- 引入外部的本机dtd文件:
<!DOCTYPE xx SYSTEM "dtd文件的路径">
- 引入网络上的dtd文件:
<!DOCTYPE 根元素 PUBLIC "DTD名称" "URL">
XML 的解析方式:dom 和 sax 。
- sax解析过程:采用事件驱动,边读边解析,从上到下,一行一行的解析,解析到某一个对象,返回对象名称,但不能实现增删改操作
- dom解析过程:根据xml的树形结构,把xml的标签,属性和文本豆封装成对象,可以实现增删改操作。可能产生内存溢出。
dom4j 解析 xml¶
拿到 document (dom4j
包下的)
SAXReader
creates a DOM4J tree from SAX parsing events.
document
的父接口是 Node
- 获取根节点:
getRootElement()
,返回 element 类型
获取 element 的子标签
- 获取所有一层子标签:
elements()
- 获取制定标签的子标签:
elements("qname")
- 获取制定标签的第一个子标签:
element("qname")
查询¶
public static void selsctName() throws DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("/Users/selfknow/program/Heima/code/web0/com/src/p1.xml");
// 得到根节点
Element root = document.getRootElement();
// 得到 p1
List<Element> list = root.elements("p1");
for(Element ele: list) {
Element name1 = ele.element("name");
System.out.println(name1.getText());
}
}
添加¶
在第一个 p1 标签末尾添加一个元素
public static void addSex() throws Exception {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("/Users/selfknow/program/Heima/code/web0/com/src/p1.xml");
// 得到根节点
Element root = document.getRootElement();
// 得到第一个p1标签
Element p1 = root.element("p1");
Element p11 = p1.addElement("Sex");
p11.setText("man");
// 写回 xml
FileOutputStream sc = new FileOutputStream("/Users/selfknow/program/Heima/code/web0/com/src/p1.xml");
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter xmlWriter = new XMLWriter(sc,format);
xmlWriter.write(document);
xmlWriter.close();
}
创建一个元素
删除¶
得到该元素,然后获取该元素的父节点(getParent()
方法),通过父节点删除该元素。
xpath¶
可以直接获取到某个元素
/AAA/DDD/BBB
一层一层的找,找到 BBB//BBB
找到所有元素为 BBB 的/*
所有元素//BBB[@id='b1']
BBB上有id属性,且id属性为b1
Tomcat¶
Web 资源
-
静态资源:html
-
动态资源:JSP/Servlet
找到 tomcat 路径的bin 文件夹
- 启动:
sh startup.sh
- 关闭:
sh shutdown.sh
默认端口:8080
在
tomcat路径\conf\server.xml
中修改端口号
在 webapps
中写项目。
conf/server.xml¶
在 Tomcat 中,conf/server.xml
文件是一个非常重要的配置文件,它用于配置整个 Tomcat 服务器的工作环境。这个文件包含了定义 Tomcat 服务器各个组件如何运行的各种设置。以下是一些 server.xml
中常见配置项的说明:
-
Server:
<Server>
元素是顶层元素,代表整个 Tomcat 服务器。这个元素通常包括属性如端口号(用于接受关闭命令)和关闭命令的字符串。 -
Service:
<Service>
元素用来定义一个服务,它包括一个或多个连接器(Connector)和一个引擎(Engine)。服务代表可以处理进来的请求的一组连接器和引擎。 -
Connector:
<Connector>
元素配置如何接收网络连接。常见的配置包括端口号、使用的协议(比如 HTTP/1.1、AJP)、以及针对性能和安全的各种设置(如连接超时、TLS/SSL 配置等)。 -
Engine:
<Engine>
元素代表请求处理的中央组件,它处理通过连接器接收的所有请求。一个引擎可以包含多个主机(Host),即虚拟主机。 -
Host:
<Host>
元素定义一个虚拟主机,即一个域名对应的网站容器。常见的配置包括主机名(如localhost
)、应用部署的目录(appBase
)以及日志配置。 -
Context:
<Context>
元素定义一个 Web 应用的运行环境,可以设置如应用的路径、文档根目录和与特定 Web 应用相关的参数。 -
Realm:
<Realm>
元素用于配置用户认证和授权的环境。Tomcat 支持多种类型的 Realm 实现,如基于内存的用户列表、基于数据库的用户验证等。 -
Executor:
<Executor>
元素用于定义线程池,可以被多个 Connector 共享。这有助于优化线程资源的使用和管理。 -
Valve:
<Valve>
元素用于在特定的管道(pipeline)点插入特定功能,例如访问日志的记录、IP 过滤等。
这些配置项的组合定义了 Tomcat 服务器的工作方式,包括它如何处理入站连接、如何部署和运行 Web 应用程序、以及如何管理安全和日志记录。正确地配置这些参数对于优化服务器性能和保证运行安全至关重要。
http 协议¶
Referer:请求来自那个页面,可以用来做防盗链。
http-equiv
属性常见的用途包括:
-
内容类型(Content-Type):
这设置了文档的字符集,告诉浏览器使用 UTF-8 编码来解析页面。 -
缓存控制(Cache-Control):
这告诉浏览器不要缓存页面的内容,每次访问时都需要从服务器重新加载。 -
过期控制(Expires):
设置页面的过期时间,过了这个时间点后,页面内容需要从服务器重新加载。 -
自动刷新(Refresh):
这会在 30 秒后自动将用户重定向到指定的 URL 地址。 -
X-UA-Compatible(主要用于控制 Internet Explorer 的兼容模式):
这告诉 Internet Explorer 使用最新的渲染引擎。
通过使用 http-equiv
属性,网页开发者可以在不更改服务器配置的情况下,控制页面的某些行为和设置,提高网页的灵活性和响应性。
响应码
- 200: 请求成功
- 404: 请求的资源没有找到
- 500: 服务器内部出现了错误
- 302: 重定向
Servlet¶
Servlet 的作用是处理请求,服务器会把接收到的请求交给 Servlet 处理。
有三种实现方式
- 实现
javax.servlet.Servlet
接口 - 继承
javax.servlet.GenericServlet
类 - 继承
javax.servlet.http.HttpServlet
类
Servlet 接口¶
Servlet 由我们来写,但对象有服务器来实现,并由服务器来调用相应的方法。
生命周期方法
- init(ServletConfig);
- service(ServletRequest, ServletResponse) :每次处理请求时都会被调用
- destroy();
特性:
- 单例,一个类只有一个对象
- 线程不安全
如何让浏览器访问 Servlet¶
- 给 Servlet 指定一个 Servlet 路径
- 浏览器访问路径
在 web.xml 中配置路径
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>org.example.AServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/AServlet</url-pattern>
</servlet-mapping>
新版的 Idea 可以直接利用注解来配置 Servlet
ServletConfig¶
HttpServlet¶
重写 doGet()
和 doPost()
方法。不重写会报 405
错误:http.method_get_not_supported
.
ServletContext¶
一个项目只有一个 ServletContext 对象。
- 在服务器启动时创建对象,服务器关闭时销毁对象
对象的作用是 在整个 Web 应用的动态资源之间共享数据。例如 AServlet 向 ServletContext 对象中保存了一个值,然后在 BServlet 中就可以获取这个值。
操作数据的方法:
void setAttribute(String name,Object value)
Object getAttribute(String name)
void removeAttribute(String name)
Enumeration getAttributeNames()
获取所有域属性的名称String getInitParameter(String name)
得到初始化参数
获取资源
getRealPath()
获取真实路径getResourceAsStream()
获取资源的路径后,在创建出输入流对象
Response¶
每次请求服务器时,服务器会把客户端的请求数据封装到 request 对象中,request 就是请求数据的载体。
服务器还会创建 response 对象,这个对象与客户端链接在一起,他可以用来向客户端发送相应。
状态码:
- 200表示成功,302表示重定向,404表示客户端错,500表示服务器端错
相应头:Content-Type,Refresh,Location
-
void setHeader(String name, String value)
-
void setIntHeader(String name, int value)
-
发送 302:设置 Location 头,完成重定向
-
定时刷新:设置 Refresh 头
-
禁止浏览器缓存:Cache-Control、pragma、expires
相应体:
-
response 的两个流
- 两个流不能同时使用
- ServletOutputStream,用来发送字节数据
- PrintWriter,用来发送字符数据
-
快捷重定向:
response.sendRedirect(String location)
Request¶
获取客户端ip:resquest.getRemoteAddr()
获取请求方式:request.getMethod()
获取请求头¶
String getHeader(String name)
int getIntHeader(String name)
获取请求URL¶
- 获取请求 URL,不包含参数的整个请求路径:
String getRequestURL()
. 例:http://localhost:8080/day10/AServlet
- 获取请求 URI,项目名 + Servlet 路径:
StringRequestURI()
. 例:/day10/AServlet
- 获取参数部分,问号后面的部分:
String getQueryString()
. 例:username=xxx&password=yyy
- 获取 Servlet 路径:
String getServletPath()
. 例:/AServlet
- 获取项目名:
String getContextPath()
. 例:/day10
- 获取服务器端口:
String getServerPort()
. 例:8080
- 获取服务器名:
String getServerName()
. 例:localhost
- 获取协议:
String getScheme()
. 例:http
获取请求参数¶
String getParameter(String name)
单值String[] getParameterValues(String name)
多值Map<String,String[]> getParameterMap()
获取所有请求参数
请求转发和请求包含¶
RequestDispatcher
的主要作用有两种:
- 转发(Forward):通过
forward
方法将请求从一个 Servlet 转发到服务器上的另一个资源(如另一个 Servlet、JSP 文件等)。当前 Servlet 可以设置相应头,但不能设置相应体。 留头不留体 - 包含(Include):通过
include
方法在响应中包含另一个资源的内容。这样可以在当前响应中组合多个资源的输出,实现资源的复用。 留头又留体
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/CServlet");
// 请求转发
requestDispatcher.forward(req,resp);
// 请求包含
requestDispatcher.include(req,resp);
request 域¶
Servlet 中三大域对象:request,session,application。都有如下三个方法
void setAttribute(String name,Object value)
Object getAttribute(String name)
void removeAttribute(String name)
属性:Attribute 是 Servlet 和 Servlet 在转发或包含时用来传递值。
请求转发和重定向的区别¶
- 请求转发时一次请求一次响应,而重定向时两次请求两次响应
- 请求转发地址栏不变化,而重定向会变化
- 请求转发只能转发到本项目其他Servlet(即只能在本服务器),重定向无所谓。
- 请求转发时服务器端的行为,只需给出转发的 Servlet 路径。
转发:req.getRequestDispatcher(path).forward(req,resp);
重定向:resp.sendRedirect(req.getContextPath() + path);
重定向不能携带 req 中的信息
URL 编码¶
在客户端和服务器之间传递中文时,转换成合适的方式:把中文转化成 %
后面跟随两位的 16进制(utf-8编码)
- URL 编码:
URLEncoder.encoder(String s,String encoding)
- URL 解码:
URLDecoder.decode(String s,String encoding)
JSP¶
内置对象¶
out
: 等同于response.getWriter()
,用来向客户端发送文本数据。request
: 即 HttpServletRequest 类的对象response
: 即 HttpServletResponse 类的对象application
: 即 ServletContext 类的对象session
: 即 HttpSession 类的对象pageContext
:页面上下文对象。
JSP 的四大域
- 整个应用程序:application
- 整个会话:session
- 一个请求:request
- 一个 jsp 页面:pageContext
pageContext
的 findAttribute(String name)
方法在 JSP 开发中非常有用,它提供了一种便捷的方式来查找属性,无需开发者显式指定属性应该在哪个作用域中查找。这个方法会按照一定的顺序在不同的作用域中搜索指定的属性:
- Page Scope:首先在页面作用域中搜索属性。
- Request Scope:如果在页面作用域中找不到,则继续在请求作用域中搜索。
- Session Scope:如果请求作用域中也找不到,且存在会话(Session),则在会话作用域中搜索。
- Application Scope:最后在应用程序作用域中搜索。
如果属性在上述任何作用域中被找到,findAttribute
方法会返回该属性的值。如果在所有作用域中都找不到,它将返回 null
。
Jsp 的作用¶
jsp:Java server pages。
- 在原有 html 的基础上添加 java 脚本,构成 jsp 页面。
- 作为请求发起页面,例如显示表单、超链接。
- 作为请求结束页面,例如显示数据
- Servlet:作为请求中处理数据的环节
jsp 的组成¶
jsp 包含 html,java脚本,Java标签
jsp 中无需创建即可使用的对象一共有九个,被称为 9 大内置对象。例如:request 对象、out 对象
三种java脚本
<% ... %>
Java 代码片段<%= ... %>
Java 表达式,常用于输出<%! ... %>
声明,用来创建类的成员变量和成员方法
还有一种输出方式
jsp 的注释:<%-- ... --%>
<%@include ... %>
: 静态包含
<%taglib ... %>
: 导入标签库
动作标签:
<jsp:forward>
:请求转发<jsp:include xxx>
:动态包含,会调用 xxx 的输出结果(形成 xxx.class),区别于 静态包含(<%@include>
),静态包含最终只会形成一个 class 文件。<jsp:param>
: 用来传递参数。
示例¶
Servlet 端
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String s1 = req.getParameter("num1");
String s2 = req.getParameter("num2");
int num1 = Integer.parseInt(s1);
int num2 = Integer.parseInt(s2);
int sum = num1 + num2;
req.setAttribute("sum",sum);
// 转发到 result.jsp
req.getRequestDispatcher("/add/result.jsp").forward(req,resp);
}
form 页面:
<form action="/day10/CServlet" method="post">
整数1: <input type="text" name="num1"> <br>
整数2: <input type="text" name="num2"> <br>
<input type="submit" value="submit">
</form>
result 页面:
jsp 原理¶
jsp 其实是一种特殊的 Servlet
- 当 jsp 页面第一次被访问时,服务器会把 jsp 编译成 java 文件
- 然后再把 Java 文件编译成 class 文件
- 然后创建该类对象
- 最后调用对象的
service()
方法 - 第二次请求同一 jsp 时,直接调用
service()
方法
Cookie¶
先有服务器保存 Cookie 到浏览器,下次浏览器请求服务器时把上一次请求得到的 Cookie 再归还给服务器。
向浏览器添加Cookie:response.addCookit()
,设置的属性(Set-Cookie
)
获得浏览器归还的Cookie:resquest.getCookie()
, 注意是从 request 拿到的。
Request: 浏览器发给服务器的
response: 服务器发给浏览器的
maxAge¶
Cookie 的最大生命,即 Cookie 可保存在硬盘中的最大时长,单位为秒。cookie.setMaxAge(60)
- maxAge > 0: 浏览器会把 cookie 保存到硬盘上。
- maxAge < 0: cookie 只会在浏览器内存中存在,关闭浏览器是,cookie 消失
- maxAge = 0: 浏览器会马上删除这个 cookie
path¶
cookie 的 path 默认值:当前访问路径的父路径。
- 例如当访问
/day11/jsps/a.jsp
时,path 默认值为/day11/jsps
当访问路径包含 cookie 路径时,request 中会有对应的 cookie 值。
Session¶
会话(Session),会话范围是某个用户从首次访问服务器开始,到该用户关闭浏览器结束。
session 是服务器端对象,保存在服务器端。
HttpSession 是 Servlet 三大域之一(request,seesion,application(ServletContext) ).
void setAttribute(String name, Object value)
Object getAttribute(String name)
void removeAttribute(String name)
服务器会为 每个用户当前浏览器 创建一个 session 对象,直到关闭浏览器结束
- Servlet:
request.getSession()
- Jsp: session 是内置对象,可以直接使用。
Session 原理¶
当访问 jsp 文件(内置 Session 对象)或者在 Servlet 中调用 response.getSession()
方法,会在 Cookie 中创建 JSESSIONID
, JSESSIONID
是会话的唯一标识。
其他方法
String getId()
: 获取 sessionIdvoid invalidate()
: 让 seesion 失效。int getMaxInactiveInterval()
: 获取 session 的最大不活动时间,默认为 30 分钟。
session 依赖 Cookie,目的是让客户端发送请求时归还 sessionId,这样才能找到对应的 session。
response.encodeURL(String url)
- 该方法会对 URL 进行智能的重写
- 如果没有 cookie 会重写 url
- 有 cookie 中的 sessionId 则不重写
- 示例:
/day10/AServlet;jsessionid=E9E51CDF937140AD0A8B231622C493FB
- 使得每个 URL 都有当前的 sessionId
MVC¶
MVC
- M:model 模型
- V:view 视图
- C:controller 控制器
JavaWeb 三层框架¶
Web 层:与 Web 相关的内容(Servlet,JSP,Servlet相关API:request、response、session、ServletContext)
业务逻辑层:业务对象(Service)
数据层:操作数据库(DAO,Data Access Object)
实体类:JavaBean
登陆注册项目¶
功能:
- 注册
- 登陆
分析
JSP:
- login.jsp:登陆表单
- regist.jsp:注册表单
- index.jsp:主页(只有登陆成功才能看到)
Servlet:
- LoginServlet
- RegistServlet
Service:
- UserService:与用户相关的业务类
Dao:
- UserDao:与用户相关的数据类。
Domain:
- User (对应数据库,还要对应所有表单)
- username
- password
- verifyCode
数据库设计(暂时用 xml 顶替)
user.xml
注册¶
regist.jsp
- 第一步:提交表单
RegistServlet(作为服务员,拿到前端的数据,调用service层中的方法)
- 封装表单数据,封装到 User 对象
- 调用 UserService 中的注册方法
- 注册成功
- 注册失败,则抛出异常,错误信息保存到 request 域中,并转发到注册页面。
UserService#regist()
- 注册失败会抛出一个自定义的异常,添加异常信息。
- 校验用户名是否已被注册(通过用户名查询用户)
- 已被注册,抛出异常(“用户名已被注册”)
- 添加用户
UserDao:通过业务分析,得到结果:需要实现两个方法
- 按用户名查询用户对象
- 插入一个用户到数据库中
分析上是正着分析:表单,Servlet,Service,Dao
实际开发:表单,Dao,Service,Servlet
EL 表达式:有就显示,没有就不显示,按照 scope 逐层搜索。
修改1:实现 Dao 工厂¶
- 把 UserDao 修改为接口,然后把原来的 UserDao 修改类名为 UserDaoImpl
- 修改 UserService 中对 UserDao 的实例化:
private UserDao userDao = DaoFactory.getUserDao();
- 创建 DaoFactory,提供
getUserDao()
方法
修改2: 实现数据库连接¶
实现 JdbcUserDaoImpl
继承 UserDao 接口。
采用 ORM 对象关系映射,把查询到数据封装为 User 对象,再返回。
在尝试调用一个对象的方法之前,必须确保该对象不是 null。如果你试图关闭一个未初始化或已经设置为 null 的对象,如 pstmt 或 con,直接调用 close() 方法会抛出 NullPointerException。通过检查对象是否为 null 可以避免这种异常的发生。
JDBC¶
Jdbc 协议格式:jdbc:mysql://localhost:3306/数据库名称
创建连接:Connection con = DriverManager.getConnection(url,username,password);
语句:Statement stmt = con.createStatement()
- 增删改:
stmt.excuteUpdate(sql)
,返回影响的行数 - 查询:
stmt.excuteQuery(sql)
,返回查询结果ResultSet
事务¶
事务的四大特性:原子性是一个动作,一致性是一个结果。
- 原子性:事务中所有操作是不可分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
- 一致性:事务执行前后,数据库的状态必须从一个一致性状态变到另一个一致性状态。
- 隔离性:并发操作中,不同事务之间应该隔离开来,使得每个事务不会相互干扰。
- 持久性:一旦事务提交成功,事务中的所有数据操作都会被持久化到数据库中。
并发事务问题
读问题:
- 读脏数据:读到另一个事务的尚未提交的修改。
- 不可重复读:在一个事务内,多次读取同一数据不一样。(因为另一个事务对该数据做了修改)
- 幻读:对同一张表的两次查询不一样。(因为另一事务插入了一条记录)
事务的四大隔离级别
- 串行化:三种读问题都能解决,性能最差。
- 可重复读:处理了读脏数据,不可重复读
- 读已提交数据:处理了读脏数据
- 读未提交数据:啥也不处理(
事务的应用¶
mysql中的事务
开始事务:start transaction
持久化事务:commit
回滚事务:rollback
jdbc中的事务
同一事务中所有的操作,都必须在用 同一个 Connection 对象。
用这样的操作保证事务的 原子性: 要么全做,要么全不做。
try{
con.setAutoCommit(false); // 开启事务
...
con.commit(); // 持久化事务
} catch() {
con.rollback(); // 回滚事务
}
用这样的操作保证事务的 隔离性 : 通过确保只有当前线程的事务连接(con)才会被关闭,而其他连接(connection)如果不是当前事务的连接,则不会被关闭。这种处理方式确保了在同一个事务上下文中,操作的是同一个数据库连接,避免了多线程环境下的连接混淆,从而保证了事务的隔离性。
public static void releaseConnection(Connection connection) throws SQLException {
Connection con = tl.get();//获取当前线程的事务连接
if(connection != con) {//如果参数连接,与当前事务连接不同,说明这个连接不是当前事务,可以关闭!
if(connection != null &&!connection.isClosed()) {//如果参数连接没有关闭,关闭之!
connection.close();
}
}
}
BaseServlet¶
我们希望在一个 Servlet 中可以有多个请求处理方法
- 客户端发送请求时,必须多给出一个参数,来说明调用的方法
- 请求处理方法的签名必须与 service 相同,即返回值和参数,以及声明的异常都相同。
拿到方法名之后,我们可以通过反射的方法调用 service 中的方法。
- 反射调用:
method.invoke(this,req,resp)
- 正常调用:
this.addUser(req,resp)
ThreadLocal¶
ThreadLocal 内部是一个 Map
class TL<T> {
private Map<Thread,T> map = new HashMap<Thread,T>();
// 用当前线程做 key
public void set(T data) {
map.put(Thread.currentThread(),data);
}
public T get() {
return map.get(Thread.currentThread());
}
public void remove() {
map.remove(Thread.currentThread());
}
}
DbUtils¶
Commons-dbutils 中 QueryRunner
: 两个主要的方法是update和query,它们分别用于执行更新操作和查询操作。
update
方法¶
update
方法用于执行数据库更新操作,例如INSERT
、UPDATE
和DELETE
语句。它的主要作用是执行不返回结果集的SQL语句,并返回受影响的行数。
query
方法¶
query
方法用于执行数据库查询操作,并将结果集转换为相应的Java对象。它需要传入一个结果集处理器(ResultSetHandler
),用来定义如何将结果集中的数据映射到Java对象中。
- sql:要执行的SQL查询语句。
- handler:结果集处理器,用于将结果集转换为所需的Java对象。常用的处理器有
BeanListHandler
、ScalarHandler
、MapListHandler
等。 - params(可选):SQL语句中的参数。
返回值¶
- 返回值是泛型类型,由结果集处理器决定。例如,
BeanListHandler
会返回一个List
,其中每个元素是一个映射到JavaBean的对象。
常用的结果集处理器¶
- BeanListHandler:将结果集中的每一行映射到一个JavaBean对象,并返回一个包含这些对象的列表。
- ScalarHandler:用于处理单行单列的结果,例如聚合函数的结果。
- MapListHandler:将结果集中的每一行映射到一个
Map
对象,并返回一个包含这些映射的列表。
顾客管理系统项目¶
添加用户¶
CustomerServlet#addCustomer()
- 封装表单数据到 Customer 对象
- 补:uuid
- 传递给 Service 层
- 保存成功信息,转发到 msg
用这种方法传递 method 参数,使得用户不可见:
<form action="${pageContext.request.contextPath}/CustomerServlet" method="post">
<input type="hidden" name="method" value="addCustomer">
编辑用户¶
先拿到顾客的信息(拿到 cid),然后做修改(做回显),再提交表单修改信息。
如何从前端拿到参数:(无论是从 URL 还是从表单中)
req.getParameter(String name)
获取当前Web应用程序的上下文路径的表达式。
${pageContext.request.contextPath}
要将 List<Customer> params
转化成 Object[]
类型的,QueryRunner
下的 query()
方法才能接受,具体方法如下:
params.toArray();
监听器¶
在 IDEA 中实现一个监听类
- 写一个监听器类:实现某个监听器接口;
- 注册:直接在类上进行标注
@WebListener
注解
事件源:三大域。
- ServletContext
- 生死监听
- 属性监听
- HttpSession
- 生死监听
- 属性监听
- ServletRequest
- 生死监听
- 属性监听
生命周期:
- ServletContext:应用程序启动时创建,关闭时销毁。
- HttpSession:调用
request.getSession()
时创建,超时/关闭浏览器 会话 结束。 -
ServletRequest:动态请求时创建,响应后销毁。
过滤器¶
在 IDEA 中实现一个过滤器
- 写一个过滤器类:实现 Filter 接口
- 注册:直接在 类上 (而不是方法上)进行标注
@WebFilter(urlPatterns = "/*", dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD})
注解
Java Servlet API 中的 Filter 接口用于在请求达到 Servlet 或响应发送给客户端之前,对它们进行预处理和后处理。Filter 可以改变请求和响应,并且能够阻止请求被进一步处理。
过滤器的 doFilter()
方法是 Filter 接口中最关键的方法,它负责实现过滤功能。
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 做一下预处理操作 ...
System.out.println("预处理");
// 将请求和响应传递给下一个过滤器或最终的目标资源
chain.doFilter(request,response);
// 后处理操作 ...
System.out.println("后处理");
}
- FilterChain chain:过滤器链,负责管理一个或多个过滤器的执行,以及过滤器后的资源访问。
四种拦截方式:
- REQUEST(默认)
- FORWARD
- INCLUDE
- ERROR
filterDemo¶
分ip统计网站的访问次数¶
在过滤器中做一些预处理,用 Map<String,Interger> map
来记录 ip 和访问次数。
- map 的创建:在服务器启动时完成创建 map,并存放到 ServletContext 中
- 来一个 ip ,记录一下。
- 写一个页面展示 map 的数据。
用监听器来写创建 map:它允许你在应用程序启动时进行初始化操作,并且确保在任何 Servlet 或 Filter 使用 map 之前它已经被创建并存放在 ServletContext 中。
- 实现:
ServletContextListener
接口。
Ajax¶
AJAX(Asynchronous JavaScript and XML):异步的 js 和 xml
- 发一个请求后,无需等待服务器的响应,然后就可以发第二个请求。
- 可以使用 js 接受服务器的响应,然后使用 js 来局部刷新。
发送异步请求¶
GET请求
在前端可以这样写:
<script type = "text/javascripts">
// 文档加载完毕后执行
window.onload = function() {
// 给按钮你的点击时间注册监听
var btn = document.getElementById("btn");
btn.onclick = function() {
// ajax 异步 get 请求
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = JSON.parse(xhr.responseText);
console.log(response);
}
};
xhr.send();
}
}
</script>
POST 请求
要设置 Content-Type
请求头
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
参数:xhr.send(参数)
XStream¶
XStream 可以 JavaBean 序列化为 XML,或将 XML 反序列化为 Java 对象。
- 设置根节点别名:
xstream.alias("root", RootClass.class);
- 设置类别名:
xstream.alias("user", User.class);
- 设置字段别名:
xstream.aliasField("name", User.class, "username");
- 忽略特定成员:例:
xstream.omitField(User.class, "password");
JSON¶
语法¶
属性名必须使用 双引号 扩起来。
属性值:
- null
- 数值
- 字符串
- 布尔值:true 或 flase
- 数组:使用
[ ]
扩起来 - 对象:使用
{ }
扩起来
核心类:
- JSONObject:相当于 Map
- JSONArray:相当于 List
Ajax 执行过程
省市联动¶
dao:提供两个方法
- 查询所有省
- 通过省名称查询指定的市
servlet:两个方法
- 把所有省转化成 json,发送给客户端
- 获取省名称,查询所有市,转化为json,发送给客户端
web:
- 访问 servlet,得到所有省,显示在
<select id="province">
下 - 给
<select id="province">
添加 onchange 事件监听,获取选择的省名称,访问 servlet ,得到所有市,显示在<select id="city">
使用 outerHTML 可以获取或设置整个元素(包括其标签和内容)的 HTML。
// 查询所有省
fetch('<c:url value='/ProvinceServlet'/>')
.then(response => response.json())
.then(allProvince => {
for (const province of allProvince) {
// 创建 <option> 元素
var optionEle = document.createElement("option");
optionEle.value = province.pid; // 实际值
optionEle.innerHTML = province.name; // 显示值
// 添加到下拉框中
document.getElementById("province").appendChild(optionEle);
}
});
// 给 province 添加 onchange 监听
document.getElementById("province").onchange = function () {
fetch('<c:url value='/CityServlet'/>', {
method: "POST",
headers: {
'Content-Type': 'application/x-www-form-urlencoded;'
},
body: "pid=" + this.value,
})
.then(response => response.json())
.then(citys => {
// 清空原有的 city
var cityList = document.getElementById("city");
cityList.innerHTML = cityList.options[0].outerHTML;
for (const city of citys) {
// 创建 <option> 元素
var optionEle = document.createElement("option");
optionEle.value = city.cid; // 实际值
optionEle.innerHTML = city.name; // 显示值
// 添加到下拉框中
cityList.appendChild(optionEle);
}
});
};
图书商城¶
用户¶
用户模块
- 注册
- 邮箱激活
- 登陆
- 退出
分类模块
- 查看所有分类
图书模块
- 查询所有图书
- 按分类查询图书
- 查询图书详细(按 id 查询)
购物车模块
- 添加购物车条目
- 清空所有条目
- 删除特定条目
- 我的购物车(按用户查询购物车)
订单模块
- 生成订单
- 我的订单(按用户查询订单)
- 按 id 查询订单
- 确认收货
- 付款功能(只是跳转到银行页面)
- 付款回调功能(由银行来调用这个方法,表示付款成功)
管理员后台¶
管理员
- 登录
分类管理
- 添加分类
- 查看所有分类
- 删除分类
- 按 id 查询
- 修改分类
图书管理
-
查看所有图书
-
按 id 查询
-
添加图书(上传图片)
-
删除图书
-
修改图书
订单模块
- 查询所有订单
- 按状态查询订单
- 发货
注册 和 登录¶
Servlet
- 用来传送 对象,参数
- 然后调用 Service 的具体方法
- 处理 Service 中的异常
- 返回客户端
Service 用来处理具体的逻辑。
UserServlet¶
- 封装表单数据到 User 对象中
- 补全 uid,code(激活码)
- 输入校验(不访问数据库)
- 保存错误信息到 request
- 保存 form 到 request(回显)
- 转发会到 regist.jsp
- 调用 service 的注册方法
- 保存错误信息到 request
- 保存 form 到 request(回显)
- 转发会到 regist.jsp
- 发邮件
- 保存成功信息到 request
- 转发到 msg.jsp
UserService¶
- 校验 User 的 username 是否被注册
- 校验 User 的 email 是否被注册
- 把 User 存到数据库
激活¶
邮箱打开激活链接:
- 拿到激活码
- 通过激活码查到用户
- 把用户的状态置为 true
- 然后给用户链接,让用户跳转到登陆页面
登录¶
登录界面:
- Servlet 拿到用户名和密码
- 调用 service 中的登录方法
- 查询用户名是否存在
- 检验密码是否正确
- 若错误,则爆出异常,返回错误信息到 msg
- 页面跳转到 main.jsp
购物车¶
登录时创建购物车 cart
,存放在 session 中。
添加到购物车
- 从 session 中拿到购物车
- 获取参数:bid 和 count
- 使用 BookService 通过 bid 得到 book 对象
- 创建购物车条目
- 返回 list.jsp
订单¶
我的订单
- 获取参数 uid
- 通过 uid 来查询订单,返回一个
List<Order>
- 返回
order/list.jsp
因为在 orderitem
表中包含图书和数量
- 图书:
bid
,但在 javabean 中存取的是 Book 对象,所以只用 bid 是无法填充 orderitem 的
我们采用多表查询,然后用 MapHandler
拿到对应的数据,然后使用 toBean 方法,进行对 Book 和 orderitem 对象进行封装。
具体的:
private void loadOrderItem(Order order) {
String sql = "select * from orderitem,book where orderitem.bid = book.bid and oid = ?";
try {
List<Map<String, Object>> mapList = qr.query(sql, new MapListHandler(), order.getOid());
// 利用 mapList 生成两个对象 book 和 orderItemList
List<OrderItem> orderItemList = new ArrayList<OrderItem>();
for(Map<String,Object> map: mapList) {
OrderItem orderItem = CommonUtils.toBean(map,OrderItem.class);
Book book = CommonUtils.toBean(map,Book.class);
orderItem.setBook(book);
orderItemList.add(orderItem);
}
order.setOrderItemList(orderItemList);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
后台¶
删除分类¶
- list.jsp 传递 cid
- 去 servlet 删除
- 若分类下还有图书,则不能删除
- 爆异常,返回 msg.jsp
- 返回 list.jsp
修改分类名称¶
在 mod.jsp 中拿到当前修改的分类
- list.jsp 页面拿到当前的 cid
- 去 servlet ,通过 cid 查询分类
- 保存分类到 req
- 转发到 mod.jsp
修改名称
- 从 req 中拿到 category
- 回显
- 提交表单 到 servlet
- 修改:检查是否有重名
- 爆出异常,返回 msg.jsp
- 返回 list.jsp
添加图书¶
上传三步
- 创建工厂
- 创建解析器
- 解析request 得到的表单字段
把表单字段封装到 Book 对象中
调用 service 的方法
返回 list.jsp
删除图书¶
book 表与 orderitem 有关联关系
删除图书不是真的在数据库表中删除记录,而是给 book 表添加一个 del 字段,表示是否删除。
- 修改 bookDao 中相关的方法
- 修改 Book 类,添加 del 属性
泛型¶
泛型类:
泛型方法:
在创建实例时,为泛型的类型变量赋值。
通配符 ?
?
:无边界通配符,表示可以是任何类型。? extends T
:有上边界的通配符,表示类型必须是T
或其子类型。? super T
:有下边界的通配符,表示类型必须是T
或其父类型。
注解¶
注解的作用目标:
- 类
- 方法
- 构造器
- 参数
- 局部变量
- 包
注解的属性¶
定义属性:
使用注解给属性赋值:
设置默认值:int age() default 100;
设置总用目标的限定(限定在成员变量、方法..上)
反射注解¶
-
要求 * 注解的保留策略必须是RUNTIME
-
反射注解需要从作用目标上返回 * 类上的注解,需要使用Class来获取 * 方法上的注解,需要Method来获取 * 构造器上的注解,需要Construcator来获取 * 成员上的,需要使用Field来获取
Class
Method、Constructor、Field:AccessibleObject
它们都有一个方法:
* Annotation getAnnotation(Class)
,返回目标上指定类型的注解!
* Annotation[] getAnnotations()
,返回目标上所有注解!
Created: June 3, 2024