前篇
今天是持续学习WEB的第二天,希望每天进步一点点!
英语雅思考试进击中...以后每天分享自己的随记! 耶!
中篇
关于前端向后端传输中文数据的事儿。
remark:tomcat8之前的版本底层采用的编码集为ISO-8859-1
Servlet的继承关系
相关方法
Servlet的生命周期
说到生命周期,先解释一下什么是生命周期?
从出生到死亡的过程就是生命周期,在servlet中对应到了三个方法:init()、service()、destory()
在默认情况下,Tomcat容器接收到Socket请求时,容器会将对应的servlet进行实例化(容器通过反射调用构造构造方法)、
初始化(调用init方法)、服务(调用service方法)、
销毁(随着容器的关闭调用destory方法)
当对这个servlet进行第二次请求时,只会调用service方法,因为在第一次请求的时候容器已经完成了这个servlet的实例化和初始化的操作。
当容器关闭时,其中的所有servlet实例会被销毁,调用对应servlet的销毁方法。
以上,就是一个servlet的生命周期(创建和销毁的整个过程)。
Tomcat容器与Servlet的关系?
在默认情况下,第一次请求时,对应的Servlet才会被Tomcat容器去实例化、初始化、然后再服务;这样的好处是什么?可以提高系统启动的速度。 这样做的缺点是什么?第一次请求时,耗时较长。
结论: 如果需要提高系统的启动速度,当前默认情况就是这样。如果需要提高响应速度,我们应该设置Servlet的初始化时机。
Servlet的初始化时机
默认是第一次接收请求时,实例化,初始化
我们可以通过<load-on-startup>来设置servlet启动的先后顺序,数字越小,启动越靠前,最小值0
<servlet>
<servlet-name>Demo01Servlet</servlet-name>
<servlet-class>com.crystal.test.Demo01Servlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
Servlet的特点
Servlet在容器中是:单例的、线程不安全的
单例:所有的请求都是同一个实例去响应
线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断。但是在中间某个时机,另一个线程改变了这个成员变量的值,从而导致第一个线程的执行路径发生了变化
结论
我们已经知道了servlet是线程不安全的!所以: 尽量的不要在servlet中定义成员变量。如果不得不定义成员变量,那么:①不要去修改成员变量的值 ②不要去根据成员变量的值做一些逻辑判断
Http协议
Http : Hyper Text Transfer Protocol 超文本传输协议
Http最大的作用就是规定了请求和响应数据的格式。
浏览器发送给服务器的数据:请求报文;服务器返回给浏览器的数据:响应报文。
会话
Http是无状态的,这里的无状态是指:服务器无法判断两次请求是同一个客户端发过来的,还是不同的客户端发过来的;如果不能解决这个问题,就会导致混乱,比如:A客户要结账,刷新了之后,再次请求之后,服务器就分不清到底是谁了。
类似于生活场景:比如一个盲人要与很多人进行对话交流,此时他要分清楚每一个第一次与他进行对话的人,记住他们各自的特征,方便第二次进行交谈的时候,能把话题无缝衔接,而不是重新要问"我们接下来要聊些什么呢?"而此时的会话就相当于一个连接客户端与服务器交互的专属通道,也就是盲人要与特定的人进行特定对话的一个标记或特征。
通过会话跟踪技术可以来解决无状态的问题。
会话跟踪技术
客户端第一次发请求给服务器,服务器会获取请求中携带的session(在客户端指的是cookie),获取不到,就会创新新的,然后响应给客户端。 下次客户端给服务器发请求时,会携带第一次服务器分配的sessionID,服务器就能获取到了。 常用的API:
request.getSession(); 获取当前的会话,没有则创建一个新的会话 request.getSession(true); 与不带参数的一样 request.getSession(false);获取当前的会话,没有则返回null,不会创建新的
session.getId();获取sessionID
session.isNew();判断当前session是否是第一次创建
session.getMaxInactiveInterval();session的非激活间隔时长,默认为1800秒
session.invalidate();强制性让会话立即失效
session保存作用域
session保存作用域:服务器端会有专门的内存区域,去保存某一个session中的数据,这个session就是以上所说的"专属通道"。
常用的API:
void session.setAttribute(k,v);
Object session.getAttribute(k);
void removeAttribute(k);
每一个请求都有自己唯一的sessionID,且对应唯一的session保存作用域,第二次请求会带有自己的sessionID,对应到自己的session保存作用域,获取对应的数据。
资源跳转技术:服务端内部转发和客户端重定向
服务端内部转发:一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的,URL没有发生变化request.getRequestDispatcher("...").forward(request,response);
客户端重定向:两次请求响应的过程。客户端肯定知道,请求URL有变化 response.sendRedirect("...");
浏览器会显示302状态码,表示该请求会发生重定向
Thymeleaf——视图模板技术
总结
1.servlet的生命周期:
初始化(tomcat通过反射进行实例化)、init、service(一旦前端访问servlet,容器首先会调用servlet类中的service方法,然后去判断请求方式,如果方式不匹配,就会调用Servlet类中对应的do方法,浏览器报出405错误)、destory(随着容器的关闭所有servlet会销毁:容器会去调用各自的destory方法)
2.http协议: 8种不同的请求方式和协议具体内容(关注http详篇)
3.会话和保存作用域
4.Thymeleaf的简单使用
小试牛刀
demo: 使用最"原始"的技术将单表的数据查询出来显示在html表格中
- 数据库:mysql8.0
- 数据库连接技术:JDBC
- 视图渲染:Thymeleaf
package com.test;
import com.test.dao.FruitDAO;
import com.test.dao.impl.FruitDAOImpl;
import com.test.myssm.ViewBaseServlet;
import com.test.pojo.Fruit;
import javax.servlet.ServletException;
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.util.List;
/**
* @author crystal
* @create 2022-12-13 10:17
*/
@WebServlet("/fruit.do")
public class IndexServlet extends ViewBaseServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
FruitDAO fruitDAO = new FruitDAOImpl();
List<Fruit> fruitList = fruitDAO.getAll();
HttpSession session = req.getSession();
session.setAttribute("fruitList",fruitList);
super.processTemplate("index",req,resp);
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>水果库存管理</title>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<h1>水果库存管理系统</h1>
<table>
<input type="hidden" name="operate" value="fruit"/>
<tr>
<th>名称</th>
<th>单价</th>
<th>库存</th>
<th>备注</th>
</tr>
<tr th:if="${#lists.isEmpty(session.fruitList)}">
<td colspan="4">对不起,库存为空!</td>
</tr>
<tr th:unless="${#lists.isEmpty(session.fruitList)}" th:each="fruit : ${session.fruitList}">
<td th:text="${fruit.fname}">apple</td>
<td th:text="${fruit.price}">12</td>
<td th:text="${fruit.fcount}">3</td>
<td><a>删除</a></td>
</tr>
</table>
</body>
</html>
辅助代码
package com.test.myssm;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* @author crystal
* @create 2022-12-03 23:25
*/
public abstract class BaseDAO<T> {
// 读取配置文件,获取连接
public Connection getConn(){
// 获取类加载器
try {
// InputStream inputStream = BaseDAO.class.getResourceAsStream("jdbc.properties");
InputStream inputStream = BaseDAO.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(inputStream);
String driverClass = pros.getProperty("driverClass");
// 注册驱动
Class.forName(driverClass);
String url = pros.getProperty("url");
String user = pros.getProperty("user");
String password = pros.getProperty("password");
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void closeResource(Connection conn,PreparedStatement ps,ResultSet rs){
try {
if (rs != null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 执行update操作
public void update( String sql,Object...args){
try {
Connection conn = getConn();
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
ps.execute();
closeResource(conn,ps,null);
} catch (SQLException e) {
e.printStackTrace();
}
}
Class<?> clazz = null;
public BaseDAO(){
Type genericSuperclass = this.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] arguments = parameterizedType.getActualTypeArguments();
clazz = (Class<T>) arguments[0];
}
// 查询,返回单条记录
public <T> T queryOne(String sql,Object ...args){
try {
Connection conn = getConn();
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1,args[i]);
}
ResultSet rs = ps.executeQuery();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
if (rs.next()){
T t = (T) clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
String columnLabel = metaData.getColumnLabel(i + 1);
Object columnValue = rs.getObject(i + 1);
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t,columnValue);
}
closeResource(conn,ps,rs);
return t;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 查询,返回多条记录
public <T> List<T> queryList(String sql,Object ...args){
try {
Connection conn = getConn();
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1,args[0]);
}
ResultSet rs = ps.executeQuery();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
List<T> list = new ArrayList<>();
while (rs.next()){
T t = (T) clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
String columnLabel = metaData.getColumnLabel(i + 1);
Object columnValue = rs.getObject(i + 1);
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t,columnValue);
}
list.add(t);
}
closeResource(conn,ps,rs);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.test.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;
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 {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
}
<?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>