目录

  • 目录
  • 前言
  • 什么是Servlet
  • Servlet生命周期
  • Servlet工作原理
  • Servlet的类层次结构
  • 实现Servlet的三种方式
    • 1) 实现Servlet接口
    • 2) 继承GenericServlet
    • 3) 继承HttpServlet
  • 注册Servlet的两种方式
    • 1) 通过Web-INF下的web.xml
    • 2) 通过annotation注解
  • http方法get/post
  • 了解 Web.xml
    • 1) 初始化参数
    • 2) 上下文初始化参数
    • 3) 通过注解设置配置信息
    • 4) 设置项目首页
  • Servlet API
    • ServletConfig 接口
    • ServletContext 接口
    • ServletRequest接口
      • 1) HttpServletRequest接口
    • ServletResponse接口
      • 1) HttpServletResponse接口
  • 会话管理
    • 1) 隐藏字段
    • 2) URL重写
    • 3) Cookie
    • 4) Session
    • 5) 对比 getParameter() 与 getAttribute()
  • 重定向与请求转发
    • 1) HttpServletResponse实现重定向
    • 2) RequestDispatcher实现请求转发
  • 监听器
    • Servlet 上下文监听器

      • 1) ServletContextListener 接口
      • 2) ServletContextAttributeListener 接口
    • Http会话监听器
      • 1) HttpSessionListener接口
      • 2) HttpSessionAttributeListener接口
      • 3) HttpSessionActivationListener 接口
    • Servlet 请求监听器
      • 1) ServletRequestListener 接口
      • 2) ServletRequestAttributeListener 接口
  • 错误处理
    • Http状态码点这
    • 配置错误页面
    • 打印错误日志
  • Servlet线程模型
    • 多线程模型
    • 单线程模型(了解)
    • 解决线程安全问题
  • Filter过滤器
    • 实现Filter实例
    • 注册Filter
      • 1) 通过 web.xml
      • 2) 通过注解
    • FilterChain 接口
  • 程序上下文路径的问题
  • Servlet文件上传
    • 编写表单
    • cos.jar

前言

简要学习整理了一下Servlet, 顺序可能有些许混乱, 复习使用

参考资料:Servlet 教程 | 菜鸟教程 (runoob.com)

此外,文章中引用一些大佬的文章, 在此不在一一列出. 感谢, 侵删.

如有错误请指出

什么是Servlet

Servlet生命周期

Servlet工作原理

Servlet的类层次结构

实现Servlet的三种方式

1) 实现Servlet接口

/* 实现Servlet接口 */
public class Servlet1 implements Servlet{//一个Servlet是有三个生命周期方法:init(), service(), destroy()//init()方法用于执行一些初始化操作,如果有初始化代码要执行,在此方法中编写代码@Overridepublic void init(ServletConfig config) throws ServletException {// 只会在第一次运行Servlet时被调用一次}//getServletConfig()方法用于获取Servlet配置信息@Overridepublic ServletConfig getServletConfig() {return null; // 一般返回null}//service()方法中编写处理请求的逻辑代码@Overridepublic 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的基本信息@Overridepublic String getServletInfo() {return null; // 一般返回null}//destroy()方法在一个servlet实例被摧毁之前调用,用于编写诸如释放资源的代码// 调用destroy() -> Stop Tomcat@Overridepublic void destroy() {}
}

2) 继承GenericServlet

该类是抽象类,不过只有一个抽象方法service(), 所以只强制重写service()

public class StudentInfoServlet extends GenericServlet {// 只需要重写service()@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {}//其他方法可以按需重写@Overridepublic void init() throws ServletException {super.init(); //To change body of generated methods, choose Tools | Templates.}
}

3) 继承HttpServlet

最常用的方式

public class StudentInfoServlet extends HttpServlet{// 按照请求类型,选择重写doGet() 或 doPost(), 也可同时实现@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 具体的逻辑}@Overrideprotected 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"} ) // 可以指定多个urlpublic class StuInfoServlet implements Servlet {@Overridepublic void init(ServletConfig servletConfig) {}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic void service(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {//...}@Overridepublic String getServletInfo() {return null;}@Overridepublic 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

API 参考

ServletConfig 接口

可以用来获取初始化参数的相关信息

ServletContext 接口

可以用来获取上下文初始化参数的相关信息

ServletRequest接口

主要用来处理客户端的请求, 获取请求的相关信息

API 点这

  • 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请求标头的相关信息

关于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 {@Overrideprotected 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="data:images/P001.jpg" alt="卫龙辣条" /><hr>卫龙辣条,你值得拥有</a></li><li><a href="ps?pid=P002" class="img-rounded img-thumbnail"><img src="data:images/P002.jpg" alt="卫龙辣条" /><hr>青岛大虾,不一样的大虾</a></li><li><a href="ps?pid=P003" class="img-rounded img-thumbnail"><img src="data:images/P003.jpg" alt="卫龙辣条" /><hr>四级真题,600包过</a></li><li><a href="ps?pid=P004" class="img-rounded img-thumbnail"><img src="data: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到期时间

      cookie默认到期时间是本次会话结束

      cookie1.setMaxAge(24*60); // 单位(s)
      

      可以利用cookie到期时间来删除该cookie对象

      cookie1,setMaxAge(0);
      
  • cookie发送给客户端(浏览器)

    cookie可以随http响应标头一同发送给客户端, 并由客户端将cookie保存到本地

    response.addCookie(cookie1); // response 为 HttpServletResponce对象
    
  • 获取cookie对象

    客户端保存cookie后, 还会将cookiehttp请求标头一起发送给创建它的服务器

    可以通过HttpServletRequest接口中的getCookies()方法获得Cookies

    Cookie[] 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();}
    }
    

    还可以使用jspEL表达式来获取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即调度

  • forword()

此方法可以将当前页面的requestresponse对象转发给其他页面, 从而实现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);
  • include()

此方法可以将当前页面的requestresponse对象转发给下一个页面的同时,还将当前页面的内容一并转发给了下一个页面, 所以响应页面显示的是两个页面的内容

注意, 浏览器地址栏仍然是调用请求转发页面的URL, MIMIE 和 字符集 仍由该页面指定

<a href="firstServlet?flag=3">3. 请求转发-include</a>
@WebServlet("/firstServlet")
public class FirstServlet extends HttpServlet {@Overrideprotected 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 {@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("程序启动");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println("程序停止");}
}

2) ServletContextAttributeListener 接口

public class MyServletContextAttributeListener implements ServletContextAttributeListener {// 分别在上下文对象属性 被添加 / 删除 / 修改时 被调用@Overridepublic void attributeAdded(ServletContextAttributeEvent scae) {System.out.println("上下文对象中的属性被添加:" + scae.getName() + " " + scae.getValue());}@Overridepublic void attributeRemoved(ServletContextAttributeEvent scae) {System.out.println("上下文对象中的属性被删除:" + scae.getName() + " " + scae.getValue());}@Overridepublic void attributeReplaced(ServletContextAttributeEvent scae) {System.out.println("上下文对象中的属性修改:" + scae.getName() + " " + scae.getValue());}
}

Http会话监听器

1) HttpSessionListener接口

@WebListener
// 分别在会话创建, 摧毁时调用
public class MyHttpSessionListener implements HttpSessionListener {@Overridepublic void sessionCreated(HttpSessionEvent se) {System.out.println("创建了一个会话, 会话ID" + se.getSession().getId());}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {System.out.println("摧毁了一个会话, 会话ID" + se.getSession().getId());}
}

2) HttpSessionAttributeListener接口

@WebListener
// 分别在会话属性 添加 / 修改 / 删除时 被调用
public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener {@Overridepublic void attributeAdded(HttpSessionBindingEvent se) {System.out.println("当前会话添加了属性" + se.getName() + " " + se.getValue());}@Overridepublic void attributeRemoved(HttpSessionBindingEvent se) {System.out.println("当前会话删除了属性" + se.getName() + " " + se.getValue());}@Overridepublic 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等都是发请求//所以都会导致请求对象被创建@Overridepublic void requestInitialized(ServletRequestEvent event) {//通过事件对象可获取到发生事件的请求对象,需要转成HttpServletRequest类型才能调用请求对象的更多方法HttpServletRequest req=(HttpServletRequest)event.getServletRequest();System.out.println("请求对象被创建,请求url:"+req.getRequestURL());}//该方法响应请求对象摧毁被调用,请求对象处理完会被立即摧毁,所以看控制台输出能看到请求对象创建很快,摧毁也很快@Overridepublic void requestDestroyed(ServletRequestEvent event) {//通过事件对象可获取到发生事件的请求对象,需要转成HttpServletRequest类型才能调用请求对象的更多方法HttpServletRequest req=(HttpServletRequest)event.getServletRequest();System.out.println("请求对象被摧毁,请求url:"+req.getRequestURL());}
}

2) ServletRequestAttributeListener 接口

@WebListener
// 分别在 请求对象属性 添加 / 删除 / 修改 时别调用
public class MyServletRequestAttributeListener implements ServletRequestAttributeListener{@Overridepublic void attributeAdded(ServletRequestAttributeEvent event) {System.out.println("向请求对象中添加属性:"+event.getName()+"-"+event.getValue());}@Overridepublic void attributeRemoved(ServletRequestAttributeEvent event) {System.out.println("从请求对象中删除属性:"+event.getName()+"-"+event.getValue());}@Overridepublic 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 {@Overrideprotected 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() 可以不重写*/@Overridepublic 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() 可以不重写*/@Overridepublic 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>&nbsp;&nbsp;<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包

Servlet学习记录相关推荐

  1. Servlet学习记录3

    提交表单信息 Web程序的任务是实现服务器与客户端浏览器之间的信息交互.客户端提交的信息可能来自表单里的文本框,密码框,选择框,单选按钮,复选框以及文件域.这些表单信息被以参数形式提交到了服务器.Se ...

  2. Android 学习记录(持续更新)

    Android 学习记录(持续更新) 1.AndroidManifest.xml 详解: http://www.jb51.net/article/73731.htm (AndroidManifest. ...

  3. Android 开发学习记录(4)---- httpclient使用(三)

    之前在Android 开发学习记录(3)---- httpclient使用(二)中介绍了如何使用httpclient访问需要账户登录的网址,当然首先是要有一个合法的登录账户. 但是现在好多网站在登录时 ...

  4. 基于Android的学生学习记录与提醒管理系统

    末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用Vue技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件 ...

  5. Pytorch学习记录-torchtext和Pytorch的实例( 使用神经网络训练Seq2Seq代码)

    Pytorch学习记录-torchtext和Pytorch的实例1 0. PyTorch Seq2Seq项目介绍 1. 使用神经网络训练Seq2Seq 1.1 简介,对论文中公式的解读 1.2 数据预 ...

  6. HTML5与CSS3权威指南之CSS3学习记录

    title: HTML5与CSS3权威指南之CSS3学习记录 toc: true date: 2018-10-14 00:06:09 学习资料--<HTML5与CSS3权威指南>(第3版) ...

  7. springboot @cacheable不起作用_Springboot学习记录13 使用缓存:整合redis

    本学习记录的代码,部分参考自gitee码云的如下工程.这个工程有详尽的Spingboot1.x教程.鸣谢! https://gitee.com/didispace/SpringBoot-Learnin ...

  8. 【Cmake】Cmake学习记录

    Cmake学习记录 1.1 常例 add_library(gen_reference_infogen_reference_info/gen_reference_info.hgen_reference_ ...

  9. ASP.NETCore学习记录(一)

    ASP.NETCore学习记录(一) asp.net core介绍  Startup.cs  ConfigureServices  Configure  0. ASP.NETCore 介绍 ASP.N ...

最新文章

  1. 图灵今年的生日礼物,是新版50英镑纸币
  2. Python 环境搭建
  3. 锁分区提升并发,以及锁等待实例
  4. 关闭运动轨迹_网球初学者如何正确入门网球运动,有哪些学习细节
  5. 消费分期群体-在校大学生和职场白领
  6. spring+hibernate 下载
  7. 【小松教你手游开发】【游戏渲染】单色shader,纹理shader
  8. ssm 退出登录 java_Spring+SpringMVC+Mybatis实现简单的用户的登录和退出
  9. 虚幻4引擎垃圾回收原理
  10. 集合的相关概念(开闭、有界无界、内点边界点等)
  11. 8 款强大且免费的 MySQL 数据库建模工具
  12. 计算机在聋校教学中有哪些作用,现代信息技术在聋校语文教学中的应用
  13. systemd服务创建服务demo
  14. 窗口桌面置顶(主窗口和子窗口)
  15. 华东师范大学数据学院2020夏令营机试题解
  16. 常见的五大数据分析模型
  17. 计算机存储器 控制器 运算器,运算器,控制器,存储器
  18. 【链环科技】智能硬件APP开发——通过硬件对接,实现软件与硬件设备的完美结合
  19. 计算机硬盘休眠更改,win7不让硬盘进入休眠状态的方法步骤
  20. Python进阶之前端和爬虫基础

热门文章

  1. kettle开发篇-JavaScript脚本-Day31
  2. Photoshop设计网站首页
  3. 50个经典的Photoshop磨皮教程
  4. 虚拟主机或者网站服务器,网站服务器空间(也称:虚拟主机,或者云主机)
  5. 双胞胎宝宝取名要注意什么
  6. 解决使用Skia图形库时遇到的几个问题
  7. android之远程控制电脑播放ppt
  8. 子类调用父类方法时,方法中的变量用谁的
  9. 服务宕机问题排查思路
  10. Android三指截屏的实现