javaweb
XML
定义
- 扩展标记语言
功能
-
能存储复杂的数据结构
-
可以作为在程序之间传输数据格式(被JSON取代)
-
作为配置文件(主要: spring,maven等等)
-
充当小型数据库
语法
-
1.xml声明放在xml文档第一行
-
2.xml声明包含version(基本不变为1.0), 和字符编码一般用utf-8
1
<?xml version="1.0" encoding="UTF-8"?>
-
3.元素语法要求
-
每个xml文档有且只有一个根元素
-
根元素包括其他所有元素
-
标签分为有标签体和没有标签体的
-
有标签体
1
<a>www.google.com</a>
-
无标签体
1
<a></a>简写为<a/>
-
-
元素,标签(tag),节点术语指定是同一个东西
-
-
4.属性
-
属性值用双引号或者单引号分隔
-
一个元素可以有多个属性, 基本格式为<元素名 属性名="属性值">元素名>
-
属性名在一个标签中不能重复
-
属性值不能包括&符号
-
-
CDATA节
-
CDATA节中所有字符都会被当做简单文本,而不是xml标记
1
<![CDATA[需要的内容]]>
-
-
转义字符
1
< > &(&) "(") '(')
xml解析
-
原理,xml和html这类标记文档,可以通过w3c组织制定的dom技术解析xml获取dom对象
-
dom4j解析
-
获取dom对象的3种方式
1 2 3 4 5 6 7 8 9
// 从文件中解析 SAXReader reader = new SAXReader(); Document document = reader.read(new File("src/xml/students.xml")); // 从文本中解析 String text = "<name>mary</name>"; document = DocumentHelper.parseText(text); // 创建dom对象 document = DocumentHelper.createDocument(); document.addElement("name");
- QName是标签名字
-
遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// ============遍历==================== Element rootElement = document.getRootElement(); // 获取root的两个直接子元素 List<Element> elements = rootElement.elements(); for (Element student : elements) { // 获取student的属性id String id = student.attributeValue("id"); // 获取student子节点 Element name = student.element("name"); Element age = student.element("age"); Element gender = student.element("gender"); System.out.printf("%s,%s,%s,%s\n", id, name.getText(), age.getText(), gender.getText()); }
-
查找
1 2
//=========== 获取第二个学生=========== Element stu = (Element) rootElement.elements("student").get(1);
-
增删改
-
增
1 2 3 4 5 6 7 8 9 10 11
//=========== 添加一个新学生============= Element newStu = DocumentHelper.createElement("student"); Element newStuName = DocumentHelper.createElement("name"); // 如何给元素添加属性 newStu.addAttribute("id", "300"); // 给元素添加Text newStuName.addText("tom"); // 把子节点添加到父节点中 newStu.add(newStuName); // 把学生节点加到根节点中 rootElement.add(newStu);
-
删
1 2 3
// ============删除第三个学生========================= Element student = (Element) rootElement.elements("student").get(2); student.getParent().remove(student);
-
改
1 2 3
//==========更新第一个学生, 把学生的年龄+3============ Element newAge = (Element) ((Element) (rootElement.elements("student").get(0))).elements("age").get(0); newAge.setText(Integer.parseInt(newAge.getText()) + 3 + "");
-
-
保存修改到文件中
1 2 3 4 5 6
// ========保存xml文件========= OutputFormat out = OutputFormat.createPrettyPrint(); out.setEncoding("utf-8"); XMLWriter xmlWriter = new XMLWriter(new FileOutputStream("src/main/java/xml/students.xml"), out); xmlWriter.write(document); xmlWriter.close();
-
-
历史
-
早期JDK提供两种xml解析技术DOM和Sax(Simple API for XML),现在二者已经过时
-
第三方解析技术, jdom对dom封装, dom4j对jdom进行了封装
-
HTTP协议
命令
1
curl -i 'www.baidu.com'
请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET /hi HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: no-cache
Connection: keep-alive
Cookie: Idea-3873738e=839fe1ca-6bc2-4055-bb09-70a4cd05d6f5
Host: localhost:43999
Pragma: no-cache
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
sec-ch-ua: "Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
空行\r\n
请求体
-
请求头
-
Host请求的主机端口
-
User-Agent: 浏览器信息
-
Accept: 浏览器可以接受的数据格式
-
Accept-Language:浏览器可以接受的语言,zh-CN,en-Us
-
Accept-Encoding:浏览器可以接受数据压缩方式
-
Connection: 告诉服务器处理连接的方式, keep-alive长连接, closed,立即关闭连接
-
Referer: 表示这个请求是从哪里来的(防止盗链)
-
Cookie, 浏览器每次请求时都会带上cookie
-
Content-Type:表示提交数据的格式 application/x-www-form-urlencoded 表示表单数据
-
content-length表示数据长度
-
Origin:表示请求是从那个主机发出的
-
响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
HTTP/1.1 200
Content-Length: 321
Accept-Ranges: bytes
Connection: keep-alive
Content-Type: text/html
Date: Fri, 21 Feb 2025 06:19:23 GMT
Etag: W/"321-1740107657325"
Keep-Alive: timeout=4
Last-Modified: Fri, 21 Feb 2025 03:14:17 GMT
Proxy-Connection: keep-alive
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Wel come hello</h1>
<a href="ganyu-game.html">玩游戏吧</a>
<br>
<a href="hi">hi</a> <br>
<a href="register.html">去注册</a>
</body>
</html>
-
响应头
- Server: 服务器 Accept-Range: byte字节–>支持断点续传 ETag: 对资源的标识(类似一个token) Last-Modified: 返回资源的上次修改时间,可以用来判断是否使用缓存 Content-Type:表示返回资源类型 Content-Length:返回资源的字节长度,浏览器根据这个来判断文件是否结束了,不设置的话, 浏览器会一直转圈 Date: 服务器响应时间
-
状态码
- 200OK 302 重定向 304 Not Modified 404
- 302, 有个location响应头, 表示重定向的资源url
重定向会发送至少两次请求
304, 当没有禁用浏览器缓存时, 响应头会添加if-modified-since:时间, 表示资源的最近修改时间,服务器会比较时间, 如果服务器的资源更新, 就会返回该资源, 如果没有修改, 就返回304状态码, 但是不会返回该资源
发送请求数量案例
请求了三次
1
2
3
index.html
<img src="a.jpg">
<img src="b.jpg">
get和post请求分别有哪些
-
get
- 表单方法指定get, form, method=get a标签 link标签,引入css script标签,引入js脚本 img标签 iframe引入html页面 浏览器地址敲回车
-
post
- 表单中方法指定post
-
什么情况下使用post
-
get传输数据量小 get的密码会显示在url上
-
查询使用get OK 修改/更新/删除可以使用post请求
-
MIME类型
-
MIME类型是HTTP协议中数据类型,在响应头中的Content-Type中指定
-
常用的MIMIE类型
- text/html text/plain image/gif application/json …
Tomcat+Servlet
Tomcat
-
介绍
tomcat也是java网络程序的一部分, 他负责处理sokcet和浏览器通信的过程, 我们负责业务问题, 把用户的请求入库写入数据库或者把数据库中的数据返回给tomcat,tomcat再返回给浏览器.
-
配置
-
Tomcat的目录
-
bin: 存放启动和关闭服务的命令
-
conf 存放Tomcat服务的各种配置文件
-
server.xml 配置tomcat的基本设置,启动端口,关闭端口,主机名
-
web.xml 用来指定tomcat运行配置(比如servlet)
-
-
lib tomcat自己要用到的jar包
-
logs tomcat运行日志
-
webapps
-
web应用所在目录, 默认访问ROOT应用项目
-
web应用
-
web应用组成: html,图片,文本,声音,视频等,css,js,动态web页面, java程序(servlet), jar包,配置文件等
-
web应用目录规范
-
webapp目录
-
静态文件,html/css/js等
-
WEB-INF目录
-
web.xml
-
lib
-
-
-
-
部署方式
-
方式1,将web工程目录copy到webapps目录下
-
方式2,通过配置文件来部署(idea就是这样用这种方式的), 在tomcat/conf/Catalina/localhost/下,配置文件
-
-
访问方式
- http://ip:port/web应用目录名/请求的文件路径
-
-
-
work: tomcat的工作目录,存放解析后的jsp文件
-
-
-
使用
-
配置JAVA_HOME环境变量
-
在idea中配置tomcat
-
1.创建web项目
-
2.edit configuration, 选择tomcat server
-
3.maven打包方式选择war包
-
4.rebuild项目后生成artifacts
-
5,在第2步中选择deployment中选择artifacts
-
6.直接执行或者拷贝artifacts到tomcat的webapps目录下
-
注意, 编辑区的源码, 和运行时源码不是一个东西 编辑区的源码经过rebuild/build后才能生成可以运行的artifacts
-
-
-
tomcat底层原理
-
http请求过程
-
启动tomcat, 完成web.xml配置Servlet,启动socketServer监听
-
http Request 开启一个线程去处理请求
-
静态资源 直接返回静态资源
-
请求Servlet ->调用Servlet的service方法
-
-
-
-
启动tomcat, socket监听, 请求过来开启多线程 dom4j, 解析web.xml, 利用反射创建Servlet,
-
拼接http请求的字符串
1 2 3 4 5 6 7 8 9
String content = """ HTTP/1.1 200 Content-Type: text/html;charset=utf-8 Content-Length: %d %s """; String s = "<h1>你好, Hello, Tomcat</h1>"; content = String.format(content, s.getBytes().length, s);
-
实现简易版tomcat
核心类-加载配置-创建/调用Servlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package com.xxx.tomcat_;
import com.xxx.tomcat_.myservlet.MyHttpRequest;
import com.xxx.tomcat_.myservlet.MyHttpResponse;
import com.xxx.tomcat_.myservlet.MyHttpServlet;
import com.xxx.tomcat_.myservlet.MyServlet;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author zangxin
* @version 1.0
* @date 2025/2/22
*
*创建,调用,从xml读取Servlet配置信息
*/
public class ManageServlet {
// url --- obj
private static final Map<String, MyServlet> URL_SERVLET_MAP = new ConcurrentHashMap<>();
public static final MyServlet NULLOBJ = new MyHttpServlet();
// url --- class
private static final Map<String, String> URL_CLASS_MAP = new HashMap<>();
private static void doServlet() {
}
public static void loadServlet() {
System.out.println("load servlet start ....");
// 从web.xml文件中读取
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(ManageServlet.class.getResourceAsStream("myservlet/web.xml"));
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements("servlet-mapping");
for (Element servletMapping : elements) {
Element element = servletMapping.element("url-pattern");
String urlPattern = element.getText();
Element servletName = servletMapping.element("servlet-name");
URL_CLASS_MAP.put(servletName.getText(), urlPattern);
}
List<Element> servlets = rootElement.elements("servlet");
for (Element servlet : servlets) {
String servletName = servlet.element("servlet-name").getText();
String servletClass = servlet.element("servlet-class").getText();
String urlPattern = URL_CLASS_MAP.get(servletName);
URL_CLASS_MAP.put(urlPattern, servletClass);
URL_CLASS_MAP.remove(servletName);
URL_SERVLET_MAP.put(urlPattern, NULLOBJ);
}
} catch (DocumentException e) {
throw new RuntimeException(e);
}
System.out.println("load servlet end, " + "load " + URL_SERVLET_MAP.size() + " servlets.");
}
public static void createServlet(String url) {
String className = URL_CLASS_MAP.get(url);
try {
Class<?> clazz = Class.forName(className);
Object o = clazz.newInstance();
URL_SERVLET_MAP.put(url, (MyServlet) o);
} catch (Exception e) {
e.printStackTrace();
}
}
public static MyServlet getServlet(String url) {
return URL_SERVLET_MAP.get(url);
}
public static boolean containsUrl(String url) {
return URL_SERVLET_MAP.containsKey(url);
}
public static void doService(String url, MyHttpRequest request, MyHttpResponse response) {
String className = URL_CLASS_MAP.get(url);
try {
Class<?> clazz = Class.forName(className);
Method service = clazz.getMethod("service", MyHttpRequest.class, MyHttpResponse.class);
service.invoke(URL_SERVLET_MAP.get(url), request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
核心类-http通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.xxx.tomcat_;
import com.xxx.tomcat_.myservlet.CalTwoSumServlet;
import com.xxx.tomcat_.myservlet.MyHttpRequest;
import com.xxx.tomcat_.myservlet.MyHttpResponse;
import com.xxx.tomcat_.myservlet.MyServlet;
import com.xxx.util.StreamUtils;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.xxx.tomcat_.ManageServlet.NULLOBJ;
/**
* @author zangxin
* @version 1.0
* @date 2025/2/22
* 和2一样, 这里要支持MyServlet系列接口
*/
public class MyTomcat3 {
public static void main(String[] args) throws Exception {
System.out.println("tomcat 在43999监听");
ServerSocket serverSocket = new ServerSocket(43999);
// 加载Servlet从xml文件或者注解中
ManageServlet.loadServlet();
while (true) {
Socket socket = serverSocket.accept();
new HttpRequestThread3(socket).start();
}
}
}
class HttpRequestThread3 extends Thread {
private Socket socket;
public HttpRequestThread3(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
MyHttpRequest request = new MyHttpRequest(socket.getInputStream());
MyHttpResponse response = new MyHttpResponse(socket.getOutputStream());
// index页面
String url = request.getUrl();
if (url.equals("/")) {
response.write(StreamUtils.streamToString(CalTwoSumServlet.class.getResourceAsStream("index.html")));
}
// 查找静态资源 html
// 匹配文件扩展名
Matcher matcher = Pattern.compile("(/)(\\w+.\\w+$)").matcher(url);
if (matcher.find()) {
String resource = matcher.group(2);
System.out.println(resource);
if (resource.substring(resource.lastIndexOf(".") + 1, resource.length()).equals("html")) {
response.write(StreamUtils.streamToString(CalTwoSumServlet.class.getResourceAsStream(resource)));
}
}
// 根据url查找Servlet
if (ManageServlet.containsUrl(url)) {
MyServlet servlet = ManageServlet.getServlet(url);
if (servlet == NULLOBJ) {
ManageServlet.createServlet(url);
}
ManageServlet.doService(url, request, response);
}
response.write("404 NOT FOUND");
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.xxx.tomcat_.myservlet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
/**
* @author zangxin
* @version 1.0
* @date 2025/2/22
*/
public class MyHttpRequest {
private String url;
private String method;
private InputStream in;
private HashMap<String, String> paramMap = new HashMap<>();
public MyHttpRequest(InputStream inputStream) {
// 封装请求
this.in = inputStream;
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
try {
// 解析请求行
String requestLine = br.readLine();
// 请求行
// GET /?name=ggstar HTTP/1.1
String[] lines = requestLine.split(" ");
method = lines[0];
url = lines[1];
if (url.contains("?")) {
url = url.substring(0, url.indexOf("?"));
// 获取参数
String pls = lines[1].substring(lines[1].indexOf("?")+1);
String[] parameters = pls.split("&");
for (String parameter : parameters) {
String[] kv = parameter.split("=");
if (kv.length == 2)
paramMap.put(kv[0], kv[1]);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String getMethod() {
return method;
}
public String getParameter(String name) {
return paramMap.get(name);
}
public String getUrl() {
return url;
}
}
response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.xxx.tomcat_.myservlet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
/**
* @author zangxin
* @version 1.0
* @date 2025/2/22
*/
public class MyHttpResponse {
private OutputStream out;
public MyHttpResponse(OutputStream out) {
this.out = out;
}
public OutputStream getOut() {
return out;
}
String httpFormat = """
HTTP/1.1 200
Content-Type: text/html;charset=utf-8
Content-Length: %d
KeepAlive: 3600
%s
""";
public String write(String content) {
String format = String.format(httpFormat, content.getBytes().length, content);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
try {
writer.write(format);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
return format;
}
}
servlet接口
1
2
3
4
5
public interface MyServlet {
void init();
void service(MyHttpRequest request, MyHttpResponse response);
void destroy();
}
servlet实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MyHttpServlet implements MyServlet {
@Override
public void init() {
}
@Override
public void service(MyHttpRequest request, MyHttpResponse response) {
if (request.getMethod().equals("GET")) {
doGet(request, response);
} else if (request.getMethod().equals("POST")) {
doPost(request, response);
}
}
public void doPost(MyHttpRequest request, MyHttpResponse response) {
}
public void doGet(MyHttpRequest request, MyHttpResponse response) {
}
@Override
public void destroy() {
}
}
web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>CalTwoSumServlet</servlet-name>
<servlet-class>com.xxx.tomcat_.myservlet.CalTwoSumServlet</servlet-class>
<load-on-startup>99</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CalTwoSumServlet</servlet-name>
<url-pattern>/cal</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>test</servlet-name>
<servlet-class>com.xxx.tomcat_.myservlet.CalTwoSumServlet</servlet-class>
<load-on-startup>99</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>test</servlet-name>
<url-pattern>/ff</url-pattern>
</servlet-mapping>
</web-app>
servlet
-
定义:Servlet是javaEE规范, 是一套接口 由服务器厂商实现该接口
-
servlet和tomcat的关系: tomcat实现了Servlet接口
-
使用时,在Servlet实现业务功能
-
-
Servlet接口
-
生命周期方法
-
init
请求url时,Servlet如果存在, Tomcat就创建实例,再会调用该方法, 只调用一次(Servlet是懒加载的,只有在有请求时, 才创建Servlet对象)
-
service
从创建开始,服务直到销毁, 可被多次调用 可以处理get和post请求
1.每收到一个请求, 服务器就会产生一个新的线程去处理 2.创建一个用于封装HTTP请求消息的ServletRquest和HTTP响应对象ServletResponse 3.然后调用service方法,并将请求和响应对象作为参数传递
-
destroy
-
销毁时调用,只调用一次
-
使用kill -9 时不会调用
-
-
-
-
使用方法
-
开发方式
-
配置文件web.xml
1 2 3 4 5 6 7 8 9
<servlet> <servlet-name> <servlet-class> 设置tomcat启动时加载Servlet, 对抗懒加载 <load-on-startup>99</load-on-startup> <servlet-mapping> <servlet-name> <url-pattern>
在第二次请求时: 1.查询web.xml, url–>从map中查询获取Servlet对象–>拿到对象,调用service方法,为用户服务
-
请求过程, 如果是第一次请求 1.查询web.xml 2.查看请求的资源/helloServlet, 在web.xml配置url-pattern 3.如果找到类url-pattern, 就得到了HelloServlet对象 4.Tomcat维护了一个大的HashMap<id, Servlet> 查询该Map,看看是否有Servlet实例 5.如果没有查询到Servlet对应id, 即Servlet还不存在 6.就根据servlet-name去得到servlet-class的类路径 7.反射实例化servlet,调用init()方法, 在放入到map中, 便于以后查询
-
servlet默认是单例的, 从刚才的hashmap维护url —> Servlet对象可以看出来
-
-
注解开发
-
在我们定义Servlet类上面写上下面的注解
1 2 3 4
@WebServlet(urlPatterns = {"/as", "/as1","/as2"}) // 或者 // name和value不能重复(和其他Servlet一样) @WebServlet(name = "helloServlet", value = "/hello-servlet")
-
原理
包扫描, 某个类上面有WebServlet注解时就认为它是一个Servlet—>放入hashmap中
注解开发方式和web.xml一样, 相当于web.xml的语法糖
-
-
-
共通线
1.引入servlet-api和jsp-api依赖
2.写Servlet
写一个类继承HttpServlet类
或者通过idea自动生成
3.实现doGet方法和doPost方法
-
父类的HttpServlet.service被我们的Servlet方法继承了, 在调用时并不直接调用service方法, 而是调用service方法, 判断是get还是post等请求, 然后if判断后再去调用对应doGet方法和doPost方法(动态绑定机制)
-
路径匹配
-
精准匹配
- /ok 只匹配/ok这一个url
-
目录匹配
- /ok/* 匹配前缀是/ok/的
-
扩展名匹配
- *.action 后缀是.action, 比如/ab/c/e/gg.action
-
任意匹配
-
/和/*
-
匹配所有请求
-
连静态资源html,css,js,图片等静态资源都会匹配😅
-
会覆盖了tomcat的web.xml中配置的DefaultServlet, 导致处理不了静态资源, 建议不要使用
-
-
-
优先级: 精准路径>前缀>后缀>任意匹配
-
😅不要发明新匹配, /*/a.html, //b.html, /*/ab_.html, 都不生效, 只要上面四种有效
-
-
-
-
Repsonse
-
响应中文乱码时,加上配置
- response.setContentType(“text/html;utf-8”); response.setCharacterEncoding(“utf8”);
-
常用方法
-
// 添加content-Type请求头,告诉浏览器用html方式来解析 response.setContentType(“text/html;utf-8”);
-
// 设置状态码 setStatus
-
// 设置响应头 setHeader
-
// 获取字符流对象用来给client返回数据 getWriter
-
-
-
两个流在同一时间只能使用一个
-
// 获取字节流对用来给client返回二进制文件 getOutputStream
-
Request
-
请求参数值乱码时,加上配置
- request.setCharacterEncoding(“utf8”);
-
HttpServletRequest表示客户端的请求, http请求头中的信息都封装在这个对象,通过对象的方法可以获取客户端的信息
-
常用方法
-
getRequestURI : /req
-
getRequestURL: http://localhost:43999/req
-
getRemoteAddr
- 客户端地址
-
getHeader(“referer”)
- 获取请求头
-
getParameter(“username”)
- 获取请求参数(表单中的,和url后面都可以)
-
getCookies
- 获取cookie
-
getParameterValues(“爱好”)
- 获取checkbox复选框中多值数据
-
getMethod
- 获取请求方法
-
setAttribute/getAttribute
- 设置/获取域数据
-
request.getRequestDispatcher(“/”).forward(request, response)
- 转发
-
-
-
ServletConfig
-
为Servlet提供配置信息的类 配置信息不可更改(没有set方法)
-
定义配置信息: 在web.xml中配置
1
<Servlet>配置信息<init-param>
-
获取配置信息:
1 2
ServletConfig servletConfig = this.getServletConfig(); servletConfig.getInitParameter("username")
-
-
由tomcat创建
-
ServletConfig在Servlet程序创建时,就创建了一个对应的ServletConfig对象
-
重写init方法时(有参的那个)需要调用super.init(有参)的那个方法, 完成父类的初始化
-
-
ServletContext
-
Servlet上下文对象,为所有Servlet提供配置信息 配置信息不可更改,由set方法,但改了就报异常
-
定义配置信息: 在web.xml中配置
1
<context-param>
-
获取配置信息:
1 2
ServletContext context = getServletContext() String appName = context.getInitParameter("appName");
1 2 3 4
// 获取工程路径: /j2025/javaweb 在idea的edit configuration的deployment的application Context中配置的路径 System.out.println(context.getContextPath()); // 获取项目发布后的绝对路径: ~/IdeaProjects/j2025/out/artifacts/fishWeb_Web_exploded/ --> ROOT System.out.println(context.getRealPath("/"));
-
-
一个web工程只有一个ServletContext实例
-
由tomcat创建,在web工程启动时创建, 在web工程停止时销毁
-
获取Context, this.getServletContext
-
域对象:web应用中所有Servlet共享同一个Context对象, Servlet可以借助Context对象来通信
- 传递数据方法是: setAttribute(String name, Object object); public Object getAttribute(String name); 而不是getInitParameter
-
-
转发和重定向
-
请求转发
-
定义
请求转发指的是一个web资源收到客户端的请求后, 通知服务端去调用另外一个web资源进行处理
-
使用
-
request.getRequestDispatcher(“资源uri”).forward(request, response);
-
request对象同时也是一个域对象,通过request对象在实现转发时, 可以通过request对象把自己的数据带给其他web资源
- setAttribute getAttribute removeAttirbute getAttributeNames
-
-
注意事项
-
请求转发的地址栏不会发生改变, 状态码是302
-
在同一次http请求中,进行多次转发, 仍然是一次HTTP请求
-
转发链上的servlet可以共享request域中的数据, 因为是同一个request对象
-
可以转发到WEB-INF目录下
-
不能访问到WEB工程外的资源
-
因为浏览器地址栏会停止在第一个servlet, 如果刷新页面,会再次发生请求并携带数据, 所以在支付页面上, 不要使用请求转发, 否则会造成重复支付
-
-
-
重定向
-
定义
一个web资源收到客户端请求后, 通知客户端去访问另外一个web资源 (2次请求, 多跑一趟)
-
用法
-
第一种方法: reponse.sendRedirect(“资源位置”) (推荐)
-
第二种方法 response.setStatus(302) response.setHeader(“Location”,”/downloadNew)
-
-
注意事项
-
浏览器url地址是重定向的地址, 是2次http请求
-
第一次请求响应码为302,响应头Location为重定向的地址
-
重定向是浏览器来解析, 不是服务端解析,所以url最好写工程的全路径, /xxxServlet/xxx/bbServlet
-
动态获取资源绝对路径:
1
sendRedirect(getServletContext.getContextPath + "/resoucrepath")
-
-
不能共享Request域中的数据, 本质上是两次http请求, 会生成两个Request对象
-
不能重定向到/WEB-INF目录下的资源
-
可以重定向web工程以外的资源,比如www.google.com
-
-
典型应用场景
- 网站域名迁移
-
-
会话技术Cookie和Session
Cookie
-
cookie原理
-
Cookie创建过程
-
浏览器请求服务器时,,服务器创建Cookie对象, 添加到响应中,并返回给浏览器
1 2
Cookie cookie = new Cookie("userId","123") response.addCookie(cookie)
浏览器收到http请求时, 解析响应头 Set-Cookie: userId=123 把userId=123保存在本地cookie中
-
-
Cookie是服务器在客户端保存用户的信息, 比如登录名, 浏览历史等, 就可以使用Cookie方式保存
-
Cookie的数据量不大, 服务器在需要时可以从客户端/浏览器读取Cookie
-
浏览器每次请求时,都会携带Cookie请求头
- Cookie: JSESSIONID=E72676B5952D59BD7A5BA5BE3FE88568; userId=123
-
-
Cookie是保存在浏览器上的(其实是浏览器保存到本地文件中的, 很多网站在访问时都要求你同时使用Cookie,否则网站不能正常使用
-
-
Cookie常用方法
-
Cookie有点像一张表, 分为两列, 一个是那么, 一个是value, 数据类型都是String
-
创建Cookie, cookie的名字是唯一的
1 2
Cookie c = new Cookie(String name, String val); c.setMaxAge();// 保存时间
-
把创建的Cookie添加到客户端
1
response.addCookie(c)
-
4.读取Cookie
1
request.getCookies();
获取Cookie和创建Cookie和更新Cookie(更改Cookie和创建Cookie方法是一样的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Cookie[] cookies = request.getCookies(); boolean isCookieExist = false; for (Cookie cookie : cookies) { if (cookie.getName().equals("userId")) { Cookie userId = new Cookie("userId", WebUtil.parseInt(cookie.getValue()) + 1 + ""); response.addCookie(userId); isCookieExist = true; } System.out.printf("%s = %s\n", cookie.getName(), cookie.getValue()); } if (!isCookieExist) { Cookie userId = new Cookie("userId", "123"); response.addCookie(userId); }
-
-
JSESSIONID
- 服务器用来区分和哪个浏览器会话, 即用来区别不同的会话
-
Cookie的生命周期
-
cookie的声明周期是指Cookie什么时候被销毁
-
通过setMaxAge方法设置多久过期
-
正数: 多少秒后过期
-
负数, 表示浏览器关闭, Cookie就会被删除(默认值是-1, 会话级别)
-
0, 表示马上删除
-
响应头会加上一个max-Age和过期事件 Set-Cookie: userId=130; Max-Age=7200; Expires=Sat, 22 Feb 2025 17:25:30 GMT
-
-
-
Cookie的path属性
-
Cookie的path属性可以有效的过滤哪些Cookie可以分爱送给服务器, 哪些不发给服务器, path属性是通过请求的地址来进行有效的过滤
- Set-Cookie: password=123root; Path=/webpath/CookiePathServlet
-
规则
- cookie1.setPath=/webpath cookie2.setPath=/webpath/aaa 请求地址http://ip:port/webpath/resource cookie1会发送给服务器 cookie2不会发送给服务器 请求地址 http://ip:port/webpath/aaa/资源 cookie1会发送 cookie2会发送 path是前缀匹配的
-
-
细节
-
一个Cookie只能标识一种信息, 他有一个name和value对
-
一个web站点可以给一个浏览器发送多个Cookie, 一个浏览器也可以存储多个web站点提供的cookie
-
cookie的总数没有限制, 但是每个域名的cookie数量和cookie大小是有限制的, cookie不适合存储数量大的信息
-
删除cookie时, 必须保持path一致, 否则不能删除
-
中文cookie一般来说不支持(设置字符集为utf8时可以支持),也可以使用URL编码来解决, URLEncoder,URLDecoder
-
Session
-
定义
-
Session是服务端技术, 服务器在运行时为每一个用户的浏览器创建一个其独享的Session对象/集合
-
由于session为各个用户浏览器独享, 所以在用户访问服务器的不同页面时, 可以从各自的Session中读取/添加数据,从而完成响应任务
-
-
原理
-
创建过程
-
当用户请求服务器时, 操作Session时(调用request.getSession(方法)), 服务器就会在内存中为该浏览器分配一个Session对象, 该Session对象被这个浏览器(会话)独占
-
请求是否有jsessionid的cookie
-
无
- 请求时,如果没有携带jsessionid, 新建session对象, 分配jessionId: 响应时添加set-cookie: jsessionid=xxxxxxx,响应头, 添加jsessionid的cookie
-
有
-
是否有id=jsessionid的Session对象
-
有
- 获取Session,操作session
-
无
- 这是情况, 是服务端重启, 浏览器没有关闭 创建session对象分配jsessionid, 注意这个sessionid和原来的不一样
-
-
-
-
-
单个Session的结构
- 类似一个hashmap对象, 有键值对<String,Object>
-
服务端多个Session结构
- Map<jsessionid, Session> —> Map<jsessionid, Map<String, Object>(实际上是concurrentHashMap)
-
-
使用
-
常用方法
-
创建Session
- 获取和当前Request关联的session, 如果有就返回, 如果没有就创建一个session request.getSession()
-
向session添加属性
- addAttribute(String name, Object val)
-
获取session的某个属性
- getAttribute(String name)
-
删除
- removeAttribute
-
判断是否为新建的session
- isNew()
-
获取session的标识Id
- 每个session都有一个唯一的标识id getId()方法可以获取
-
-
-
生命周期
-
默认生命周期
-
在Tomcat的web.xml中可以配置, 默认是30min
1
<session-config>
-
-
通过方法设置过期时间
-
session.setMaxInactiveInterval(30)
-
正数, 多少秒过期
-
负数, 永不过期
- 服务端重启就没了
-
-
销毁session对象
- session.invalidate()
-
-
Session的生命周期指的是, 客户端/两次请求最大间隔时长, 而不是累计时长. 当客户端访问了自己的session, Session的生命周期将会从0开始计算(同一个会话两次请求之间的间隔)
-
tomcat使用一个线程来轮询会话状态, 如果某个会话的空闲事件超过时间设定的最大值, 则会销毁该Session
-
-
session能做什么
-
网上商城的购物车
-
保存用户的信息
-
将数据放入Session中,供用户在访问不同的页面时, 实现跨页面访问数据
-
防止用户没有登录, 就进入到登录才能访问的页面
-
监听器和过滤器
监听器
-
定义
- javaweb的三大组件, 监听器监听某种变化(对象创建/销毁,属性变化),触发对应方法完成任务
-
常用监听器
-
ServletContextListener接口
-
作用:监听ServletContext创建或者销毁(web应用启动时, 就会创建ServletContext),即生命周期监听, 应用: 加载spring的配置文件, 任务调度(配合定时器Timer/TimerTask)
-
常用方法
-
创建时触发 public void contextInitialized(ServletContextEvent sce)
-
销毁时触发 public void contextDestroyed(ServletContextEvent sce)
-
-
使用
-
定义一个类实现ServletContextListener接口,在web.xml中写标签
1
<listener>
-
-
-
ServletContextAttributeListener
-
作用监听ServletContext属性变化
-
方法
-
attributeAdded
- 添加属性时调用
-
attributeRemoved
- 删除属性时调用
-
attributeReplaced
- 替换属性值时调用
-
-
-
HttpSessionListener
-
作用:监听Session的创建或销毁(生命周期)
-
方法
-
sessionCreated
- session创建时调用
-
sessionDestroyed
- session销毁时调用
-
-
使用案例, 监控用户上下线
-
-
HttpSessionAttributeListener
-
作用:监听Session属性的变化
-
方法
-
attributeAdded
- 添加属性时调用
-
attributeRemoved
- 删除属性时调用
-
attributeReplaced
- 替换属性值时调用
-
-
-
ServletRequestListener
-
监听request的创建和销毁
-
方法
-
requestInitialized
-
requestDestroyed
-
-
使用案例,可以用来监控某个IP访问网站的频率, 日志记录,访问资源
-
-
其他监听器
-
ServletRequestAttributeListener
-
HttpSessionBindingListener
-
HttpSessionActivationListener
-
-
过滤器
-
过滤器的必要性
- 登录校验, 权限校验,记录日志,事务处理等等,如果在每个Servlet前面加上校验就很繁琐,代码冗余
-
定义
-
javaweb的三大组件之一,过滤器Filter是javaEE的接口, 有三个方法init, doFilter,destroy, 需要在web.xml中配置标签和过滤路径
1
<filter>
-
-
作用
-
拦截请求
-
过滤响应
-
-
原理
-
1.在web.xml中配置过滤器, 指定过滤规则url-pattern 2.如果匹配url-pattern,就走过滤器 3.如果不匹配就直接访问
-
生命周期
-
Filter默认不是懒加载
-
Filter接口有三个方法init, doFilter, destroy分别对应Servlet的init,service, destroy基本一致
-
-
tomcat在反射调用时Servlet.service方法之前会判断filterMap的url路径和Servlet路径是否匹配, 匹配就会在调用service之前调用doFilter方法, 如果放行, 就继续调用service, 如果不放行就不用调用service
-
filter在web项目启动时, 有tomcat创建, 只会创建一次, 创建成功时会调用init方法, 只调用一次, 在创建filter时会创建一个filterConfig对象,通过init方法传入, 通过filterConfig对象,可以获取都在web.xml中filter的
配置信息, 当一个http请求的uri路径和filter的url-pattern匹配时, 就会调用doFilter方法, 在调用doFilter方法时, tomcat会传入request和Response和FilterChain三个参数, 并通过Filter传入, 如果后面请求的数据库请求目标资源,jsp,Servlet,会使用到request, 比如登录校验, 过滤脏词 -
放行
-
调用filterChain.doFilter方法,如果没有写这个方法会返回空白页面,一般写个转发或者重定向到登录页面去
-
放行后, 会继续访问目标资源, ServletRequest, ServletResponse会继续传递给目标资源
-
-
FilterConfig的方法
-
getServletContext 获取应用域 getInitParameter 获取配置参数By参数名 getInitParameterNames 获取所有配置参数名 getFilterName 获取过滤器的名字
-
使用案例,封杀ip
1 2 3 4 5 6 7 8 9 10 11
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ // 封杀ip, 在init方法中给banIp赋值, 从initParam中获取 if (request.getRemoteAddr().startsWith(banIp)) { System.out.println("~~~~封杀该网段ip~~~ " + request.getRemoteAddr()); ((HttpServletResponse) response) .sendRedirect(request.getServletContext().getContextPath() + "/login.jsp"); } else { System.out.println("非目标ip段放行~~~~~~~ " + request.getRemoteAddr()); chain.doFilter(request, response); } }
-
-
-
-
细节说明
-
注意tomcat的版本号和Servlet-api依赖的版本号之间的对应关系
-
url-pattern
- 和Servlet匹配模式一样, 注意是服务端, 所以 / 表示工程路径名/后面的东西,
- 注意/*表示拦截所有请求, / 什么都拦截不了,
- 后缀匹配, *.do, *.action, *
- 前缀匹配/xxx/, 不能在匹配文件名,filter只关心路径是否匹配, 至于路径后的资源是否存在, 它不管
-
-
过滤器链
-
FilterChain:在处理复杂业务时, 一个过滤器不够, 可以设计多个过滤器共同完成任务,形成过滤器链
-
原理
-
FilterChain执行顺序: Http请求–>AFilter–>AFilter的前置代码–>AdoFilter–>BFilter–>BFilter前置代码–>BdoFilter–>目标资源–>B后置代码–>A后置代码–>返回给浏览器页面/数据
-
多个Filter和目标资源在一次请求, 在同一个线程中
-
当一个请求的url和Filter的url-patternP匹配时,才会执行, 如果有多个匹配, 就会形成一个filter调用链(栈数据结构)
-
多个filter共同执行时, 因为是同一个http请求, 所以共同使用一个reqeust对象
-
多个Filter执行顺序和和web.xml的配置顺序保持一致
-
chain.doFilter(req,resp)方法, 将执行下一个过滤器的doFilter方法, 如果后面没有过滤器, 则执行目标资源
-
-
-
转发不走过滤器
-
转发默认不走过滤器,过滤器默认不拦截内部的请求
-
要想走过过滤器需要配置
1 2 3 4 5 6 7 8 9 10 11 12 13
<filter> <filter-name>TestFilter</filtername> <filter-class>anni.TestFilter</filter-class> </filter> <filter-mapping> <filter-name>TestFilter</filtername> <url-pattern>/*</url-pattern> <!-- 让转发走过滤器的配置 --> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>EXCEPTION</dispatcher> </filter-mapping>
-
文件上传/下载
上传
-
准备jar包 commons-fileupload commos-io
-
表单属性设置
-
input的type指定为file
-
方法为post方法(文件比较大)
-
form的enctype(encodetype)编码类型,默认是application/x-www-form-urlencoded即url编码, 这种编码不适合二进制数据提交, 一般适用于文本
-
如果要进行二进制文件提交, enctype要设置为multipart/form-data, 表示表单提交的数据有多个部分组成, 也就是可以提交二进制数据和文本数据, 使用了该类型, 后端使用request.getParameter就失效了
-
-
服务端接收
-
判断是否为一个文件表单
-
判断表单提交的各个表单项是什么类型
-
如果是一个普通的表单项, 就按照文本的方式来处理
-
如果是一个文件表单项(二进制数据)使用IO技术进行处理
-
把表单提交的文件保存到指定的目录下
-
文件覆盖问题
- 对上传的文件名进行处理, 前面加上一个前缀(UUID)或时间戳,保证是唯一的
-
-
实例
-
前端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>上传文件</title> <base href="${pageContext.request.getContextPath()}"> <script src="js/jquery-3.7.1.min.js" type="text/javascript"></script> <script type="text/javascript"> function prev(event) { // 获取文件对象 let file = event.files[0]; // 获取文件阅读器 let reader = new FileReader(); reader.readAsDataURL(file); // base64的形式的图片 reader.onload = function () { $("#preView").attr('src', this.result) }; } </script> </head> <body> <form action="UploadServlet" method="post" enctype="multipart/form-data"> 图片:<img src="img/a.jpeg" width="200px" id="preView"> <br> <input type="file" name="pic" value="2xxx.jpg" onchange="prev(this)"><br> 图片人物:<input type="text" name="username"><br> <input type="submit" value="上传"> </form> </body> </html>
-
后端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
// 判断是否为文件表单(enctype=multipart/form-data) if (ServletFileUpload.isMultipartContent(request)) { System.out.println("是文件表单"); // 创建一个 DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory); try { // servletFileUpload对象可以表单提交的数据text/文件,将其封装到FileItem文件项中 // name=a.jpeg, StoreLocation=~/tomcat/temp/upload_45fb0d4e_b017_47d2_a35c_7e1f3cc4f6c6_00000000.tmp, // size=295675 bytes, isFormField=false, FieldName=pic // name=null, StoreLocation=null, size=5 bytes, isFormField=true, FieldName=name List<FileItem> fileItems = servletFileUpload.parseRequest(request); for (FileItem fileItem : fileItems) { System.out.println(fileItem); // 非文件表单项,即type!=file if (fileItem.isFormField()) { // username的value = ganyu // System.out.println(fileItem.getFieldName() + "的value = " + fileItem.getString()); } else { // a.jpeg String filename = fileItem.getName(); // 加上随机前缀,避免文件覆盖 filename = UUID.randomUUID() + "_" + System.currentTimeMillis() + "_" + filename; // 配置上传的文件夹,优化文件夹包含文件文件大小,每天的文件放入不同的文件夹/20010101 String filepath = getDirNameByDate(); // 获取工程下的路径作为上传目录 String fileRealPath = request.getServletContext().getRealPath(filepath); File uploadDir = new File(fileRealPath); // 如果文件夹不存,则创建文件夹 if (!uploadDir.exists()) { uploadDir.mkdirs(); System.out.println("创建文件上传保存目录: " + uploadDir); } // 剪切文件到指定的文件夹中,因为默认位StoreLocation在tomcat的temp目录下 File file = new File(uploadDir, filename); fileItem.write(file); System.out.println("上传文件成功: " + file); } // 日期文件名 private static String getDirNameByDate() { // /20020101 return "/" + DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDate.now()); }
-
下载
-
请求方法get
-
设置两个响应头
-
content-disposition
-
表示下载的数据的展示方式,如:inline内联形式(网页形式或者文件下载方式 attachment
- 下载完打开文件, 或者下载到本地目录中
-
-
ContentType,指定返回数据这类型MIME
-
-
响应体
- 在网络传输时是图片的原生数据(二进制), 图片在下载完看到,是因为浏览器做了解析
-
下载的文件名不要包含空格, base64可能转成+号, 镭雕事故
-
关于url编码的问题 (搞不懂, 以后再看
-
前端发送数据先把数据进行Base64编码, 然后发送时,浏览器进行url编码,
-
服务端收到数据时, 会先进行url解码(服务器做的),当表单数据类型为url-encoded时, 这时候会导致base64的url中+被urldecode为空格
- 在url编码中:空格–>+
-
-
示例
-
前端
1 2 3 4 5 6 7 8 9 10 11 12 13
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>下载文件</title> <base href="${pageContext.request.getContextPath()}"> </head> <body> <h1>文件下载</h1> <a href="DownloadServlet?filename=a.jpeg">下载a.jpeg</a><br> <a href="DownloadServlet?filename=b.jpeg">下载b.jpeg</a> <br> <a href="DownloadServlet?filename=夜の向日葵.mp3">下载夜の向日葵.mp3</a> </body> </html>
-
后端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
request.setCharacterEncoding("utf8"); response.setCharacterEncoding("utf8"); String filename = request.getParameter("filename"); ServletContext servletContext = request.getServletContext(); String downloadPath = "/download"; String downloadFile = downloadPath + "/" + filename; String mimeType = servletContext.getMimeType(downloadFile); response.setContentType(mimeType); response.setHeader("content-disposition", "attachment; filename=" + URLEncoder.encode(filename, "utf8")); // 读取下载的文件数据,返回给浏览器 InputStream is = servletContext.getResourceAsStream(downloadFile); ServletOutputStream os = response.getOutputStream(); // 使用commons-io工具copy文件 IOUtils.copy(is, os); System.out.println("下载成功: " + downloadFile);
-
web应用路径问题(尽量用绝对路径)
相对路径
-
假设我们的工程路径为http://localhost/webpath/
1 2
<a href="hello-servlet">Hello Servlet</a> <a href="http://localhost/webpath/hello-servlet">
-
页面所有的相对路径是,浏览器地址栏路径http://host:port/工程名/+资源来跳转
-
所以href=”hello-servlet”与 http://localhost/webpath/hello-servlet是等价的
-
相对路径的缺点:会让项目的互相调用关闭变得复杂
用base标签(在head标签内)指定页面的参考路径
-
如果用base指定后, 浏览器就会在解析时,取base+资源路径,即绝对路径
1 2 3 4
<base href="http://localhost/webpath/"> 斜杠/不能少 可以简写为: <base href="/webpath/"> <h1>当前位置: /d1/d2/b.html</h1>
1
<a href="../../a.html">到a.html页面去1</a>
1
<a href="a.html">到a.html页面去2</a>
加了base标签后, 就会以base的url为相对路径的基准点 和../../a.html等价
-
会跳转到这里
1
<a href="/a.html">
-
http://localhost/a.html / 开头的含义就是, 主机:端口/
-
base标签的问题, base标签基准url是写死了的硬编码, 改动时不方便
-
动态获取(jsp)
1
动态获取的工程路径: <%=request.getContextPath()%>
-
转发路径
-
在服务端解析第一个/会被解析成http://ip:port/Application Context
1 2 3
request.getRequestDispatcher("/d1/d2/b.html").forward(request,response); // 推荐 // 这个和上面的是等价的,推荐第一个 request.getRequestDispatcher("d1/d2/b.html").forward(request,response);
总结
-
在web中 /斜杠如果被服务端解析, 得到的地址是http://ip:port/ApplicationContext/
1
<url-pattern>/servletUrl</url-pattern>
servletContext.getRealPath(“/”) ==> 得到项目路径(磁盘绝对路径) request.getDispatcher(“/”)
-
斜杠在浏览器解析成http://ip:port/
1
<a href="/">
-
重定向
-
response.sendRedirect(“/”)等价于 http://ip:port/
-
response.sendRedirect(“b.html”);等价于 http://localhost/webpath/b.html 推荐的重定向写法 response.sendRedirect(getServletContext().getContextPath() + b.html”)
-
重定向等价于浏览器解析
-
-
浏览器url后面带斜杠/说明是路径, 不带斜杠说明是资源
服务端渲染技术(JSP)
为什么需要服务端渲染技术, html页面无法获取动态的数据,在Servlet中写HTML字符串拼接, 丑陋又难以维护
JSP
-
定义
- JSP, Java Server Pages, java服务器页面, 就是服务端渲染技术
-
特点
-
写JSP就像在写HTML
-
JSP不能直接用浏览器直接访问, 需要去请求Tomcat, 来解析成html文件
-
比html, 可以写动态数据名, 比Servlet,排版更友好,更优美
-
JSP基于Servlet, JSP就是对Servlet的包装, 可以理解JSP就是Servlet
-
理解JSP, 学thymeleaf(另一种服务端渲染技术)会更容易
-
-
JSP原理
-
jsp本质是一个Servlet程序,其性能和java关联
-
第一次访问jsp页面的时候, Tomcat会把jsp页面解析成一个java源文件, 并且对它编译成class字节码文件, 目录在: Catalinna_BASE/work目录下
-
jsp继承了HttpJspBase, 而HttpJspBase继承了HttpServlet
-
解析后的jsp.java中使用原生的out.println()拼接打印html字符串
-
-
使用
-
引入依赖
- servlet-api和jsp-api
-
常用指令
-
page
1
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
language表示jsp翻译后的语言, 只支持java
-
contentType, 等价于response.setContentType
-
pageEncoding, 表示当前jsp页面的字符集
-
import, java中的导包
-
-
声明脚本
-
<%! 声明java代码 %>
-
定义jsp需要的属性, 方法,静态代码块和内部类
1 2 3 4 5 6 7 8 9 10 11
<%! private Integer id; private String name; private static String job; static { job = "DE"; } public void setId(Integer id) { this.id = id; } %>
-
-
-
表达式脚本
-
<%=表达式%>
-
作用: 在jsp页面上输出数据
1 2 3
工作是: <%=job%> <br> 姓名是: <%=name%> <br> ID是: <%=id%> <br>
- 表达式脚本中的表达式不能以分号结束
-
-
-
代码脚本
- <% java代码 %>
-
案例
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%!
private List<Hero> heroes = new ArrayList<>();
{
heroes.add(new Hero(1, "矮人直升机", "轰炸"));
heroes.add(new Hero(2, "斯恩", "抢人头"));
}
%>
<%
for (Hero hero : heroes) {
%>
<tr>
<td><%=hero.getId()%>
</td>
<td><%=hero.getName()%>
</td>
<td><%=hero.getSkill()%>
</td>
</tr>
<%
}
%>
注释
<%– comment %>
标签
请求转发标签
1
2
<jsp:forward page="/a.jsp"></jsp:forward>
<%--与request.getRequestDispatcher("/a.jsp").forward(request,response)等价 %>
EL表达式
Expression Language语言
语法${key1}
主要作用是为了替代表达式脚本<%=%>, 本质上是语法糖
el表达式输出null为”“空串, jsp表达式标本输出null为null字符串
取得是请求域中数据, 取的对象要有get方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%
Book book = new Book();
book.setName("存在与时间");
book.setAuthor(new String[]{"马丁", "海德格尔"});
ArrayList<String> readers = new ArrayList<>();
Collections.addAll(readers, "维特根斯坦", "zangxin");
book.setReader(readers);
HashMap<String, Object> map = new HashMap<>();
map.put("Inder welt Sein", "在世界中存在");
map.put("to be or not to be", "向死而在");
book.setTopics(map);
request.setAttribute("book",book);
%>
book对象: ${book} <br>
book.name: ${book.name} <br>
book.author: ${book.author} <br>
book.author[0]: ${book.author[0]} <br>
book.readers: ${book.reader} <br>
book.readers[0]: ${book.reader.get(0)} <br>
book.topics: ${book.topics} <br>
book.topics[0]: ${book.topics.get("to be or not to be")} <br>
book.topics[0]: ${book.topics["to be or not to be"]}<br>
${}取数据按照从小大的的域取pageContext –>request –>session–>applicationContext
运算符
1
2
3
4
5
6
7
8
9
== eq
!= ne
< lt <= le
gt >= ge
&& and
|| or
! not
/ div
% mod
-
empty
-
判断一个数据是否为空, 空-true, 非空false
-
为空的情况
- null, 空串”” Object类型数组长度为0 list集合元素个数为0 map集合元素个数为0
-
-
三元运算符
?:和java一样
-
EL的隐含对象
-
四个特定域变量,类型为Map<String,Object>
-
pageScope
-
requestScope
-
sessionScope
-
1 2 3 4 5 6 7 8 9 10 11
<% pageContext.setAttribute("k1", "pageContext-k1"); request.setAttribute("k1", "request-k1"); session.setAttribute("k1", "session-k1"); application.setAttribute("k1", "application-k1"); %> pageContext数据: ${pageScope.k1} <br> request数据: ${requestScope.k1} <br> session数据: ${sessionScope.k1} <br> application数据: ${applicationScope.k1} <br> </body>
-
applicationScope
-
pageContext对象
1 2
协议:${pageContext.request.scheme} 端口:${pageContext.request.remotePort}
-
-
jstl标准标签库
-
JSP Standard Library
-
JSTL是为了替换代码脚本,jsp页面更简洁
-
依赖
1 2 3 4 5 6 7 8 9 10
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency>
-
-
jstl有5个标签库
-
先看核心标签库core, 以c开头
-
等价于request.setAttribute(“username”,”root”)
-
单分支
1
<c:if test="${10 < 2}">为真才显示我</c:if>
-
多分支选择
1 2 3 4 5 6 7 8 9 10 11
<c:choose> <c:when test="${requestScope.score >= 80}"> <h1>${score}-成绩优秀</h1> </c:when> <c:when test="${requestScope.score >= 60}"> <h1>${score}-及格了, 还不错</h1> </c:when> <c:otherwise> <h1>${score}-不及格</h1> </c:otherwise> </c:choose>
-
遍历数组,列表,map,必须提供get方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<%-- 等价于for(int i=1; <=5;i++) output 1 2 3 4 5 --%> <c:forEach begin="1" end="5" step="1" var="i"> ${i} </c:forEach> <%-- 数组 request.setAttribute("sports",new String[]{"lq","zq","ymq","tc"}) --%> <c:forEach items="${requestScope.sports}" var="sport"> ${sport} </c:forEach> <%-- Map<String, String> cities = new HashMap<>(); --%> <c:forEach items="${requestScope.cities}" var="city"> ${city.key}===${city.value} </c:forEach> <%-- List<String> list = new ArrayList<>(); --%> <c:forEach items="${list}" var="item"> ${item} </c:forEach>
-
-
-
-
JSP内置对象(build-in)
-
out
- JspWriter继承了Writer
-
request 客户端请求
- HtttpServletRequest
-
response响应对象
- HttpServletResponse
-
Session会话对象
- HttpSession
-
application
- ServletContext
-
pageContext
- jsp页面的上下文, 是一个域对象,可以setAttribute, 作用范围是本页面
-
exception
-
page
- 类似于this
-
config
- ServletConfig
-
-
JSP四大域对象
-
pageContext(jsp特有)
- 存放的数据只能在当前jsp页面使用
-
request
- 存放的数据在一次请求有效, 转发可以(重定向不行哦😯)
-
session
- 多次请求都可以, 只要会话没有变化
-
application
- ServletContext存放的数据在整个web应用运行期间有效, 范围最大,多个会话共享
-
测试
-
在一个jsp页面
- 四个域对象都有数据
-
跨jsp页面(转发)
- pageContext没有数据
-
重定向跨jsp页面
- pageContext和request没有数据
-
清除cookie(jsessionid)
- pageContext, request, session都没有数据
-
重启Tomcat
- 数据全都为null 了
-
-
总结
-
域对象是可以像map一样存取数据的对象. 四个域对象功能一样, 不同的是他们对数据存储的范围
-
存储范围大小: pageContext < request < Session < ServletContext(applicantion)
-
-
JSON转换
jackson
文档
- https://www.baeldung.com/jackson-object-mapper-tutorial
依赖
- jackson-databind
序列化
1
2
ObjectMapper om = new ObjectMapper();
String jsonString = om.writeValueAsString(books);
反序列化json string —> java
-
单个对象
1
om.readValue(jsonString, Book.class);
-
数组
1
List<Book> books1 = om.readValue(jsonString, new TypeReference<List<Book>>() {});
-
map
1
HashMap<String, Book> map = om.readValue(mapJson, new TypeReference<HashMap<String, Book>>() {});
-
注意事项
- JSON转换时,实体类必须提供getter/setter
项目练习
家居网购
-
架构
-
MVC三层架构,包管理技术😁
-
浏览器
- 客户端,解析网页数据
-
view
-
html,css.js
-
网页,和用户直接交互的界面
-
发起http请求,接收响应数据/页面
-
-
可用技术,vue
-
-
controller/servlet
- 视图层: 接收用户请求,调用Service完成业务处理 返回响应数据或者重定向,转发页面 可用技术:servlet, springmvc
-
service
- 业务层: 提供了很多业务API 业务逻辑主要在这里实现 针对不同业务逻辑, 调用dao层
-
dao
- 数据访问层: 完成对数据库可的操作 数据库的增删改查,Create, Read, Update, Delete 可用技术: JDBC,DButils, JdbcTemplate,Mybatis,Mybatis-plus,Hibernate
-
bean
- 放实体类, vo,domain/bean/pojo,dto
-
util
- 工具类
-
test
- 测试类
-
-
MVC正经解说
-
MVC全称是 Model模型,View视图,Controller控制器,MVC可以有效的指导WEB层代码有效分离
-
View视图
- 只负责界面的显示, 不接收任何与现实无关的代码,便于程序员和UI的分工合作(Vue/JSP/Thymeleaf/HTML)
-
Controller控制器
- 只负责接收请求, 调用业务层的代码处理请求, 返回派发页面, 是一个调度者的角色(Servlet)
-
Model将业务逻辑相关的数据封装为具体的javaBean, 其中不不掺杂任何与数据处理相关的代码(javabean/domain/pojo)
-
model最早期就是javabean, 通过service+dao获取的数据封装成bean, 而不去管细节,只要bean
-
随着业务复杂, model分化为service+dao
-
持久层技术的出现就演变为service + dao + 持久层技术(hibernate/mybatis/…)
-
-
MVC的理念是将软件拆分成为组件, 单独开发,组合使用(目的是解耦合),(SpringMVC)
-
-
-
Servlet合并
-
痛点: 一个url对应一个servlet, n个功能对应n个servlet….
-
解决方法1
-
前端输入框传递隐藏域action, 根据action的值不同来调用不同的业务方法
-
感觉耦合性太高, 涉及前后端高度耦合,直接淘汰
-
-
模板方法模式
-
抽象出BaseService, 在service方法中定义号要做的步骤, 把这些步骤定义为抽象的, 然后子类实现这些步骤
-
uri最后一截和方法名保持一致, 利用uri作为方法名,反射调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class BaseServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf8"); resp.setCharacterEncoding("utf8"); String uri = req.getRequestURI(); String methodName = uri.substring(uri.lastIndexOf("/") + 1); Class<? extends BaseServlet> clazz = this.getClass(); try { Object o = clazz.newInstance(); Method method = clazz.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); method.invoke(o, req, resp); } catch (Exception e) { resp.sendRedirect(req.getContextPath() + "/500.html"); e.printStackTrace(); } } }
-
扩展功能, 在模板方法中如加入字符集编码设置, 统一配置
-
-
-
insertSQL页面使用转发造成重复提交提问
-
在保存页面(insert)不能用转发,因为再次刷新页面时会出现重复插入的情况
-
原因:当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户刷新页 面(f5), 就会发起浏览器记录的上一次请求
-
解决方案使用重定向即可,因为重定向本质是两次请求,而且最后一次就是请求显示家 居,而不是添加家居
-
-
后端数据校验
-
校验数据格式是否正确
- 前端传入的数据都是string,需要查看下一下是否为数字,或者长度验证,判空验证等等
-
不能依赖于前端校验
- 在接口测试时, 可以随便改数据, 绕过前端
-
springmvc有一个数据校验工具支持JSR303,Hibernate validator,注解校验, 这里一个一个校验太繁琐了
-
-
工具类的使用
-
commons-beanutils
-
封装bean,把前端传过来的数据封装成java对象
-
Furn furn = new Furn(); BeanUtils.populate(furn,request.getParameterMap());
-
需要保持name和javabean的属性名一样
-
如果传来abc, javabean为Integer, 会设置为0(默认值), 不抛出异常, 所以指望抛出异常来校验不太行
-
-
-
两个html页面之间传递参数问题不通过服务服务器转发或者重定向
1 2 3 4 5 6
// 从页面A跳转到页面B,并传递参数 window.location.href = 'pageB.html?param1=value1¶m2=value2'; // 在页面B中获取参数 var params = new URLSearchParams(window.location.search); var param1 = params.get('param1'); var param2 = params.get('param2');
-
分页功能
-
分页参数
- pageSize –每页容量 pageNum—第几页 totalRows–总记录数 totalPage–总页数 pageTotalRow–每页行数 list—分页的数据记录
- 封装成对象Page
-
分页时,不要使用异步ajax, 要么代码全部写在success中, 否则导致获取的分页参数可能为null值
-
-
登录功能+鉴权
-
登录功能显示登录名和现实订单菜单入口(前端)
-
在登录servlet中完成登录后,保存用户对象到session中, cookie中写入用户名和权限级别, 来显示用户名,根据role级别是否显示后台管理入口
- moliza的cookie工具 https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie
-
-
权限校验
-
定义: 控制用户能访问的uri
-
对http请求进行Filter, 在web.xml中配置拦截的url和白名单url, 在doFilter方法中完成配置
-
AuthFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
public class AuthFilter implements Filter { private List<String> excludeUrls; private String loginHtmlPath = "/views/member/login.html"; @Override public void init(FilterConfig filterConfig) throws ServletException { excludeUrls = Arrays.stream(filterConfig.getInitParameter("excludeUrl").split(",")).toList(); System.out.println("excludeUrls = " + excludeUrls); } @Override public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; // 配置时没有配置项目路径,这里要去掉 String url = request.getRequestURI().substring(request.getContextPath().length()); User user = SessionUtils.getUser(request); // 如果是白名单, 和 登录过用户直接放行 if (excludeUrls.contains(url) || user != null) { System.out.println("放行: " + url); chain.doFilter(request, response); } else { ((HttpServletResponse) response).sendRedirect(request.getContextPath() + loginHtmlPath); } System.out.printf("%s请求访问url: %s\n", (user == null ? "" : user.getUsername()), url); } }
-
web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<filter> <filter-name>AuthFilter</filter-name> <filter-class>com.xxx.jiajumall.filter.AuthFilter</filter-class> <init-param> <param-name>excludeUrl</param-name> <!--逗号作为分割符--> <param-value>/views/manage/manage_login.html, ,/views/member/login.html,/manage/login,/furn/get,/furn/getPageByName, ,/furn/getPage,/cart/getItems,/cart/getCarInfo</param-value> </init-param> </filter> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/views/manage/*</url-pattern> <url-pattern>/views/order/*</url-pattern> <url-pattern>/furn/*</url-pattern> <url-pattern>/cart/*</url-pattern> </filter-mapping>
-
-
-
验证码功能 解决重复提交的利器
-
1.登录html页面向服务端请求生成验证码图片, 服务端返回验证码图片 2.显示验证码图片
- KaptchaServlet被请求生成验证码, get方法 google提供的工具, 返回验证码图片, 并且把验证存在session中, 将图片以http的方式返回
-
3.用户输入用户名,密码,验证码等
-
4.先在浏览器端验证,验证码是否正确
-
5.如果验证通过发送给服务端验证码值和用户信息
- 获取浏览器提交的信息, 从session中去除验证码值, 立即删掉验证码(防止重复提交), 比较验证值是否相等,相等时注册成功
-
实现
-
前端
-
加上时间戳是为了t=xxxxxx是为刷新验证码, 否则服务会返回原来的验证码(302认为你的内容没有发生改变)
1 2 3 4 5 6 7
function verifyCodeEvent() { $("#verifyCode").click(function () { $("#verifyCode").get(0).src = '/jiaju_mall/KaptchaServlet?t=' + (new Date().getTime()) }); } // <img id="verifyCode" alt="" src="/jiaju_mall/KaptchaServlet">
-
-
后端
-
生成验证码
-
google的kaptchaServlet
- 一个Servlet收到get请求时,返回一张根据验证生成的图片, 验证放在Session中
-
-
校验验证码
1 2 3 4 5 6 7 8 9 10
private boolean checkCode(HttpServletRequest request) { // 前端传来的验证码 String code = request.getParameter("code"); // session 生成的验证码 String token = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY); // 立即删除Session中验证码,避免重复使用 request.getSession().removeAttribute(Constants.KAPTCHA_SESSION_KEY); // 比对 return token != null && token.equalsIgnoreCase(code); }
-
-
-
-
购物车设计
-
Session版购物车, 免登录可以添加购物车,关闭会话后,就会清空信息
-
需求
- 可以把商品添加到,添加购物车,结算购物车, 清空购物车, 删除购物车商品, 修改商品数量
-
模型
-
Cart购物车对象
-
购物车项cartItemList
-
cartItem单项属性
-
id
-
img 商品图片
-
name 商品名
-
count 商品数量
-
price 商品单价
-
totalPrice 商品总价
-
-
-
clear()清空购物车
-
addItem()
-
removeItem()
-
updateCount
-
getTotalCount
-
getTotalPrice
-
getItem
-
-
-
-
订单设计
-
一个用户可以有多个订单, 一个订单可以有多个订单项
-
具体字段根据用户界面来设计
-
订单对象Order持有订单项对象OrderItem
-
订单表设计(tbl_order)
-
id
-
订单编号
- orderCode
-
关联用户
- userId
-
订单时间
- createTime
-
订单金额
- amount
-
订单状态,(未发货,未支付,..)
- status
create table tbl_order ( id int primary key auto_increment, orderCode varchar(14) not null default '', userId int not null default 0, amount decimal(12, 2) not null default 0 comment '订单金额', status tinyint not null default 0 comment '订单状态 0 未支付 1 已支付待发货 2订单取消 3 订单已完成 4 售后', createTime timestamp not null default current_timestamp on update current_timestamp ) charset utf8mb4 collate utf8mb4_general_ci engine 'Innodb';
-
订单详情
-
订单项表(单个商品)tbl_order_item
-
id
-
订单号
- orderCode
-
商品id
- furnId
-
商品名
- name
-
商品数量
- count
-
商品单价
- price
-
商品总价
- totalPrice
-
-
create table tbl_order_item ( id int primary key auto_increment, orderCode varchar(14) not null default '', furnId int not null default 0 comment '商品id', name varchar(64) not null default '' comment '商品名', count int not null default 0 comment '商品数量', price decimal(12, 2) not null default 0, totalPrice decimal(12, 2) not null default 0 ) charset utf8mb4 collate utf8mb4_general_ci engine 'Innodb';
-
-
下单流程
-
购物车提交订单
-
用户是否登录
-
未登录重定向登录页面,登录后返回购物车页面
-
已经登录
- 开启事务
获取购物车对象, 遍历购物车,
插入订单表,
订单项表,
查询商品表的库存
扣减商品表的库存
更新销量, 调用支付方法,
提交订单后要清空购物车
- 完成后跳转订单完成页面
- 开启事务
获取购物车对象, 遍历购物车,
插入订单表,
订单项表,
查询商品表的库存
扣减商品表的库存
更新销量, 调用支付方法,
提交订单后要清空购物车
-
-
-
-
-
是否要使用外键
-
使用外键可以从数据库层强制保证数据的一致性
-
不使用外键, 外键影响效率, 从程序上保持数据一致性
-
-
事务处理, 处理数据不一致
-
在进行提交订单时 扣减库存–修改商品表 插入订单表 插入订单项 都使用到了BaseDao中update方法, 这个方法被调用了多次, 每次都从连接池拿取,用完放回,不能保证提交订单过程中使用是同一个连接
1 2 3 4
// 从数据库连接池获取connection,不能保证获取的是同一个连接 connection = JdbcDruidUtil.getConnection(); return queryRunner.update(connection, sql, parameters); JdbcDruidUtil.close(null, null, connection);
-
解决方案
-
要保证在一次请求中, 是同一个connection
-
将自动提交事务, 改为手动提交事务
-
-
落地
-
使用Filter + ThreadLocal进行事务管理
-
在一次http请求中, servlet-service-dao的调用过程始终是一个线程, 这是使用ThreadLocal的前提
-
使用ThreadLocal来确保所有dao操作都在同一个Connection连接中完成
- 重写JDBCUtils方法 getConnection(关闭自动提交)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/** * 保证在同一个线程中,获取的是同一连接 */ public static Connection getConnection() { try { Connection connection = connectionThreadLocal.get(); // 说明当前ThreadLocal中没有连接 if (connection == null) { // 从连接池中获取一个连接放入到ThreadLocal中 connection = ds.getConnection(); // 设置事务为手动提交 connection.setAutoCommit(false); connectionThreadLocal.set(connection); } return connection; } catch (SQLException e) { throw new RuntimeException(e); } }
commit
```java /**
-
提交事务 */ public static void commit() { Connection connection = connectionThreadLocal.get(); if (connection != null) { try { connection.commit(); } catch (SQLException e) { throw new RuntimeException(e); } finally { try { connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } } // 当提交后,需要把connection从threadLocal中清除 // 不然会造成ThreadLocal长期持有该线程, 会影响效率 // 也因为Tomcat底层使用的使用线程池技术 connectionThreadLocal.remove(); } ```
rollback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/** * 回滚事务 */ public static void rollback() { Connection connection = connectionThreadLocal.get(); // 保证连接是有效的 if (connection != null) { try { connection.rollback(); } catch (SQLException e) { throw new RuntimeException(e); } finally { try { connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } } connectionThreadLocal.remove(); }
-
-
TransactionFilter事务过滤器 doFilter()
// 前置代码,手动提交事务 autoCommit=false 获取连接,跟当前线程绑定–>ThreadLocal // 后置代码 commit
// 对所有请求开启事务, 一定要把所有异常抛出来,在filter中在捕获
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebFilter(urlPatterns = {"/*"})
public class TransactionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
JdbcDruidUtil.commit(); // 统一提交事务
System.out.println("事务提交成功~~~~");
} catch (Exception e) {
System.out.println("发生异常,回滚事务");
JdbcDruidUtil.rollback();
e.printStackTrace();
}
}
}
-
统一错误页面处理
-
原理
- 抛出的异常会给tomcat, tomcat会根据errorPage来显示对应的页面(这个是转发到项目根路径, 所以404.html图片等资源使用相对路径时要注意是相对于根路径的)
- 异常一定要抛出来, 否则不能达成预期效果
-
配置,web.xml中
1 2 3 4 5 6 7 8
<error-page> <error-code>404</error-code> <location>/views/error/404.html</location> </error-page> <error-page> <error-code>500</error-code> <location>/views/error/500.html</location> </error-page>
-
-
ajax局部刷新页面功能
-
添加购物车后,购物车数量显示加1, 但是刷新了整个页面,占用资源多
-
修改成不刷新整个页面, 也能修改购物车数量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
// 添加购物车 function addToCart(furnId) { $.post({ url: '/jiaju_mall/cart/addItem', data: { id: furnId }, dataType: 'json', success: function (res, statusText, xhr) { if (res.code == 1) { // 处理转发或定向到ajax请求上 if (res.url) { window.location.href = res.url } else { alert("添加成功"); showCartItemCount() } } else { alert("添加失败") } }, }) } //显示购物车 function showCartItemCount() { $.getJSON({ url:'/jiaju_mall/cart/getCarInfo', success: function (res,statusText,xhr){ let cart = res.cart if (res.code == 1) { $("#cart_items_count").text(cart.totalCount) } } }) }
-
针对ajax的转发和重定向失效问题 (过滤器产生的问题)
- 主要是服务器得到的是ajax发送过来的request,也就是说这个请求不是浏览器请求的, 而是ajax请求的, 所以,Servlet对request进行转发和重定向都不能影响浏览器的跳转
- 解决:在过滤器中判断是否为AJAX请求,如果是AJAX返回url, 不是AJAX请求时进行转发或者重定向 然后ajax拿到请求后执行window.location.href= url
-
1
2
3
4
5
if (request.getHeader("x-requested-with").equals("XMLHttpRequest")) {
response.setContentType("application/json;utf8");
Result result = new Result(response);
result.put("url", request.getContextPath() + loginHtmlPath);
}
-
上传头像图片功能
- 处理multipart/form-data