Java入门系列12 -- Servlet

  • Servlet是Server与Applet的缩写,意思是服务端小程序,使用Java语言编写的服务端程序,Servlet主要运行在服务端,并由服务器调用执行,是一种按照Servlet标准来开发的类,是SUN公司提供的一门用于开发动态Web资源的技术,言外之意就是要实现web开发,需要实现Servlet标准;
  • Servlet本质上也是Java类,但要遵循Servlet规范进行编写,没有main方法,它的创建,使用,销毁都由Servlet容器进行管理,言外之意就是写自己的类,不用写main方法,别人自动调用;
  • Servlet与HTTP协议是紧密联系的,其可以处理HTTP协议相关的所有内容,这也是Servlet广泛应用的原因;
  • 提供了Servlet功能的服务器,叫做Servlet容器,其常见的容器有很多,例如Tomcat,Jetty,WebLogic Server,WebSphere,JBoss等等;
Servlet的实现
  • 第一步:创建Web项目,打开IDEA,File->New->Project,如下所示:
Snip20211208_29.png
  • 选择Web Application(4.0),点击下一步;
Snip20211208_30.png
  • 填写项目名称以及工作空间,点击Finish,进入工程主界面,如下所示:
image.png
  • 第二步:实现Servlet规范,其目的是让自定义的类能够接受网络请求,接受到网络请求之后对请求进行分析,以及业务逻辑处理,具体步骤如下:

    • 创建一个Java类 命名为Servlet01,继承自HttpServlet
    • 重写service方法,进行业务逻辑处理;
    • 设置注解,即在完成业务代码编写之后,还需要向服务器说明,特定请求对应特定资源;
  • 开发servlet项目,在Servlet3.0中,可以使用@WebServlet注解将一个继承于javax.servlet.http.HttpServlet的类标注为可以处理用户请求的Servlet;

  • 案例代码如下:

package com.sf.servlet;

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;

/**
 * 实现Servlet
 * 1.创建普通Java类;
 * 2.实现Servlet规范,继承自HttpServlet;
 * 3.重写service方法,用来处理请求;
 * 4.设置注解;
 */
@WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //这行代码要注释,否则浏览器报405错误
        //super.service(req, resp);
        System.out.println("Hello Servlet!!!");
        resp.getWriter().write("Hello Servlet!!!");
    }
}
image.png
  • 运行之后,会自动打开浏览器,在URL后面添加上面定义的注解ser01,浏览器中显示的内容,以及IDEA控制台也会有打印,如下所示:
image.png
  • 上述的注解有多种不同的写法,以下写法都是等价的,如下:
//@WebServlet("/ser01")
//@WebServlet(name = "Servlet01",value = "ser01")
//@WebServlet(name = "Servlet01",value = {"ser01","ser001"})
@WebServlet(name = "Servlet01",urlPatterns = {"ser01","ser001"})
Servlet--服务器的设置
  • 刚在浏览器中输入的URL为:http://localhost:8080/Servlet01_war_exploded/ser01,其中localhost:8080是本机地址与8080端口,Servlet01_war_exploded是项目的对外访问地址,ser01是注解,其中项目的对外访问地址是可以设置的,如下所示:
    Snip20211208_51.png
Snip20211208_52.png
  • 再次运行项目,手动打开浏览器,然后输入http://localhost:8080/Servlet01/ser01即可;
Servlet的工作流程
  • 当我们在浏览器中输入http://localhost:8080/Servlet01/ser01时,其本质是浏览器客户端向Tomcat服务器发送了一次Servlet请求,其工作原理如下:
    • 首先根据localhost定位到当前计算机;
    • 然后根据8080端口,定位到Tomcat服务器程序,因为在安装Tomcat时指定其默认端口为8080;
    • 接着根据Servlet01定位到指定项目;
    • 其次根据注解ser01,定位到该项目中的指定资源,然后执行如下:
    • 1)Web服务器(Tomcat)会先检查是否已经装载并创建了该Servlet的实例对象,如果是,则直接执行第4步,否则执行第2步;
    • 2)装载并创建了该Servlet的一个实例对象;
    • 3)调用Servlet实例对象的init()方法,进行初始化;
    • 4)创建一个用于封装HTTP请求消息的HTTPServletRequest对象和一个代表HTTP响应消息的HTTPServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去;
    • 5)WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法;
Servlet的其他实现方式
  • 上面实现了一种创建Java类继承自HttpServlet的实现方式,还有其他的实现方式:
    • 创建Java类继承继承自GenericServlet
    • 创建Java类实现Servlet接口;
  • 案例代码如下:
package com.sf.servlet;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet("/ser02")
public class Servlet02 extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("继承自GenericServlet的Servlet");
    }
}
package com.sf.servlet;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet("/ser03")
public class Servlet03 implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("实现Servlet接口的Servlet");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}
Servlet的生命周期
  • 首先Servlet是没有main方法的,不能独立运行,它的运行完全由Servlet引擎来控制和调度,其生命周期如下所示:
  • 实例化和初始化:当前请求达到Web容器(Tomcat服务器)时,容器会查找该servlet实例对象是否存在,如果不存在,则会创建servlet实例对象并初始化,调用init()方法
  • 就绪/调用/服务阶段:容器调用servlet实例对象的service方法,处理请求的方法在整个生命周期中可以被多次调用;
  • 销毁:当容器关闭时(应用程序停止时),会将程序中的servlet实例对象进行销毁,调用destoty()方法
  • 案例代码:
package com.sf.servlet;

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("/ser05")
public class Servlet05 extends HttpServlet {

    @Override
    public void destroy() {
        System.out.println("Servlet被销毁");
    }

    @Override
    public void init() throws ServletException {
        System.out.println("Servlet被初始化");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet正在处理请求");
    }
}
  • 控制台调试结果如下:
Snip20211209_32.png
  • 总结:Servlet的生命周期分为四步,Servlet类的加载->实例化->服务->销毁;
  • Tomcat与Servlet的工作时序图如下所示:
Snip20211209_34.png
  • WebClient向Servlet容器(Tomcat)发出HTTP请求;
  • Servlet容器接收WebClient的请求;
  • Servlet容器创建一个HttpServletRequest对象,将WebClient请求信息封装到这个对象中;
  • Servlet容器创建一个HttpServletResponse对象,
  • Servlet容器调用HttpServlet对象的service方法,将Request与Response作为参数,传递给HttpServlet;
  • HttpServlet调用HttpServletRequest对象的有关方法,获取Http请求信息;
  • HttpServlet调用HttpServletResponse对象的有关方法,生成响应数据;
  • Servlet容器把HttpServlet的响应结果返回给WebClient;
HttpServletRequest对象
  • HttpServletRequest对象:主要是用来接收客户端发送过来的请求信息,包括请求的参数,请求头等等,service()方法中形参接收的是HttpServletRequest接口的实例化对象,表示该对象主要应用在HTTP协议上,该对象是由Tomcat封装好,传递过来的;
  • 在HttpServletRequest接口中,定义的方法很多,但基本都是围绕接收客户端参数的,常用方法有如下:
    • getRequestURL:获取请求的完整URL,从http开始,到?结束;
    • getRequestURI:获取请求的部分路径,从项目站点名开始,到?结束;
    • getQueryString:获取请求的参数字符串;
    • getMethod:获取请求方式;
    • getProtocol:获取请求的协议版本;
    • getContextPath:获取项目的站点名(项目的对外访问路径);
    • getParameter:获取指定名称的参数;
    • getParameterValues:获取指定名称的参数的所有参数值;
  • 案例代码如下:
package com.sf.servlet;

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;

/**
 * 实现Servlet
 * 1.创建Java类
 * 2.实现Servlet规范,继承自HttpServlet
 * 3.重写service方法,用来处理请求
 * 4.设置注解,指定访问的路径
 */
@WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello Servlet!!!");
        resp.getWriter().write("Hello Servlet");

        System.out.println("请求的完整URL:" + req.getRequestURL().toString());
        System.out.println("请求的部分路径:" + req.getRequestURI());
        System.out.println("请求的参数字符串" + req.getQueryString());
        System.out.println("请求方式" + req.getMethod());
        System.out.println("请求的协议版本" + req.getProtocol());
        System.out.println("项目的站点名" + req.getContextPath());
  
        //获取指定名称的参数
        String name = req.getParameter("name");
        String password = req.getParameter("password");
        System.out.println("name:" + name + " password:" + password);

        //获取指定名称的参数的所有参数值
        String[] s = req.getParameterValues("s");
        if (s != null && s.length > 0) {
            for (String str : s) {
                System.out.println("s:" + str);
            }
        }
    }
}
  • 浏览器输入:http://localhost:8080/servlet01/ser01?name=admin&psd=asd123&s=sing&s=dance&s=speak,控制台打印结果如下:
image.png
请求乱码问题
  • Tomcat8.0及以上版本,GET请求在解析参数时不会出现乱码,但POST请求在解析参数时会出现乱码;

  • Tomcat8.0以下版本,GET请求在解析参数时会出现乱码,POST请求在解析参数时也会出现乱码;

  • Request请求属于接收客户端的参数,其有默认的语言编码为ISO-8859-1,此编码不支持中文,所以解析时肯定会出现乱码,解决方案有如下:

    • 在Request中设置编码方式,告诉服务器以指定的编码方式来解析数据,即req.setCharacterEncoding("UTF-8"),注意此设置只针对POST请求才有效;
    • 解决Tomcat8以下 get请求 参数解析乱码的方案:String sf_name = new String(req.getParameter("name").getBytes("ISO-8859-1"),"UTF-8")
  • 下面模拟一下POST请求,解析参数出现乱码的情况:创建JSP文件login.jsp,内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
   <form method="post" action="ser01">
       姓名: <input type="text" name="name"> <br>
       密码: <input type="password" name="password"> <br>
       <button>登录</button>
   </form>
</body>
</html>
package com.sf.servlet;

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;

/**
 * 实现Servlet
 * 1.创建Java类
 * 2.实现Servlet规范,继承自HttpServlet
 * 3.重写service方法,用来处理请求
 * 4.设置注解,指定访问的路径
 */
@WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello Servlet!!!");
        //获取指定名称的参数
        String name = req.getParameter("name");
        String password = req.getParameter("password");
        System.out.println("name:" + name + " password:" + password);
    }
}
  • 在浏览器中输入http://localhost:8080/servlet01/login.jsp,出现登录界面,账号密码均输入张三,然后IDEA控制台解析POST请求参数时出现乱码,如下所示:
image.png
  • 现在将Servlet01中代码作如下修改:
package com.sf.servlet;

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("/ser01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello Servlet!!!");
        //request设置编码方式
        req.setCharacterEncoding("UTF-8");
        //获取指定名称的参数
        String name = req.getParameter("name");
        String password = req.getParameter("password");
        System.out.println("name:" + name + " password:" + password);
    }
}
  • 然后再次在浏览器中输入,访问登录页,输入账号密码,Servlet容器在参数解析不会出现乱码了,如下所示:
image.png
  • 解决Tomcat8以下 get请求 参数解析乱码(很少用了),代码如下:
package com.sf.servlet;

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("/ser01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello Servlet!!!");
        //request设置编码方式
        req.setCharacterEncoding("UTF-8");
        //获取指定名称的参数
        String name = req.getParameter("name");
        String password = req.getParameter("password");
        System.out.println("name:" + name + " password:" + password);

        //解决Tomcat8以下 get请求 参数解析乱码
        String sf_name = new String(req.getParameter("name").getBytes("ISO-8859-1"),"UTF-8");
        System.out.println("name:" + sf_name);
    }
}
请求转发
  • 请求转发:是一种服务器的行为,当客户端请求达到后,服务器进行转发,此时会将请求对象进行保存,地址栏中的URL地址不会改变,得到响应之后,服务器端再将响应发送给客户端,从始至终只有一个请求发出,实现了请求数据的共享,可实现页面的跳转
  • 案例代码:
  • Servlet04文件
package com.sf.servlet;

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("/ser04")
public class Servlet04 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //接收参数
        String name = req.getParameter("name");
        System.out.println("Servlet04 name:" + name);
    }
}
  • login.jsp文件
<%--
  Created by IntelliJ IDEA.
  User: liyanyan33
  Date: 2021/12/8
  Time: 下午4:46
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  $END$
  </body>
</html>
  • login.html文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1> 登录 </h1>
</body>
</html>
package com.sf.servlet;

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("/ser03")
public class Servlet03 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //接收参数
        String name = req.getParameter("name");
        System.out.println("Servlet03 name:" + name);

        //请求转发到Servlet04
        req.getRequestDispatcher("ser04").forward(req,resp);

        //请求转发给jsp文件
        req.getRequestDispatcher("login.jsp").forward(req,resp);

        //请求转发给html文件
        req.getRequestDispatcher("login.html").forward(req,resp);
    }
}
Request作用域
  • Request作用域可以看成是一个对象,通过该对象可以在一个请求中传递数据,作用范围是:在一次请求中有效,即服务器跳转有效;
  • 案例代码如下:
package com.sf.servlet;

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;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/ser05")
public class Servlet05 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet05");
        //设置域对象的内容
        req.setAttribute("name","admin");
        req.setAttribute("age",20);
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        req.setAttribute("list",list);

        //设置请求转发Servlet06
        req.getRequestDispatcher("ser06").forward(req,resp);

        //设置请求转发jsp
    }
}
package com.sf.servlet;

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;
import java.util.List;

@WebServlet("/ser06")
public class Servlet06 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet06");
        //获取域对象
        String name = (String) req.getAttribute("name");
        System.out.println("name: " + name);

        Integer age = (Integer) req.getAttribute("age");
        System.out.println("age: " + age);

        List<String> list = (List<String>) req.getAttribute("list");
        if (list != null && list.size() > 0) {
            System.out.println(list.get(0));
        }
    }
}
  • 运行项目,在浏览器中输入http://localhost:8080/servlet01/ser05,结果如下:
image.png
HttpServletResponse对象
  • HttpServletResponse对象:是服务器端对客户端的请求进行响应,将Web服务器处理后的结果返回给客户端;
  • Servlet容器接收到客户端请求之后,可以通过HttpServletResponse对象直接进行响应,响应时需要获取输出流,输出流有两种形式:
    • 字符输出流:getWriter()
    • 字节输出流:getOutputStream()
  • 两种输出流不能同时使用,否则会报错;
  • 案例代码:
package com.sf.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/ser07")
public class Servlet07 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取字符输出流
        PrintWriter writer = resp.getWriter();
        writer.write("Hello");

        //获取字节输出流
        ServletOutputStream outputStream = resp.getOutputStream();
        outputStream.write("World".getBytes());
        
        //不能同时使用 会报错
    }
}
响应乱码问题
  • 在响应中,若响应的内容中含有中文,有可能出现乱码问题,这时因为服务器响应的数据会经过网络传输,服务器端有一种编码方式,在客户端也有一种编码方式,当两端的编码方式不同时,就会出现乱码;
  • 针对字符输出流getWriter(),响应中文必定会出现乱码,因为服务端在进行编码时默认使用ISO-8859-1格式的编码,该编码只支持中文;
  • 解决方案:
    • 分别设置服务端响应与客户端响应的编码格式;
    • 同时设置服务端响应与客户端响应的编码格式;
  • 案例代码:
package com.sf.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/ser07")
public class Servlet07 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.分别设置服务端与客户端的编码格式
        //设置服务端的编码格式
        resp.setCharacterEncoding("UTF-8");
        //设置客户端的编码格式
        resp.setHeader("content-type","text/html;charset=UTF-8");

        //2.同时设置服务端与客户端的编码格式
        resp.setContentType("text/html;charset=UTF-8");

        //获取字符输出流
        PrintWriter writer = resp.getWriter();
        writer.write("<h2>你好</h2>");
    }
}
  • 针对字节输出流getOutputStream(),解决方案与上面的完全相同,代码如下:
package com.sf.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/ser07")
public class Servlet07 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.分别设置服务端与客户端的编码格式
        //设置服务端的编码格式
        resp.setCharacterEncoding("UTF-8");
        //设置客户端的编码格式
        resp.setHeader("content-type","text/html;charset=UTF-8");

       //2.同时设置服务端与客户端的编码格式        
       resp.setContentType("text/html;charset=UTF-8");

        //获取字节输出流
        ServletOutputStream outputStream = resp.getOutputStream();
        outputStream.write("<h2>你好</h2>".getBytes());
    }
}
HttpServletResponse对象 -- 重定向
  • 重定向:是一种服务器指导,客户端的行为;
  • 当客户端发出第一个请求,被服务端接收处理之后,服务端会进行响应,在响应的同时,服务端会给客户端一个新的地址(下次请求的地址),当客户端接收到响应后,会立刻根据服务端给的新地址发起第二个请求,服务端接收请求并作出响应,重定向完成;
  • 在重定向中会存在两个请求,属于客户端行为;
  • 客户端浏览器的地址栏会发生改变;
  • Request对象不共享;
  • 案例代码如下:
package com.sf.servlet;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
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("/ser08")
public class Servlet08 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet08");
        //获取请求参数
        String name = req.getParameter("name");
        System.out.println("name: " + name);
        //请求重定向到Servlet09
        resp.sendRedirect("ser09");
    }
}
package com.sf.servlet;

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("/ser09")
public class Servlet09 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet09");
        //获取请求参数
        String name = req.getParameter("name");
        System.out.println("name: " + name);
    }
}
  • 在浏览器中输入http://localhost:8080/servlet01/ser08?name=yanzi,按下回车键,地址栏会变成http://localhost:8080/servlet01/ser09,且控制台打印如下:
image.png
请求转发与重定向的区别
  • 不同点:
    • 请求转发是一次请求,数据在Request域中共享,重定向是两次请求,数据在Request域中不共享;
    • 请求转发是服务端行为,重定向是客户端行为;
    • 请求转发时地址栏不发生变化,重定向时地址栏会发生变化;
    • 请求转发时的地址只能是当前站点(当前项目)下的资源,重定向时的地址可以是任意的地址,能实现跨域;
  • 相同点:
    • 两者都能实现跳转;
Cookie对象
  • Cookie是浏览器提供的一种技术,通过服务器的程序能将一些只须保存在客户端,或者在客户端进行处理的数据,放在本地计算机上,不需要通过网络传输,因而提高网页处理的效率,并且能够减少服务器的负载,但是由于Cookie是服务端保存在客户端的信息,所以其安全性也是很差的,例如常见的记住密码则可以通过Cookie来实现;

  • 有一个专门操作Cookie的类javax.servlet.http.Cookie,跟随服务端的响应发送给客户端,保存在浏览器,当下次再访问服务器是把Cookie再带回服务器;

  • Cookie的格式:键值对,多个键值对用分号隔开;

  • Cookie的创建与发送:通过new Coookie()方法来创建Cookie对象,然后添加到response对象中,接着随着响应发送给客户端;

  • 案例代码如下:

package com.sf.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/ser02")
public class Servlet02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello Servlet!!!");
        //创建Cookie
        Cookie cookie = new Cookie("name","admin");
        //添加到response 发送给客户端
        resp.addCookie(cookie);
    }
}
  • 运行项目,在Google浏览器中输入http://localhost:8080/Servlet01/ser02,然后浏览器中,右键->检查,如下所示:
image.png
  • Cookie的获取:在服务端只提供了一个getCookies()的方法来获取客户端回传的所有cookie组成的一个数组,如果需要获取单个cookie则需要通过遍历,getName()获取Cookie的名称,getValue()获取Cookie的值;
  • 案例代码如下:
package com.sf.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/ser02")
public class Servlet02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Hello Servlet!!!");
        //获取cookie数组
        Cookie[] cookies = req.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie: cookies) {
                String name = cookie.getName();
                String value = cookie.getValue();
                System.out.printf("cookie名称:" + name + ",值:" + value);
            }
        }
    }
}
  • Cookie设置到期时间:Cookie存在有效时间的,默认为当前浏览器关闭就会失效,我们可以手动设置cookie的有效时间,通过setMaxAge(int time)方法来设置cookie的最大有效时间,以秒为单位;
  • 到期时间的取值情况如下 :
    • 负整数:表示不存储该cookie,其maxAge属性的默认值为就是-1,表示只在浏览器内存中存活,一旦关闭浏览器窗口,那么cookie就会消失;
    • 正整数:表示存储的秒数,浏览器会把cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie也会存活相应的时间;
    • 等于0,表示删除该cookie,浏览器内存中和客户端硬盘上都会删除该cookie对象;
  • 案例代码如下:
package com.sf.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/cookie03")
public class Cookie03 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie cookie1 = new Cookie("name1","zhangsan");
        cookie1.setMaxAge(-1);
        resp.addCookie(cookie1);

        Cookie cookie2 = new Cookie("name2","lisi");
        cookie2.setMaxAge(60);
        resp.addCookie(cookie2);

        Cookie cookie3 = new Cookie("name3","wangwu");
        cookie3.setMaxAge(0);
        resp.addCookie(cookie3);
    }
}
  • Cookie的注意点
    • cookie保存在当前浏览器中,不能跨浏览器保存;
    • cookie存中文有问题,正常情况下cookie是不能出现中文的,如果有中文则通过URLEncoder.encode()来进行编码,获取时通过URLDecoder.decode()来进行解码;
    • 同名cookie问题,如果服务端发送重复的cookie那么会覆盖原来的cookie;
    • 浏览器存放cookie的数量是有上限的,cookie时存储在客户端的,一般由服务端创建和设定,后期结合Session来实现回话跟踪;
    • cookie存储的数据是有大小限制的,一般上限为4KB左右;
  • 案例代码:
package com.sf.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;

@WebServlet("/cookie04")
public class Cookie04 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = "姓名";
        String vaule = "李四";

        //编码
        name = URLEncoder.encode(name);
        vaule = URLEncoder.encode(vaule);

        Cookie cookie = new Cookie(name,vaule);
        resp.addCookie(cookie);

        Cookie[] cookies = req.getCookies();
        if (cookies != null && cookies.length > 0) {
            for (Cookie cook: cookies) {
                //解码
                System.out.println(URLDecoder.decode(cook.getName()));
                System.out.println(URLDecoder.decode(cook.getValue()));
            }
        }
    }
}
  • Cookie的路径:通过setPath()方法设置cookie的路径,这个路径直接决定了服务器的请求是否会从浏览器中加载某些cookie
  • 情景一:当前服务器下任何项目的任意资源都可以获取cookie对象;
  • 情景二:当前项目下的资源可获取cookie对象,默认不设置cookie的路径;
  • 情景三:指定项目下的资源可获取cookie对象;
  • 情景四:指定目录下的资源可获取cookie对象;
  • 总结:只有访问的路径中包含Cookie对象的Path,才可以获取到Cookie对象;
  • 案例代码:
package com.sf.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;

@WebServlet("/cookie05")
public class Cookie05 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //当前服务器下任何项目的任意资源都可以获取cookie对象
        Cookie cookie1 = new Cookie("cookie01","cooki01");
        cookie1.setPath("/");
        resp.addCookie(cookie1);

        //当前项目下的资源可获取cookie对象,默认不设置cookie的路径
        //http://localhost:8080/servlet01/
        Cookie cookie2 = new Cookie("cookie02","cooki02");
        cookie2.setPath("/servlet01");
        resp.addCookie(cookie2);

        //指定项目下的资源可获取cookie对象
        Cookie cookie3 = new Cookie("cookie03","cooki03");
        cookie3.setPath("/servlet02");
        resp.addCookie(cookie3);

        //当前项目 指定目录下的资源可获取cookie对象
        Cookie cookie4 = new Cookie("cookie04","cooki04");
        cookie4.setPath("/servlet01/ser01");
        resp.addCookie(cookie4);
    }
}
HttpSession对象
  • HttpSession对象:是javax.servlet.http.HttpSession的实例,属于HTTP协议的范畴;
  • 对于服务器而言,每一个脸接到它的客户端都是一个session,servlet容器使用此接口创建HTTP客户端和HTTP服务器之间的会话,会话将保留指定的时间段,跨多个连接或来自用户的页面请求,一个会话通常对应一个用户,该用户可能多次访问一个站点,可以通过此接口查看和操作某个会话的信息,比如会话标识符,创建时间和最后一次访问时间,在整个Session中,最重要的就是属性的操作;
  • session无论客户端还是服务端都可以感知到,若重新打开一个新的浏览器,则无法获取之前的session,因为每一个session只保存在当前浏览器中,并在相关的页面取得;
  • session的作用是为了标识一次会话,或者说是确认一个用户,在一次会话(一个用户的多次请求)期间共享数据,我们可通过request.getSession()方法,来获取当前会话的session对象;
  • 案例代码如下:
package com.sf.servlet;

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 javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/session01")
public class Session01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取会话对象
        HttpSession session = req.getSession();
        //获取会话id
        System.out.println(session.getId());
        //获取会话创建的时间
        System.out.println(session.getCreationTime());
        //获取会话最后一次访问时间
        System.out.println(session.getLastAccessedTime());
        //判断是否是新的会话
        System.out.println(session.isNew());
    }
}
  • 获取session对象时,若session对象存在时,则直接获取,若session对象不存在时,则创建session对象;
标识符JSEESIONID
  • session是为了标识一次会话,其标识就是会话id即sessionId;
  • 每当一次请求到达服务器时:
  • 如果开启了会话,服务器第一步会查看是否从客户端回传一个名为JSEESIONID的cookie,
    • 如果没有则认为这是一次新的会话,会创建一个新的session对象,并用唯一的sessionid为此会话作一个标志;
    • 如果有JSEESIONID这个cookie回传,服务器则会根据JSEESIONID这个值去查看是否含有id为JSEESIONID值的session对象,如果没有则认为是一个新的会话,则重新创建一个新的session对象,并用唯一的sessionid为此会话作一个标志,如果找到了对应的session对象,则认为是之前标志过的一次会话,返回该session对象,数据实现共享;
  • JSEESIONID是一个cookie,这是一个比较特殊的cookie,当请求到达服务器时,如果访问了session,则服务器会创建一个名为JSEESIONID,值为获取到的session(无论是获取到的还是新创建的)的sessionid的cookie对象,并添加到response对象中,响应给客户端,有效时间为关闭浏览器;
  • Session的底层是依赖Cookie来实现的;
image.png
session域对象
  • Session表示一次会话,在一次会话过程中数据是可以共享的,这时session作为域对象存在,可以通过setAttribute(name,value)方法往域对象中添加数据,通过getAttribute(name)从域对象中获取数据,通过removeAttribute(name)从域对象中移除数据;
  • 数据存储在session域对象中,当session对象不存在了,或者两个不同的session,数据也就不能共享了;
  • 请求转发时的案例如下:
<%--
  Created by IntelliJ IDEA.
  User: liyanyan33
  Date: 2021/12/8
  Time: 下午4:46
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
   <%
     //获取session域对象
     String s_name = (String) request.getSession().getAttribute("s_name");
     String s_password = (String) request.getSession().getAttribute("s_password");
     out.print("s_name: " + s_name + ",s_password: " + s_password);

     //获取request域对象
     String r_name = (String) request.getAttribute("r_name");
     out.print(",r_name: " + r_name);
   %>
  </body>
</html>
package com.sf.servlet;

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 javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/session02")
public class Session02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取会话对象
        HttpSession session = req.getSession();
        //设置session域对象
        session.setAttribute("s_name","lisi");
        session.setAttribute("s_password","asd123");
        //移除session域对象
        session.removeAttribute("s_password");

        //设置request域对象
        req.setAttribute("r_name","zhangsan");

        //请求转发
        req.getRequestDispatcher("index.jsp").forward(req,resp);
    }
}
  • 浏览器输入http://localhost:8080/servlet01/session02,调试结果如下:
    image.png
  • 请求重定向时,代码修改如下:
package com.sf.servlet;

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 javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/session02")
public class Session02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取会话对象
        HttpSession session = req.getSession();
        //设置session域对象
        session.setAttribute("s_name","lisi");
        session.setAttribute("s_password","asd123");
        //移除session域对象
        session.removeAttribute("s_password");

        //设置request域对象
        req.setAttribute("r_name","zhangsan");

        //请求重定向
        resp.sendRedirect("index.jsp");
    }
}
  • 浏览器输入http://localhost:8080/servlet01/session02,调试结果如下:
image.png
  • 总结:
  • 请求转发时,request作用域有效,session作用域有效;
  • 请求重定向时,request作用域失效,session作用域有效;
Session对象的销毁
  • 默认到期时间:当客户端第一次请求servlet且操作session时,session对象生成,Tomcat中session默认存活时间为30分钟,一旦操作,session就会重新计时;
  • 默认到期时间可以在Tomcat/conf/web.xml文件中进行修改的,如下所示:
<session-config>
   <session-timeout>30</session-timeout>
</session-config>
  • 手动设置到期时间:通过设置session的最大不活动时间,调用setMaxInactiveInterval()方法,单位为秒;
  • 立即失效:调用invalidate()方法,让session立即失效;
  • 关闭浏览器就会失效:session底层依赖cookie,cookie在浏览器关闭时就会失效;
  • 关闭服务器就会失效
ServletContext对象
  • 每一个Web应用都有且仅有一个ServletContext对象,又称为Application对象,其与应用程序相关,在Web容器启动时,会为每一个Web应用程序创建一个对应的ServletContext对象;
  • ServletContext对象有两大作用:
    • 作用域对象实现共享数据,此数据在整个应用程序中实现共享;
    • 该对象中保存了当前应用程序的相关信息,可通过相关Api获取;
  • 案例代码如下:
package com.sf.servlet;

import javax.servlet.ServletContext;
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("/scontext01")
public class ServletContext01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取ServletContext对象
        //1.通过request对象获取
        ServletContext context1 = req.getServletContext();

        //2.通过session对象获取
        ServletContext context2 = req.getSession().getServletContext();

        //3.通过ServletConfig获取
        ServletContext context3 = getServletConfig().getServletContext();

        //4.直接获取
        ServletContext context4 = getServletContext();

        //常用方法
        //获取当前服务器的版本信息
        String serverInfo = context1.getServerInfo();
        System.out.println("当前服务器的版本信息: " + serverInfo);
        //获取项目的真实路径
        String realPath = context1.getRealPath("/");
        System.out.println("项目的真实路径:" + realPath);
    }
}
  • 控制台打印结果如下:
image.png
ServletContext域对象
  • ServletContext可以作为域对象,能存数据,使得整个应用程序共享数据,但不建议存放过多的数据,因为ServletContext中的数据一旦存储进去没有手动移除将会一直存在;
  • Servlet中有三大域对象:
    • request域对象,请求转发时有效,重定向时失效;
    • session域对象,请求转发和重定向时均有效,session销毁时失效;
    • ServletContext域对象,在整个应用程序中有效,服务器关闭时失效;
//1.通过request对象获取
ServletContext context1 = req.getServletContext();
context1.setAttribute("name","yanzi");
context1.setAttribute("password","asd123");
context1.removeAttribute("password");
String name = (String) context1.getAttribute("name");
文件的上传
  • 文件上传涉及到前台页面的编写和后台服务端代码的编写,前台发送文件,后台接收并保存文件,实现文件的完整上传;
  • 首先文件上传的前台页面编写如下:
  • 创建一个upload.html文件,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
   <form method="post" enctype="multipart/form-data" action="uploadServlet">
       姓名: <input type="text" name="name"> <br>
       文件: <input type="file" name="myfile"> <br>
       <button>提交</button>
   </form>
</body>
</html>
  • 首先是定义表单;

  • 设置表单的提交类型为post;

  • 设置表单类型为文件上传表单 enctype="multipart/form-data";

  • 设置文件提交的地址;

  • 准备表单元素:

    • 普通表单项 type="text";
    • 文件表单项 type="file"
  • 设置表单项的name属性值,否则后台无法接收数据;

  • 然后文件上传的后台代码编写:

package com.sf.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;

@WebServlet("/uploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("文件上传");
        //设置请求的编码
        req.setCharacterEncoding("UTF-8");
        //获取普通表单项
        String name = req.getParameter("name");
        System.out.println("name: " + name);
        //获取上传文件
        Part part = req.getPart("myfile");
        //通过part对象 获取上传的文件名
        String fileName = part.getSubmittedFileName();
        System.out.println("上传文件名: " + fileName);
        //文件存放路径
        String filePath = req.getServletContext().getRealPath("/");
        System.out.println("文件存放路径: " + filePath);
        //上传文件到执行目录
        part.write(filePath + "/" + fileName);
    }
}
  • 运行项目,在浏览器中输入http://localhost:8080/Servlet07/upload.html,然后选择文件text.txt,点击提交,进入后台,IDEA控制台打印如下:
    image.png
  • 查看文件存放路径,确实上传成功;
文件下载
  • 将服务器上的资源文件下载到本地,可通过超链接进行下载,或者通过后台代码下载
  • 超链接下载:使用a标签;
    • 当超链接遇到浏览器能识别的资源时(例如txt,png,jpg文件能识别),默认不会下载,可通过download属性进行下载;
    • 当超链接遇到浏览器不能识别的资源时,会自动下载;
  • 首先准备资源文件并设置路径,如下所示:
image.png
  • 然后创建download.html文件,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
</head>
<body>
   <a href="download/test.txt">文本文件</a>
   <a href="download/tk.png">图片文件</a>
   <a href="download/xxx.zip">压缩文件</a>
   <hr>
   <a href="download/test.txt" download=>文本文件</a>
   <a href="download/tk.png" download="推客.png">图片文件</a>
</body>
</html>
  • 浏览器输入http://localhost:8080/Servlet07/download.html,看到如下:
image.png
  • 后台代码下载的实现,创建DownloadServlet文件;
    • 需要设置响应类型,浏览器无法使用某种方式或激活某个程序来处理的MIME类型,即resp.setContentType("application/x-msdownload")
    • 需要设置响应头,即resp.setHeader("Content-Disposition","attachment;filename=" + fileName)
    • 读取下载的文件,调用resp.getOutputStream()向客户端写入内容;
  • 案例代码如下:
package com.sf.servlet;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

@WebServlet("/downloadServlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("文件下载");
        //设置请求编码格式
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        //获取文件名
        String fileName = req.getParameter("fileName");
        if (fileName == null || "".equals(fileName.trim())) {
            resp.getWriter().write("请输入要下载的文件名");
            resp.getWriter().close();
            return;
        }
        System.out.println("文件名: " + fileName);
        //获取图片的存放路径
        String path = req.getServletContext().getRealPath("/download/");
        System.out.println("目标文件路径: " + path);
        //通过路径获取file对象
        File file = new File(path + fileName);
        if (file.exists() && file.isFile()) {
            resp.setContentType("application/x-msdownload");
            resp.setHeader("Content-Disposition","attachment;filename=" + fileName);
            //输入流
            InputStream in = new FileInputStream(file);
            //字节输出流
            ServletOutputStream out = resp.getOutputStream();
            //byte数组
            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = in.read(bytes)) != -1) {
                //输出
                out.write(bytes,0,length);
            }
            //关闭资源
            out.close();
            in.close();
        } else {
            resp.getWriter().write("文件不存在,请重试");
            resp.getWriter().close();
        }
    }
}
  • download.html文件代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件下载</title>
</head>
<body>
   <a href="download/test.txt">文本文件</a>
   <a href="download/tk.png">图片文件</a>
   <a href="download/xxx.zip">压缩文件</a>
   <hr>
   <a href="download/test.txt" download=>文本文件</a>
   <a href="download/tk.png" download="推客.png">图片文件</a>

   <form action="downloadServlet">
       文件名: <input type="text" name="fileName" placeholder="请输入要下载的文件名">
       <button>下载</button>
   </form>

</body>
</html>
  • 运行项目,浏览器中输入http://localhost:8080/Servlet07/download.html,然后输入文件名test.txt,进行文件下载;
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容