Servlet学习记录

目录

前言

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

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

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

如有错误请指出

什么是Servlet

Servlet生命周期

image-20210312233740299

Servlet工作原理

image-20210312233310349

Servlet的类层次结构

image-20210312234127474

实现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()

image-20210310104530894

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

image-20210310105122871

注册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包含了表单提交的相关信息

    image-20210310093354948

  • post

    不会被缓存

    <form action="/StuInfoServlet" class="form-inline" method="post">
    

    post方法则不会

    image-20210310093458785

    不过相关信息也可以找到

其他对比点这

了解 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 接口

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

image-20210316173335957

image-20210316173347701

ServletContext 接口

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

image-20210316173655468

image-20210316173709322

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

image-20210316181729284

关于Http标头点这

  • 常用方法

    • public Cookie[] getCookies();
      
    • public HttpSession getSession(boolean create);
      

ServletResponse接口

主要用来响应请求, 比如获取PrintWriter来打印相应页面

image-20210316180636405

1) HttpServletResponse接口

继承自ServletResponse接口, 除了可以相应请求外, 还可以设置响应标头信息以及将响应页面重定向

image-20210316183103700

image-20210316183202515

  • 常用方法

    • 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是无状态协议, 无法储存用户跨网页活动, 所以需要一些会话管理技术来存储用户跨网页活动信息,

简单来说就是实现数据共享

image-20210320155527268

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对象处理不同的请求

    image-20210323134309105

点击不同的图片, 利用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

image-20210320155728076

  • 创建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 {

    @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>

image-20210330163301323

监听器

监听器用于监听相关的事件是否发生, 以便在该事件发生时做出响应

监听器分别监听 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状态码点这

由服务端发送给客户端

image-20210329164337617

image-20210329164403607

除了使用上述状态码, 我们可以自己指定状态码和信息

  • sendError()

    指定发送给客户端的错误状态码 (4XX / 5XX)

    httpServletResponse.sendError(404,"请求的资源不存在(哈哈)");
    // HttpServletResponse.SC_NOT_FOUND为httpServletResponse接口中定义的一些常量, 用于表示状态码
    httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "请求的资源不存在(哈哈)");
    

    image-20210329165612970

  • setStatus()

    指定发送给客户端的非错误状态码 (1XX / 2XX / 3XX)

    httpServletResponse.setStatus(302,"临时重定向");
    

    image-20210329170901261

配置错误页面

我们可以对出现的错误状态码, 在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,作为自定义错误页面用!");
 }
}

日志信息:

image-20210329172529163


Servlet线程模型

多线程模型

Servlet默认的是单实例, 多线程模式, 即 Servlet只在第一次收到请求时被实例化, 接下来的请求都是使用同一个Servlet实例, 假设当前对该进行并发请求,则这些线程同时操作一个Servlet实例, 所以容易引发线程安全问题

image-20210406163255975

单线程模型(了解)

image-20210406163353684

解决线程安全问题

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");是设置响应内容的编码, 并不能改变用户提交给请求对象的参数(如表单)的编码

  • 设置前

    image-20210406141740730

  • 设置后

    image-20210406141606296

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

  • 程序上下文路径即部署在服务器中的项目的路径

    image-20210406151639967

  • 项目目录结构

    image-20210406150144343

  • firstServlet.java

    image-20210406152033915

  • index.jspimage-20210406150226767

image-20210406150243417

点击任意一个按钮会出现404错误, 即找不到资源

image-20210406150337921

观察地址栏发现, 路径不正确程序上下文路径/jsp/firstServlet?flag=2

这是因为我们在写路径使使用了相对路径firstServlet?flag=2

而在点击超链接 / 重定向 / 请求转发 时跳转的路径是相对于当前页面所在文件夹(jsp)的路径,

如果写路径时使用了绝对路径/, 如/firstServlet?flag=2, 则跳转的路径会拼接在服务器IP地址之后,这也是不对的

image-20210406151244033

正确应该为程序上下文路径/firstServlet?flag=2

我们可以使用request.getContextPath()来获取程序的上下文路径

image-20210406151834031

成功跳转

image-20210406151908178

Servlet文件上传

  • Demo:

image-20210428084735588

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包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值