05、servlet
5.1、idea创建JavaWeb项目及相关问题
【idea】
1、新建项目 - 新建
module
2、在
module
中添加web
3、创建
artifact
- 部署包4、
lib
和artifact
之间的关系
- 先有
artifact
,后来才添加的mysql.jar
,此时,这个jar
包并没有添加到部署包中。- 那么在
Project Structure
中有一个problems
提示,点击fix
,选择add to artifact
- 另外,可以直接把
lib
文件夹直接新建在WEB-INF
下。
- 缺点:这个
lib
只能是当前这个module
独享,如果有第二个module
,我们需要再次重复的新建lib
。5、在
deployment
,修改application Context
,然后在会带server
选项卡,检查URL的值。
URL
的值指的是,Tomact
启动完成后自动打开所指定的浏览器,其默认访问的网址。- 启动后,报
404
错误
404
:找不到指定的资源- 如果我们的网址是:http://localhost:8080/webPro1/,那么表明我们访问的是
index.html
- 可以通过
<welcome-file-list>
标签进行欢迎页。(在tomcat
的web.xml
中设置,或者在自己项目的web.xml
中设置)<welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
6、
405
问题:当前请求的方法不支持
- 如:我们表单
method=post
,那么Servlet
必须对应doPost
,否则报405
错误。7、空指针或者是
NumberFormatException
,因为有价格和库存,如果价格获取不到,结果你想对null
进行Integer.parseInt()
就会报错。错误的原因大部分是因为name = "price"
此处写错了,结果在Servlet
端还是使用request.getParameter("price")
去获取;
5.2、servlet获取参数
【add.html】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="add" method="post">
名称:<input type="text" name="fname"/></br>
单价:<input type="text" name="price"/></br>
库存:<input type="text" name="fcount"/></br>
备注:<input type="text" name="remark"/></br>
<input type="submit" value="添加"/>
</form>
</body>
</html>
【addServlet.java】
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 addServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//post方式下,设置编码:防止中文乱码
request.setCharacterEncoding("UTF-8");
//1、获取用户(客户端)发送的数据
String fname = request.getParameter("fname");
String priceStr = request.getParameter("price");
Integer price = Integer.parseInt(priceStr);
String fcountStr = request.getParameter("fcount");
Integer fcount = Integer.parseInt(fcountStr);
String remark = request.getParameter("remark");
//System.out.println("fname:"+fname);
FruitDAO fruitDAO = new FruitDAOImpl();
boolean flag = fruitDAO.addFruit(new Fruit(fname,price,fcount,remark));
System.out.println(flag == true ?"添加成功" : "添加失败");
}
}
【web.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>AddServlet</servlet-name>
<servlet-class>com.javaweb.servlets.addServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AddServlet</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>
</web-app>
- 过程:
- 用户发请求,
action = post
- 项目中,
web.xml
中找到url-pattern = /add
->第12行 - 找第11行的
servlet-name = AddServlet
- 找和
servlet-mapping
中的servlet-name
一致的servlet
->第7行 - 找第8行中的
servlet-class
->com.javaweb.servlets.addServlet
- 用户发送的
post
请求(method = post
),因此tomcat
会执行AddServlet
中的doPost
方法
5.3、处理servlet请求参数中文乱码问题
5.3.1、Post请求方式
post
方式下,设置编码为UTF-8
:防止中文乱码
request.setCharacterEncoding("UTF-8");
5.3.2、Get请求方式
1、get方式目前不需要设置编码(基于tomcat8)
2、如果是get请求发送的中文数据,转码稍微有点麻烦(tomcat之前)
String fname = request.getParameter("fanme");
//1.将字符串打散成字节数组
byte[] bytes = fname.getBytes("ISO-8859-1");
//2.将字节数组按照设定的编码方式重新组装成字符串
fname = new String(bytes, "UTF-8");
注意:设置编码这一句代码必须在所有的获取参数动作之前
5.4、Servlet的继承关系——重点:查看服务方法(service())
5.4.1、继承关系
5.4.2、相关方法
javax.servlet.Servlet
接口:
-
void init(config)
:初始化方法 -
void service(request, response)
:服务方法 -
void destory()
:销毁方法
javax.servlet.GenericServlet
抽象类:
-
void service(request, response)
:服务方法,仍然是抽象的
javax.servlet.http.HttpServlet
抽象子类:
void service(request, response)
:服务方法,不是抽象的
String method = req.getMethod();
获取请求的方式- 各种if判断,根据请求方式不同,决定去调用不同的do方法
- 在HttpServlet`这个抽象类中,do方法都差不多
如:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
service()
:
- 当有请求过来时,
service
方法会自动响应(其实是tomcat
容器调用的)- 在
HttpServlet
中去分析请求的方式:get
、post
、head
、put
、delete
等- 然后决定调用哪个
do
开头的方法- 那么在
HttpServlet
中这些do
方法默认都是405的实现风格——要我们子类去实现对应的方法,否则默认会报405错误。因此,在新建
Servlet
时,要去考虑请求方法,从而决定重写哪个do
方法。
5.5、Servlet的生命周期
- 生命周期:从出生到死亡的过程就是生命周期。
对应Servlet
中的三个方法:
-
init()
; -
service()
; -
destory()
;
- 默认情况下:
- 第一次接收请求时,这个
Servlet
会进行实例化(调用构造方法)、初始化(init()
),然后服务(service()
)。 - 从第二次请求开始,每一次都是服务
- 当容器关闭时,其中的所有
Servlet
实例会被销毁,调用destory()
;
- 通过案例发现:
Tomcat
只会创建一个Servlet
实例,所有的请求都是这个实例去响应。- 默认情况下,第一次请求时,Tomcat才会去实例化,初始化,然后再服务。
- 优点:提高系统的启动速度
- 缺点:第一次请求时,耗时较长
- 因此得出结论:
- 如果需要提高系统的启动速度,当前默认情况就是这样。
- 如果需要提高响应速度,我们需要设置servlet的初始化时机。
- Servlet的初始化时机:
- 默认第一次接收请求时,实例化,初始化
- 可以通过
<load-on-startup>
来设置Servlet启动的先后顺序,数字越小,启动越靠前,最小为0。
【web.xml】
<servlet>
<servlet-name>Demo02Servlet</servlet-name>
<servlet-class>com.javaweb.servlets.Demo02Servlet</servlet-class>
<!--数字越小(最好不要是负数),启动的顺序越靠前-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Demo02Servlet</servlet-name>
<url-pattern>/demo02</url-pattern>
</servlet-mapping>
- Servlet在容器中是:单例的、线程不安全的
- 单例:所有的请求都是一个实例去响应。
- 线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断,但是在中间某个时机,另一个线程改变了这个成员变量的值,从而导致第一个线程的执行路径发生变化。
线程1访问该代码块,获取了变量num的值为1,正准备将num=1送进判断之前,线程2访问该代码块,并将num值改变,此时线程1获取的num值也发生了改变,从而导致线程1的执行路径发生变化。
启发:尽量不要在Servlet中定义成员变量。如果不得不定义成员变量,那么不要去根据成员变量的值做一些逻辑判断。
5.6、HTTP协议
5.6.1、介绍
1、HTTP
:Hyper Text Transfer
超文本传输协议。
2、HTTP
是无状态的。
3、HTTP
最大的作用就是确定了请求和响应数据的格式。
- 浏览器发送给服务器的数据:请求报文;
- 服务器返回给浏览器的数据:响应报文。
5.6.2、请求报文
包含三个部分:
- 请求行
作用:展示当前请求的最基本信息
- 请求方式
- 请求的
URL
:访问地址- 请求的
HTTP
协议的版本(一般是HTTP1.1
)
- 请求(消息)头
请求消息头中包含了很多客户端需要告诉服务器的信息。
作用:通过具体的参数对本次请求进行详细的说明
格式:键值对,键和值之间用冒号隔开
相对比较重要的请求消息头:
名称功能Host服务器的主机地址Accept声明当前请求能够接受的媒体类型Refer当前请求来源页面的地址Content-Length请求体内容的长度Content-Type请求体的内容类型,这一项的具体值是媒体类型中的某一种Cookie浏览器访问服务器时携带的Cookie数据
- 请求体
作用:作为请求的主题,发送数据给服务器,具体来说其实就是
POST
请求方式下的请求参数。三种情况:
Get
方式:没有请求体,但是有一个queryString
Post
方式:有请求体,form data
Json
格式:有请求体,request payload
5.6.3、响应报文
包含三个部分:
- 响应(状态)行
包含三个信息:
- 协议
- 响应状态码
- 响应状态
- 响应(消息)头
包含了服务器的信息;服务器发送给浏览器的信息(内容的媒体类型、编码、内容长度等)
- 响应体
响应的实际内容(比如添加
add.html
页面时,响应的内容就是<html><head><body><form>
…)
5.7、Session
HTTP
是无状态的。
-
HTTP
无状态:服务器无法判断请求是同一个客户端发过来的,还是不同的客户端发过来的。 - 无状态带来的现实问题:第一次请求是添加商品到购物车,第二次请求是结账;如果这两次请求服务器无法区分是同一个用户的,那么就会导致混乱。
- 通过会话跟踪技术来解决无状态的问题
- 会话跟踪技术
- 客户端第一次发请求给服务器,服务器获取
session
,获取不到,则创建新的session
,然后响应给客户端。 - 下次客户端给服务器发请求时,会把
sessionID
带给服务器,那么服务器就能获取到了,服务器判断这一次请求和上次某次请求是同一个客户端,从而能够区分开客户端。 - 常用的API:
-
request.getSession()
:获取当前的会话,没有则创建一个新的会话 -
request.getSession(true)
:效果和不带参数相同 -
request.getSession(false)
:获取当前会话,没有则返回null
,不会创建新的 -
session.getId()
:获取sessionID
-
session.isNew()
:判断当前的session
是否是新的 -
session.getMaxInactiveInterval()
:获取session的非激活间隔时长,默认1800秒。 -
session.setMaxInactiveInterval()
:获取session的非激活间隔时长 -
session.invalidate()
:强制性让会话立即失效 - …
- session保存作用域
- session保存作用域是和具体的某一个session对应的
- 常用的API:
void session.setAttribute(key,value)
Object session.getAttribute(key)
void removeAttribute(key)
5.8、服务器内部转发以及客户端重定向
- 服务器内部转发:
request.getRequestDispatcher("...").forward(request,response);
- 一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的。
- 地址栏无变化
- 客户端重定向:
response.sendRedirect("...");
- 两次请求响应的过程。客户端肯定知道请求
URL
有变化。- 地址栏有变化
5.9、Thymeleaf——试图模板技术
5.9.1、Thymeleaf概述
- Thymeleaf优势
- SpringBoot官方推荐使用的试图模板技术,和SpringBoot完美整合。
- 不经过服务器运算仍然可以直接查看原始值,对前端工程师更友好。
- 流程:
5.9.2、Thymeleaf的使用
- 添加
thymeleaf
的jar
包 - 在
web.xml
文件中添加配置
【web.xml】
<!--配置上下文参数-->
<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>
- 配置前缀:
view-prefix
- 配置后缀:
view-suffix
- 新建一个
servlet
类ViewBaseServlet
【ViewBaseServlet.java】
package com.fruit.myssm.myspingmvc;
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());
}
}
4.使自己新建的servlet
继承ViewBaseServlet
import com.fruit.dao.FruitDAO;
import com.fruit.dao.impl.FruitDAOImpl;
import com.fruit.myssm.myspingmvc.ViewBaseServlet;
import com.fruit.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;
//servlet从3.0版本开始支持注解方式的注册
@WebServlet("/index")
public class indexServlet extends ViewBaseServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FruitDAO fruitDAO = new FruitDAOImpl();
List<Fruit> fruitList = fruitDAO.getFruitList();
//保存到session作用域
HttpSession session = request.getSession();
session.setAttribute("fruitList", fruitList);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图名称 上去
//逻辑视图名称:index
//物理视图名称:view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称:/index.html
super.processTemplate("index",request,response);
}
}
5.根据逻辑视图名称,得到物理视图名称
- 视图名称是
index
- 那么
thymeleaf
会将这个 逻辑视图名称 对应到 物理视图名称 上去 - 逻辑视图名称:
index
- 物理视图名称:
view-prefix
+ 逻辑视图名称 +view-suffix
- 所以真实的视图名称:
/index.html
5.9.3、thymeleaf`的标签
<th:if>
<th:unless>
<th:each>
<th:text>
5.10、保存作用域
- 原始情况下,保存作用域有四个:
-
page
:页面级别,现在几乎不用 -
request
:请求级别,一次请求响应范围 -
session
:会话级别,一次会话范围 -
application
:应用级别,整个应用程序范围
request
:一次请求响应范围
- 重定向
- 内部转发
代码演示:
【demo01.java】
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;
@WebServlet("/demo01")
public class Demo01Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.向request保存作用域保存数据
request.setAttribute("uname","lili");
//2.重定向
//response.sendRedirect("demo02");
//3.服务器端转发
request.getRequestDispatcher("demo02").forward(request,response);
}
}
【demo02.java】
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;
@WebServlet("/demo02")
public class Demo02Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取request保存作用域保存的数据
Object obj = request.getAttribute("uname");
System.out.println("unameObj = " + obj);
}
}
session
:一次会话范围有效,同一个客户端有效
此时,第二个客户端请求,服务器端无法打印出lili
。application
:一次应用程序范围有效
【demo5.java】
//演示application保存作用域
@WebServlet("/demo05")
public class Demo05Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.向application保存作用域保存数据
//ServletContext:Servlet上下文
ServletContext application = request.getServletContext();
application.setAttribute("uname","lili");
//2.重定向
//response.sendRedirect("demo02");
//3.服务器端转发
request.getRequestDispatcher("demo02").forward(request,response);
}
}
【demo06.java】
@WebServlet("/demo06")
public class Demo06Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取application保存作用域保存的数据
ServletContext application = request.getServletContext();
Object obj = application.getAttribute("uname");
System.out.println("unameObj = " + obj);
}
}
5.11、路径问题