目录
- 目录
- 前言
- 什么是Servlet
- Servlet生命周期
- Servlet工作原理
- Servlet的类层次结构
- 实现Servlet的三种方式
- 注册Servlet的两种方式
- http方法
get/post
- 了解 Web.xml
- Servlet API
- 会话管理
- 重定向与请求转发
- 监听器
- 错误处理
- Servlet线程模型
- Filter过滤器
- 程序上下文路径的问题
- Servlet文件上传
前言
简要学习整理了一下
Servlet
, 顺序可能有些许混乱, 复习使用参考资料:Servlet 教程 | 菜鸟教程 (runoob.com)
此外,文章中引用一些大佬的文章, 在此不在一一列出. 感谢, 侵删.
如有错误请指出
什么是Servlet
Servlet生命周期
Servlet工作原理
Servlet的类层次结构
实现Servlet的三种方式
1) 实现Servlet
接口
/* 实现Servlet接口 */
public class Servlet1 implements Servlet{
//一个Servlet是有三个生命周期方法:init(), service(), destroy()
//init()方法用于执行一些初始化操作,如果有初始化代码要执行,在此方法中编写代码
@Override
public void init(ServletConfig config) throws ServletException {
// 只会在第一次运行Servlet时被调用一次
}
//getServletConfig()方法用于获取Servlet配置信息
@Override
public ServletConfig getServletConfig() {
return null; // 一般返回null
}
//service()方法中编写处理请求的逻辑代码
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//每发送一个请求,都会一个请求对象和一个响应对象被创建,并且会传入service()方法
//可以通过请求对象获取请求相关的信息
//可以通过响应对象操作响应
//1. 获取请求参数
//根据请求参数的名称(name属性)获取请求参数的值(用户输入的值),并将获取的参数值存入某个变量中
String sid=req.getParameter("studentId");
String sname=req.getParameter("studentName");
//2. 根据考号查询四六级成绩
Random rand=new Random();
int score=Math.abs(rand.nextInt(711)); //生成一个710以内的随机非负整数
//3. 生成响应,显示查询的成绩给客户端
//通过响应对象可以获取一个打印器,用于输出响应内容到客户端
//返回的是一个PrintWriter对象
//可通过设置响应内容的编码修复中文乱码问题
//但是必须要在获取PrintWriter对象之前设置
//可调用setContentType()方法设置响应内容的MIME类型和内容的字符编码
res.setContentType("text/html;charset=utf-8");
PrintWriter out=res.getWriter();
out.println("<h1>查询结果</h1><hr>");
out.println("<br>考号:"+sid);
out.println("<br>姓名:"+sname);
out.println("<br>成绩:"+score);
}
getServletInfo()方法用于获取Servlet的基本信息
@Override
public String getServletInfo() {
return null; // 一般返回null
}
//destroy()方法在一个servlet实例被摧毁之前调用,用于编写诸如释放资源的代码
// 调用destroy() -> Stop Tomcat
@Override
public void destroy() {
}
}
2) 继承GenericServlet
类
该类是抽象类,不过只有一个抽象方法service()
, 所以只强制重写service()
public class StudentInfoServlet extends GenericServlet {
// 只需要重写service()
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
}
//其他方法可以按需重写
@Override
public void init() throws ServletException {
super.init(); //To change body of generated methods, choose Tools | Templates.
}
}
3) 继承HttpServlet
类
最常用的方式
public class StudentInfoServlet extends HttpServlet{
// 按照请求类型,选择重写doGet() 或 doPost(), 也可同时实现
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 具体的逻辑
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 具体的逻辑
}
}
如果表单提交方式与之不对应, 则会405
注册Servlet的两种方式
1) 通过Web-INF
下的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>
<description>查询相关信息</description>
<servlet-name>StuInfoServlet</servlet-name> <!-- 指定Servlet类的名字,随便起-->
<servlet-class>com.qdu.servlet.StuInfoServlet</servlet-class> <!-- 指定Servlet类的全限定名(包名.类名)-->
</servlet>
<servlet-mapping>
<servlet-name>StuInfoServlet</servlet-name> <!-- 需与上同 -->
<url-pattern>/StuInfoServlet</url-pattern>
<url-pattern>/a</url-pattern> <!--可以指定多个访问路径-->
</servlet-mapping>
</web-app>
2) 通过annotation注解
// 通过注解的方式注册Servlet
@WebServlet("/StuInfoServlet") // 即指定Servlet访问路径是当前目录上一级的StuInfoServlet.html
@WebServlet(value = "/StuInfoServlet") // value可不写
@WebServlet(urlPatterns = "/StuInfoServlet") // urlPatterns等价于value
@WebServlet(urlPatterns = {"/StuInfoServlet","/a"} ) // 可以指定多个url
public class StuInfoServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {
//...
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
http方法get/post
-
get
可以被缓存
<form action="/StuInfoServlet" class="form-inline" method="get"> <!--默认是get-->
action
属性指定了提交表单后跳转的地址最直观的感受是浏览器地址栏中的URL包含了表单提交的相关信息
-
post
不会被缓存
<form action="/StuInfoServlet" class="form-inline" method="post">
而
post
方法则不会不过相关信息也可以找到
了解 Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" 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_3_1.xsd">
<!--上下文参数, 所有的Servlet都可以去使用-->
<context-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</context-param>
<context-param>
<param-name>email</param-name>
<param-value>100111@qq.com</param-value>
</context-param>
<servlet>
<servlet-name>servlet1</servlet-name> <!-- 该Servlet的名称 -->
<servlet-class>com.qdu.servlet.Servlet1</servlet-class> <!-- 该Servlet对象的全限定名
<!-- 初始化参数, 只能被该Servlet1使用-->
<init-param>
<param-name>dbUserName</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>servlet2</servlet-name>
<servlet-class>com.qdu.servlet.Servlet2</servlet-class>
<init-param>
<param-name>str</param-name>
<param-value>只能被Servlet2获取并使用</param-value>
</init-param>
</servlet>
<servlet-mapping> <!-- 指定 servlet 和 URL 模式之间的映射 -->
<servlet-name>servlet1</servlet-name> <!-- 需要与上面保持一致 -->
<url-pattern>/s1</url-pattern> <!-- 指定该Servlet实例的上下文路径 -->
</servlet-mapping>
<servlet-mapping>
<servlet-name>servlet2</servlet-name>
<url-pattern>/s2</url-pattern>
</servlet-mapping>
</web-app>
1) 初始化参数
初始化参数只能供该Servlet
实例使用
-
获取参数
通过
ServletConfig
对象获取ServletConfig servletConfig = this.getServletConfig(); String dbUserName = servletConfig.getInitParameter("dbUserName"));
2) 上下文初始化参数
上下文初始化参数可以被所有的Servlet
实例使用
-
获取参数
通过上下文对象
ServletContext
获取ServletContext servletContext = this.getServletContext(); String encoding = servletContext.getInitParameter("encoding"));
-
设置上下文属性
上下文对象也可作为共享属性的媒介, 如Servlet1将一些动态生成的参数放入上下文对象, 供Servlet2使用
servletContext.setAttribute("message","Servlet2也可以看到"); // 设置共享属性 String message = servletContext.getAttribute("message") // 获取共享属性
也可以通过
EL表达式
获取${applicationScope.message};
3) 通过注解设置配置信息
@WebServlet(
name = "servlet1",
urlPatterns = "/s1",
// 设置初始化参数
initParams = {
@WebInitParam(name = "dbUserName",value = "root"),
@WebInitParam(name = "dbPassWord", value = "niit"),
}
)
上下文属性只能通过
Web.xml
来设置
4) 设置项目首页
<web-app>
<welcome-file-list>
<!-- 设置项目的首页,可以设置多个, 如果不设置, 默认为index.html(前提项目中有)-->
<welcome-file>login.jsp</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Servlet API
ServletConfig 接口
可以用来获取初始化参数的相关信息
ServletContext 接口
可以用来获取上下文初始化参数的相关信息
ServletRequest接口
主要用来处理客户端的请求, 获取请求的相关信息
-
public String[] getParameterValues(String name);
获取一组参数的值, 比如获取多选框组件的值
爱好: <!--多个同一个组的复选框名称可以相同,可以不同,但是一般建议相同--> <!--如果相同,则参数作为一个字符串数组提交给服务器 hobby--> <input id="h1" checked type="checkbox" name="hobbies" value="LOL"><label for="h1">英雄联盟</label> <input id="h2" checked type="checkbox" name="hobbies" value="贪玩蓝月"><label for="h2">贪玩蓝月</label> <input id="h3" checked type="checkbox" name="hobbies" value="王者荣耀"><label for="h3">王者荣耀</label> <input id="h4" checked type="checkbox" name="hobbies" value="绝地求生"><label for="h4">绝地求生</label> <input id="h5" type="checkbox" name="hobbies" value="学习"><label for="h5">学习</label> <button class="btn btn-primary">提交注册</button> </form>
String[] hobbies = req.getParameterValues("hobbies"); // 获取多选框的值 out.println("<br>爱好:"); for(String hobby : hobbies) { out.println(hobby + " "); }
1) HttpServletRequest接口
继承自
ServletRequest
接口, 除了可以获取请求参数, 还可以获取Http请求标头
的相关信息
-
常用方法
-
public Cookie[] getCookies();
-
public HttpSession getSession(boolean create);
-
ServletResponse接口
主要用来响应请求, 比如获取PrintWriter
来打印相应页面
1) HttpServletResponse接口
继承自
ServletResponse
接口, 除了可以相应请求外, 还可以设置响应标头信息以及将响应页面重定向
-
常用方法
- public void addCookie(Cookie cookie)
-
通过标头设置页面自动刷新
resp.setIntHeader("refresh",5);
public class Servlet1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setIntHeader("refresh",5); PrintWriter out = resp.getWriter(); Calendar calendar = Calendar.getInstance(); out.println(calendar.getTime()); } }
-
通过标头设置页面禁止缓存
response.setHeader("Cache-Control", "no-cache");
会话管理
由于
HTTP
是无状态协议, 无法储存用户跨网页活动, 所以需要一些会话管理技术来存储用户跨网页活动信息,简单来说就是实现数据共享
1) 隐藏字段
隐藏字段不会显示在页面中, 但是可以在源代码中查看
如果你想跨页面传输一些信息, 又不需要用户填写,可以使用隐藏字段
<form>
<input type="hidden" value="001" name="pid">
</form>
value
指定隐藏字段的值
name
服务器根据该值获取隐藏字段
-
获取隐藏字段
request.getParameter("pid")
2) URL重写
即将要传输的信息添加到响应页面的URL
<a href="transcript.jsp?sid=<%= request.getParameter("sid")%>"> 查看个人成绩单</a>
响应页面的URL
http://localhost:8080/C03_D02_URL_Rewriting_war_exploded/transcript.jsp?sid=2019208888
-
实例
使用同一个Servlet对象处理不同的请求
点击不同的图片, 利用URL重写
添加响应商品编号到URL,从而只跳转至同一个Servlet,但动态生成响应页面
<ul class="proList">
<li>
<!-- URL 重写 -->
<a href="ps?pid=P001" class="img-rounded img-thumbnail">
<img src="images/P001.jpg" alt="卫龙辣条" />
<hr>
卫龙辣条,你值得拥有
</a>
</li>
<li>
<a href="ps?pid=P002" class="img-rounded img-thumbnail">
<img src="images/P002.jpg" alt="卫龙辣条" />
<hr>
青岛大虾,不一样的大虾
</a>
</li>
<li>
<a href="ps?pid=P003" class="img-rounded img-thumbnail">
<img src="images/P003.jpg" alt="卫龙辣条" />
<hr>
四级真题,600包过
</a>
</li>
<li>
<a href="ps?pid=P004" class="img-rounded img-thumbnail">
<img src="images/P004.jpg" alt="卫龙辣条" />
<hr>
快乐肥宅水,你今天快乐了吗?
</a>
</li>
</ul>
3) Cookie
-
创建
Cookie
对象Cookie类在javax.servlet.http包中
Cookie
对象由服务器创建public Cookie(String name, String value) { validation.validate(name); this.name = name; this.value = value; }
Cookie
只有一个构造方法, 需要在创建对象时指定name
,value
Cookie cookie1 = new Cookie("lastVisitTime", DateUtil.getCurrentTime());
-
-
设置
cookie
到期时间cookie默认到期时间是本次会话结束
cookie1.setMaxAge(24*60); // 单位(s)
可以利用
cookie
到期时间来删除该cookie
对象cookie1,setMaxAge(0);
-
-
将
cookie
发送给客户端(浏览器)cookie
可以随http响应标头
一同发送给客户端, 并由客户端将cookie
保存到本地response.addCookie(cookie1); // response 为 HttpServletResponce对象
-
获取
cookie
对象客户端保存
cookie
后, 还会将cookie
随http请求标头
一起发送给创建它的服务器可以通过
HttpServletRequest
接口中的getCookies()
方法获得CookiesCookie[] cookies = request.getCookies(); String lastVisitTime = new String(); String number = new String(); for(Cookie cookie : cookies) { if("lastVisitTime".equals(cookie.getName())) { lastVisitTime = cookie.getValue(); } else if("number".equals(cookie.getName())) { number = cookie.getValue(); } }
还可以使用
jsp
的EL表达式
来获取cookie对象laim${cookie.cookieName.value}
例如通过EL表达式
获取cookie对象的值, 并传递给<input>
的value
属性
账号:
<input type="text" name="username" value="${cookie.username.value}">
密码:
<input type="password" name="password" value = "${cookie.password.value}">
4) Session
服务器运行时为每一个浏览器的每一个网站用户创建独有的session
对象
-
创建/获取
session
对象* @param create * true to create a new session for this request if necessary; * false to return null if there's no current HttpSession session = httpServletRequest.getSession(true);
-
设置
session
对象属性session.setAttribute("userName","root");
-
获取
session
对象属性String username = (String) session.getAttribute("username");
${sessionScope.username}
-
获取
session id
session
存储在服务器端, 若服务器突然关闭,数据会丢失, 访问量大时, 会给session
造成压力 浏览器借助cookie,将
session id
作为一个cookie存到浏览器中session.getId();
-
结束会话
session
在会话超时或调用invalidate()
后摧毁-
设置会话最大非活动时间(即会话等待请求的最长时间
- 通过
session
对象
- 通过
session.setMaxInactiveInterval(76060); // 单位s,也可设置0或负值, 表示该会话始终保持激活
- 通过`web.xml` ```xml <web-app> <session-config> <session-timeout>30</session-timeout> <!-- 单位 minute --> </session-config> </web-app>
- 调用
invalidate
session.invalidate();
-
5) 对比 getParameter() 与 getAttribute()
-
getParameter()
此方法主要用来获取 用户提交给
request
对象的参数-
如获取URL重写的数据
<a href="ps?pid=P001">link</a>
// ps 对应的 Servlet String pid = request.getParameter("pid");
<!-- 可以使用EL的隐式对象param获取request对象的初始化参数 --> ${param.pid}
-
如获取表单提交的数据
<form> <input type="hidden" value="001" name="pid"> </form>
String pid = request.getParameter("pid");
-
-
getAttribute()
此方法主要用来获取页面间的共享数据
-
如获取
request
对象中的数据req.setAttribute("product",product); // 转发...下一个页面获取 req.getAttribute("product"); ${requestScope.product}
-
如获取
session
对象中的数据session.setAttribute("userName","root"); session.getAttribute("userName"); ${sessionScope.product}
-
如获取
ServletContext
对象中的数据servletContext.setAttribute("userName","root"); servletContext.getAttribute("userName");
-
重定向与请求转发
1) HttpServletResponse实现重定向
-
重定向响应页面
sendRedirect()
之后的代码仍会被执行浏览器地址栏的
url
是重定向之后的,MIME类型和字符集类型
由重定向之后的页面指定response.sendRedirect(url)
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String choice = req.getParameter("choice"); if("1".equals(choice)) { resp.sendRedirect("1.html"); } else { resp.sendRedirect("2.html"); } }
2) RequestDispatcher实现请求转发
http://c.biancheng.net/view/4013.html
dispatcher
即调度
此方法可以将当前页面的request
和response
对象转发给其他页面, 从而实现Servlet间的小范围数据共享
注意转发的同时会跳转至相应的页面, 且
forword()
之后的代码不会被执行但浏览器地址栏仍然是调用请求转发页面的URL, 所以MIME和字符集仍然由该页面指定, 而不是
projectInfo.jsp
// 将产品信息放入请求对象request
req.setAttribute("product",product);
// 将当前请求对象和响应对象转发给 productInfo.jsp, 所以productInfo.jsp页面使用同一个请求和响应对象
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/projectInfo.jsp");
requestDispatcher.forward(req,resp);
此方法可以将当前页面的request
和response
对象转发给下一个页面的同时,还将当前页面的内容一并转发给了下一个页面, 所以响应页面显示的是两个页面的内容
注意, 浏览器地址栏仍然是调用请求转发页面的URL, MIMIE 和 字符集 仍由该页面指定
<a href="firstServlet?flag=3">3. 请求转发-include</a>
@WebServlet("/firstServlet")
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<link rel=\"stylesheet\" href=\"css/bootstrap.min.css\"/>");
out.println("<link rel=\"stylesheet\" href=\"css/style.css\"/>");
out.println("<div class=\"container bg-success text-center text-success\">");
out.println("<br><h1>这是FirstSevlet输出的内容1</h1>");
req.setAttribute("number",666);
req.getRequestDispatcher("second.jsp").include(req,resp);
}
}
<!-- second.jsp -->
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>second页面</title>
<link rel="stylesheet" href="css/bootstrap.min.css"/>
<link rel="stylesheet" href="css/style.css"/>
</head>
<body>
<div class="bg-danger text-danger text-center">
<br>
<hr>
<h2>这是second.jsp页面的内容</h2>
<p>请求参数flag: ${param.get("flag")}</p>
<!-- param为EL表达式的隐式对象,用于获取初始化参数 -->
<p>请求中的属性: ${requestScope.number}</p>
<hr>
<br>
</div>
</body>
</html>
监听器
监听器用于监听相关的事件是否发生, 以便在该事件发生时做出响应
监听器分别监听 Servlet上下文属性, Http会话, Servlet请求
,
对应了Servlet中的三个作用范围(从大到小)
如果要在应用程序中实现监听器, 除了实现 相应的接口之外, 还需要 注册相应监听器:
-
通过
web.xml
<web-app> <listener> <description>请求监听器</description> <listener-class>com.qdu.listener.MyServletRequestListener</listener-class> </listener> <listener> <description>请求属性监听器</description> <listener-class>类的全限定名</listener-class> </listener> </web-app>
-
通过注解
@WebListener
Servlet 上下文监听器
1) ServletContextListener 接口
@WebListener
/**
* 上下文监听器, 上下文对象在程序创建时创建, 摧毁时摧毁, 所以也就是监听程序启动和停止
*/
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("程序启动");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("程序停止");
}
}
2) ServletContextAttributeListener 接口
public class MyServletContextAttributeListener implements ServletContextAttributeListener {
// 分别在上下文对象属性 被添加 / 删除 / 修改时 被调用
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
System.out.println("上下文对象中的属性被添加:" + scae.getName() + " " + scae.getValue());
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
System.out.println("上下文对象中的属性被删除:" + scae.getName() + " " + scae.getValue());
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
System.out.println("上下文对象中的属性修改:" + scae.getName() + " " + scae.getValue());
}
}
Http会话监听器
1) HttpSessionListener接口
@WebListener
// 分别在会话创建, 摧毁时调用
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("创建了一个会话, 会话ID" + se.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("摧毁了一个会话, 会话ID" + se.getSession().getId());
}
}
2) HttpSessionAttributeListener接口
@WebListener
// 分别在会话属性 添加 / 修改 / 删除时 被调用
public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("当前会话添加了属性" + se.getName() + " " + se.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("当前会话删除了属性" + se.getName() + " " + se.getValue());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("当前会话修改了属性" + se.getName() + " " + se.getValue());
}
}
3) HttpSessionActivationListener 接口
Servlet 请求监听器
1) ServletRequestListener 接口
@WebListener
public class MyServletRequestListener implements ServletRequestListener{
//该方法响应请求对象创建被调用,请求页面、css、图片、servlet、js等都是发请求
//所以都会导致请求对象被创建
@Override
public void requestInitialized(ServletRequestEvent event) {
//通过事件对象可获取到发生事件的请求对象,需要转成HttpServletRequest类型才能调用请求对象的更多方法
HttpServletRequest req=(HttpServletRequest)event.getServletRequest();
System.out.println("请求对象被创建,请求url:"+req.getRequestURL());
}
//该方法响应请求对象摧毁被调用,请求对象处理完会被立即摧毁,所以看控制台输出能看到请求对象创建很快,摧毁也很快
@Override
public void requestDestroyed(ServletRequestEvent event) {
//通过事件对象可获取到发生事件的请求对象,需要转成HttpServletRequest类型才能调用请求对象的更多方法
HttpServletRequest req=(HttpServletRequest)event.getServletRequest();
System.out.println("请求对象被摧毁,请求url:"+req.getRequestURL());
}
}
2) ServletRequestAttributeListener 接口
@WebListener
// 分别在 请求对象属性 添加 / 删除 / 修改 时别调用
public class MyServletRequestAttributeListener implements ServletRequestAttributeListener{
@Override
public void attributeAdded(ServletRequestAttributeEvent event) {
System.out.println("向请求对象中添加属性:"+event.getName()+"-"+event.getValue());
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent event) {
System.out.println("从请求对象中删除属性:"+event.getName()+"-"+event.getValue());
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent event) {
System.out.println("修改请求对象中的属性:"+event.getName()+"-"+event.getValue());
}
}
错误处理
Http状态码点这
由服务端发送给客户端
除了使用上述状态码, 我们可以自己指定状态码和信息
-
sendError()
指定发送给客户端的错误状态码 (4XX / 5XX)
httpServletResponse.sendError(404,"请求的资源不存在(哈哈)"); // HttpServletResponse.SC_NOT_FOUND为httpServletResponse接口中定义的一些常量, 用于表示状态码 httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "请求的资源不存在(哈哈)");
-
setStatus()
指定发送给客户端的非错误状态码 (1XX / 2XX / 3XX)
httpServletResponse.setStatus(302,"临时重定向");
配置错误页面
我们可以对出现的错误状态码
, 在Web.xml
下配置错误页面(或者servlet类, 如利用servlet类收集错误日志)
<web-app>
<!-- 当项目出现404错误时, 会跳转至 /error404.html -->
<error-page>
<error-code>404</error-code>
<location>/error404.html</location>
</error-page>
<!-- 当项目出现500错误时, 会跳转至 /error500.html -->
<error-page>
<error-code>505</error-code>
<location>/error500.html</location>
</error-page>
</web-app>
除了可以根据错误状态码
配置错误页面外, 还可以在Web.xml
下对项目抛出的异常类型
配置错误页面
<web-app>
<error-page>
<!-- 当项目抛出异常时, 会跳转至相应的servlet处理异常-->
<exception-type>java.lang.Exception</exception-type>
<!-- 也可以指定处理错误对象的servlet, 如从servlet中收集日志 -->
<location>/errorServlet</location>
</error-page>
</web-app>
打印错误日志
ErrorServlet.java
调用servletContext.log()
可以打印日志信息到Tomcat的日志窗口
/**
* 自定义错误页面可以是一个html或jsp页面,也可是一个servlet
* 如果是针对简单错误代码的错误页面,使用html即可,如果是需要收集错误和异常信息,可以考虑使用servlet或jsp作为错误页面
*/
@WebServlet("/errorServlet")
public class ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
ServletContext servletContext = getServletContext();
//如果一个servlet是作为自定义错误页面使用,则该错误和异常信息会被以属性的形式添加到请求对象中
//从请求对象中可根据固定的属性名,获取错误和异常信息,如javax.servlet.error.status_code用于获取状态码
//此外,ServletContext接口的log方法可用于写入信息到服务器日志,如果需要,可考虑使用该方法写入日志内容
//也可借助一些其他专门的日志类库写入日志,如log4j等
servletContext.log("....................................................................");
servletContext.log("状态代码:" + req.getAttribute("javax.servlet.error.status_code"));
servletContext.log("错误消息:" + req.getAttribute("javax.servlet.error.message"));
servletContext.log("异常类型:" + req.getAttribute("javax.servlet.error.exception_type")); servletContext.log("....................................................................");
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().println("这是处理500错误的Servlet,作为自定义错误页面用!");
}
}
日志信息:
Servlet线程模型
多线程模型
Servlet默认的是单实例, 多线程
模式, 即 Servlet只在第一次收到请求时被实例化, 接下来的请求都是使用同一个Servlet实例, 假设当前对该进行并发请求
,则这些线程同时操作一个Servlet实例, 所以容易引发线程安全问题
单线程模型(了解)
解决线程安全问题
Servlet上下文对象和会话中的属性, 都可能引发线程安全问题, 因为在同一时刻可能由多个线程访问它们
-
同步化方法
synchronized
保证同一时刻只会有一个线程访问该方法, 其他线程等待public synchronized void withdraw100(String who) { System.out.println(" 取钱 "); try { System.out.println(who + "取款前余额:" + balance); Thread.sleep(50); balance -= 100; System.out.println(who + "取款后余额:" + balance); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println(" 结束 "); }
-
同步化代码块
也可以同步化代码块,保证这一块代码在某一时刻只能被一个线程访问
public void withdraw100(String who) { // 同步化代码块, 范围越小越好 synchronized (this) { try { System.out.println(" 取钱 "); System.out.println(who + "取款前余额:" + balance); Thread.sleep(50); balance -= 100; System.out.println(who + "取款后余额:" + balance); System.out.println(" 结束 "); } catch (InterruptedException ex) { ex.printStackTrace(); } } }
Filter过滤器
过滤器可以拦截一组请求和响应,用来请求预处理和响应后处理
不单单可以拦截Servlet
资源, 还可以拦截jsp
资源
实现Filter实例
使用Filter
拦截一组请求, 在Servlet响应之前, 通过request.setCharacterEncoding()
设置传入请求对象的参数的字符集, 防止中文乱码
注意response.setContentType("text/html;charset=UTF-8");
是设置响应内容的编码, 并不能改变用户提交给请求对象的参数(如表单)的编码
-
设置前
-
设置后
package com.qdu.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter(
filterName = "EncodingFilter",
urlPatterns = {"/rs","/ps","/ms"}
)
public class EncodingFilter implements Filter {
/**
* init() 可以不重写
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter在程序启动时,调用init(),实例化该实例,只会实例化一次");
}
@Override
/**
* servletRequest 过滤器拦截的请求对象
* servletResponse 过滤器拦截的响应对象
* filterChain 过滤器链
*/
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 设置传入请求对象的参数的字符集类型, 防止中文乱码(一般doPost()发出的请求会乱码)
servletRequest.setCharacterEncoding("UTF-8");
// doFilter 方法用于通知 Web 容器把请求交给 Filter 链中的下一个 Filter 去处理,
// 如果当前调用此方法的 Filter 对象是Filter 链中的最后一个 Filter,那么将把请求交给目标 Servlet 程序去处理。
filterChain.doFilter(servletRequest,servletResponse);
}
/**
* destroy() 可以不重写
*/
@Override
public void destroy() {
System.out.println("程序关闭前, 调用destroy()释放相关资源");
}
}
注册Filter
1) 通过 web.xml
web.xml
可以同时注册多个过滤器, 这些过滤器的拦截顺序与在web.xml
中的注册顺序<filter-mapping>
一致
<web-app>
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.qdu.filter.EncodingFilter</filter-class>
</filter>
<!-- 注册过滤器 -->
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<!-- <url-pattern>/*</url-pattern> 表示该Filter可以拦截所有Servlet的实例; -->
<url-pattern>/rs</url-pattern>
<url-pattern>/ms</url-pattern>
<url-pattern>/ps</url-pattern>
</filter-mapping>
</web-app>
<url-pattern>
用来指定该过滤器可以拦截哪些servlet实例
通过不同的url模式,可以拦截一个/一组资源发出的请求
精确模式:/rs
拦截名为/rs
的资源的请求
前缀模式:/students/*
拦截students
下的所有请求
后缀模式:*.jsp
拦截后缀为.jsp
的左右请求
所有请求:/*
2) 通过注解
@WebFilter(
filterName = "EncodingFilter",
urlPatterns = {"/rs","/ps","/ms"}
)
@WebFilter({"/rs","/ps","/ms"})
FilterChain 接口
FilterChain
即过滤器链, 该接口只定义了一个 doFilter
方法
public void doFilter(ServletRequest request, ServletResponse response) throws java.io.IOException.ServletException
doFilter
方法用于通知 Web 容器把请求交给 Filter 链中的下一个 Filter 去处理,如果当前调用此方法的 Filter 对象是Filter 链中的最后一个 Filter,那么将把请求交给目标 Servlet 程序去处理。
过滤器拦截到请求后会先执行chain.doFilter()之前的代码,然后执行下一个过滤器或者servlet,紧接着执行chain.doFilter()之后的代码。
只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter
方法,
目标 Servlet 的 service 方法都不会被执行。
利用此特性, 可以实现权限控制
, 如没有登录就不能查看购物车等
程序上下文路径的问题
https://www.cnblogs.com/tuyang1129/p/10724898.html
-
程序上下文路径即部署在服务器中的项目的路径
-
项目目录结构
-
firstServlet.java
-
index.jsp
点击任意一个按钮会出现404
错误, 即找不到资源
观察地址栏发现, 路径不正确程序上下文路径/jsp/firstServlet?flag=2
这是因为我们在写路径使使用了相对路径firstServlet?flag=2
而在点击超链接 / 重定向 / 请求转发 时跳转的路径是相对于当前页面所在文件夹(jsp)的路径
,
如果写路径时使用了绝对路径/
, 如/firstServlet?flag=2
, 则跳转的路径会拼接在服务器IP地址之后,这也是不对的
正确应该为程序上下文路径/firstServlet?flag=2
我们可以使用request.getContextPath()
来获取程序的上下文路径
成功跳转
Servlet文件上传
- Demo:
Demo将个人头像保存到相应位置, 并将表单信息保存到数据库. 这里只给出文件上传的相关实现
编写表单
需要注意的是, 表单中不仅包括文本信息, 还包括文件这样的二进制信息.
所以必须设置表单的enctype="multipart/form-data"
,且表单的提交方式必须为post
<form class="form-inline" id="add_form" method="post" enctype="multipart/form-data">
<label for="iempid">员工编号:</label>
<input id="iempid" type="text" name="empId" placeholder="输入员工编号,如E001" class="form-control sinfo">
<br><br>
<label for="iname">员工姓名:</label>
<input id="iname" type="text" name="empName" placeholder="输入姓名,不超过4个中文字符" class="form-control sinfo">
<br><br>
<label for="igender">员工性别:</label>
<select id="igender" name="empGender" class="form-control select">
<option value="男">男</option>
<option value="女">女</option>
</select>
<br><br>
<label for="idob">出生日期:</label>
<input id="idob" name="empDob" type="date" class="form-control">
<br><br>
<span class="title">个人头像: </span>
<input type="file" id="fileInput" name="file">
<br><br><br>
<button type="submit" formaction="aes1" class="btn btn-success">添加员工1</button>
<button type="submit" formaction="aes2" class="btn btn-danger">添加员工2</button>
</form>
可以将文件上传到Tomcat服务器上, 但需要注意的是, 每次重新部署项目, 之前上传到Tomcat的图片就会丢失, 所以我们需要将文件上传到一个真实的磁盘路径下
cos.jar
此方式依赖第三方jar包cos.jar
, 这是老师上课介绍的一种方式
-
maven
<dependency> <groupId>servlets.com</groupId> <artifactId>cos</artifactId> <version>05Nov2002</version> </dependency>
如果未使用maven构建项目, 可从这里获取jar包