六、Servlet


您的导航

  • 六、Servlet
  • 6.1 Servlet概述
  • 6.2 Servlet准备
  • 6.3 Hello,Servlet!
  • 6.3.2 编写HelloServlet代码
  • 6.3.3 Servlet部署
  • 6.3.4 添加tomcat配置
  • 6.4 HelloServlet原理
  • 6.5 ServletContext
  • 6.5.1概述
  • 6.5.2 共享数据
  • 6.5.3 转发请求
  • 6.5.4 转发和重定向
  • 6.5.5 获取资源
  • 6.6 HttpServletResponse对象
  • 6.6.1 一些玩法
  • 6.6.2 重定向
  • 6.7 HttpServletRequest对象
  • 6.7.1 请求转发


6.1 Servlet概述

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

Servlet是一个接口,任何实现了servlet接口的类也是servlet,

servlet运行在支持Java应用的服务器中,原理上可以响应任何类型的请求,但大多数情况下只用来扩展基于HTTP协议的web服务器。

6.2 Servlet准备

要继承实现了servlet的类或是直接实现servlet接口,都得有servlet的信息,这些信息得导入servlet的包。

在pom.xml中添加servlet依赖:

<dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
</dependency>

(导入后记得刷新)

查看servlet接口

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

不难看出,这就是servlet的生命周期:

  • Servlet 初始化后调用 init () 方法。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 销毁前调用 destroy() 方法。
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

init和destroy初学无需关心,最重要的是service处理请求的阶段。

如何处理请求呢?

6.3 Hello,Servlet!

一般情况下,我们用servlet来处理http请求,我们不需要自己实现service()方法,HttpServlet提供了处理各种http请求的方法。

6.3.2 编写HelloServlet代码

下面我们就继承这个类写出我们第一个servlet程序

public class HelloServlet extends HttpServlet {
    // 重写了父类的doGet方法,顾名思义,doGet方法就是用来处理get请求的
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("Hello Servlet");
    }
}

6.3.3 Servlet部署

当我们编写完Servlet程序后,我们得告诉服务器,到哪找我们的servlet,如何找我们的servlet,所以我们得去web.xml文件中注册一下:

<servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>vip.yangsf.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

servlet-name随便取,一个servlet要对应一个servlet-mapping,servlet-class填Servlet程序所在的地方,url-pattern填访问路径。

6.3.4 添加tomcat配置

  1. 右上角添加配置->编辑配置
  2. 添加本地tomcat服务器

java开发 word 换行 javaweb换行servlet_intellij-idea

  1. 添加工件,选择对应的war包

java开发 word 换行 javaweb换行servlet_java开发 word 换行_02

因为我的程序在ServletTest模块下,所以我选择这个war包

java开发 word 换行 javaweb换行servlet_java_03

应用程序上下文就表示访问路径,可以更改。

  1. 在确认这几个参数都没问题后,就可以启动服务

java开发 word 换行 javaweb换行servlet_java开发 word 换行_04

java开发 word 换行 javaweb换行servlet_java开发 word 换行_05

  1. 启动后,在url后面加上servlet的访问地址(例如刚刚的/hello)即可看到HelloServlet

6.4 HelloServlet原理

浏览器向web服务器发送http请求,web服务器调用servlet的service方法处理请求,处理完成后,web服务器给浏览器发送经过处理后的http响应。

java开发 word 换行 javaweb换行servlet_intellij-idea_06

6.5 ServletContext

6.5.1概述

  • ServletContext是一个全局的存储信息的空间,服务器开始就存在,服务器关闭后才释放。

java开发 word 换行 javaweb换行servlet_java_07

可以把ServletContext看作是一个公共的空间,每一个Servlet可以通过ServletContext进行通信,我们可以通过getServletContext()方法来获取ServletContext对象。

6.5.2 共享数据

做点小测试:

// 在context中添加一个属性
public class setContextAttr extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        ServletContext context = this.getServletContext();
        context.setAttribute("username","root");
        context.setAttribute("password","123456");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        doGet(req, resp);
    }
}
// 获取context中的属性
public class getContextAttr extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        ServletContext context = this.getServletContext();
        String name = (String) context.getAttribute("username");
        String pw = (String) context.getAttribute("password");
        // 设置编码
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf8");
        PrintWriter writer = resp.getWriter();
        writer.println("用户名:" + name + "<br>" + "密码:" + pw);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doGet(req, resp);
    }
}

别忘了在web.xml中注册servlet

<servlet>
    <servlet-name>setArr</servlet-name>
    <servlet-class>vip.liuqian.setContextAttr</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>setArr</servlet-name>
    <url-pattern>/setArr</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>getArr</servlet-name>
    <servlet-class>vip.liuqian.getContextAttr</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>getArr</servlet-name>
    <url-pattern>/getArr</url-pattern>
</servlet-mapping>

启动服务,先后访问set、get,效果如下:

java开发 word 换行 javaweb换行servlet_java开发 word 换行_08

6.5.3 转发请求

就是字面意思,把Http请求转发到另一个地方,可用getRequestDispatcher()方法来转发请求。

写代码测试:

// 先写一个页面
public class DispatcherTarget extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 防止乱码
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf8");
        resp.getWriter().println("这是B的页面");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doGet(req, resp);
    }
}

注册:

<servlet>
    <servlet-name>distarget</servlet-name>
    <servlet-class>vip.liuqian.DispatcherTarget</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>distarget</servlet-name>
    <url-pattern>/B</url-pattern>
</servlet-mapping>

效果:

java开发 word 换行 javaweb换行servlet_web_09

再用getRequestDispatcher()方法试试能不能转发

public class RequestDispatcher extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        context.getRequestDispatcher("/B").forward(req, resp);
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf8");
        resp.getWriter().println("这是A的页面");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

注册:

<servlet>
    <servlet-name>dispatch</servlet-name>
    <servlet-class>vip.liuqian.RequestDispatcher</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>dispatch</servlet-name>
    <url-pattern>/A</url-pattern>
</servlet-mapping>

按道理,如果没有转发,当我们访问路径为"/A"时,页面上应该输出"这是A的页面",但实际上是这样的

java开发 word 换行 javaweb换行servlet_javaee_10

并且状态码为200,说明没有跳转

java开发 word 换行 javaweb换行servlet_java_11

6.5.4 转发和重定向

刚刚的案例,如果是重定向而不是转发,那么url最后就应该是B而不是A,说明了转发和重定向还是有区别的。

转发:

java开发 word 换行 javaweb换行servlet_intellij-idea_12

从头至尾,浏览器和B没有直接请求或响应。

重定向:

java开发 word 换行 javaweb换行servlet_java开发 word 换行_13

重定向是,访问A,然后实际上是访问B。

6.5.5 获取资源

我们来试试用Servlet获取properties文件的内容。

先创建一个data.properties文件在resources目录下(必须是),然后data中本来有一些数据(手动输入)。

java开发 word 换行 javaweb换行servlet_java开发 word 换行_14

可以用ServletContext配合getResourceAsStream()方法来获取文件的流。

写代码试试:

public class getResource extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 为什么是这个路径?打开target目录看看就明白了
        InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/data.properties");
        Properties prop = new Properties();
        prop.load(is);
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf8");
        resp.getWriter().println("用户名:" + prop.getProperty("username") + "<br>" + "密码:" + prop.getProperty("password"));
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doGet(req, resp);
    }
}

写完就去注册:

<servlet>
    <servlet-name>getResource</servlet-name>
    <servlet-class>vip.liuqian.getResource</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>getResource</servlet-name>
    <url-pattern>/getResource</url-pattern>
</servlet-mapping>

成功获取资源:

java开发 word 换行 javaweb换行servlet_java_15

resources目录的文件在maven编译后,会在target目录的WEB-INF路径下的classes下找到这个文件。

java开发 word 换行 javaweb换行servlet_intellij-idea_16

6.6 HttpServletResponse对象

还记得Servlet怎么处理请求的吗?

java开发 word 换行 javaweb换行servlet_java_17

Web Server接到Http请求后,把这个请求转换成了一个HttpServletRequest对象,Servlet就可以处理这个请求对象,然后返回一个HttpServletResponse对象,我们现在要了解的,就是这个HttpServletResponse对象。

我们知道Http请求和响应都是一些参数,现在我们拿到了响应对象,就可以设置一下这些参数。

做点小测试:

6.6.1 一些玩法

  1. 先来利用response下载个文件吧~

首先,我们要有一个文件。我这里的文件是1.png,放到resources目录下(约定)。

java开发 word 换行 javaweb换行servlet_intellij-idea_18

然后就可以开始敲代码了

// Response文件下载
public class Test01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 通过文件获取根路径,为什么这里填这个路径?和6.5.5是一样的道理
        String realPath = this.getServletContext().getRealPath("/WEB-INF/classes/1.png");
        // 通过根路径获取文件名,最后一个'\'之后的就是文件名
        String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
        // 设置响应头,就是响应的一些参数,下载文件就需要这样设置
        resp.setHeader("Content-disposition", "attachment;filename=" + fileName);
        // IO操作
        FileInputStream fileInputStream = new FileInputStream(realPath);
        ServletOutputStream outputStream = resp.getOutputStream();
        int len;
        byte[] buffer = new byte[1024];
        while ((len = fileInputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, len);
        }
        fileInputStream.close();
        outputStream.close();
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doGet(req, resp);
    }
}

写完就去注册:

<servlet>
    <servlet-name>download</servlet-name>
    <servlet-class>vip.yangsf.Test01</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>download</servlet-name>
    <url-pattern>/download</url-pattern>
</servlet-mapping>

马上启动服务去试试:

访问 http://localhost:8080/download 就让我下载文件。

java开发 word 换行 javaweb换行servlet_java开发 word 换行_19

  1. 再搞个自动生成验证码(随机数)的页面
// 验证码
public class Test02 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 首先要有随机数
        Random random = new Random();
        // 生成六位随机数
        int a = random.nextInt(1000000);
        // 不足六位时用0填充 如 000252 不能是252
        String s = String.format("%06d", a);
        // 输出到网页上
        resp.getWriter().println(s);
        // 3秒刷新一次
        resp.setHeader("refresh", "3");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doGet(req, resp);
    }
}

还是注册注册注册

<servlet>
    <servlet-name>random</servlet-name>
    <servlet-class>vip.yangsf.Test02</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>random</servlet-name>
    <url-pattern>/random</url-pattern>
</servlet-mapping>

访问 http://localhost:8080/random , 成功每三秒刷新一次页面换一个验证码。

6.6.2 重定向

java开发 word 换行 javaweb换行servlet_java_20

访问A,A告诉浏览器应该访问B。

之前在6.5.3试了servlet的请求转发,又在6.5.4讲了转发和重定向的区别,现在我们用response做一个重定向,用sendRedirect方法。

简单实现一下:

public class Test03 extends HttpServlet {
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
       resp.sendRedirect("./random");
   }

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
       doGet(req, resp);
   }
}

注册:

<servlet>
    <servlet-name>redirect</servlet-name>
    <servlet-class>vip.yangsf.Test03</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>redirect</servlet-name>
    <url-pattern>/re</url-pattern>
</servlet-mapping>

然后我们访问 http://localhost:8080/re 就会跳转到 http://localhost:8080/random ,并且状态码多了一个302。

大概知道了重定向长啥样,我们就用重定向来实现一个简单的登录效果:

先来一个网页:

<%@ page contentType = "text/html;charset=UTF-8" language = "java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/login1", method="post">
    用户名:<input type="text" name="username">
    <br>
    密码:<input type="text" name="password">
    <br>
    <input type="checkbox" name="hobbies" value="rap"> rap
    <input type="checkbox" name="hobbies" value="篮球"> 篮球
    <input type="checkbox" name="hobbies" value="唱"> 唱
    <input type="checkbox" name="hobbies" value="跳"> 跳
    <input type="submit" value="登录">
</form>
</body>
</html>

servlet:

// 登录 重定向
public class Test04 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println(username + ":" + password);
        resp.sendRedirect("./random");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        doGet(req, resp);
    }
}

注册:

<servlet>
    <servlet-name>login</servlet-name>
    <servlet-class>vip.yangsf.Test04</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>login</servlet-name>
    <url-pattern>/login</url-pattern>
</servlet-mapping>

然后我们输入用户名和密码之后,控制台就会打印对应的用户名和密码,并且跳转到刚才生成随机数的页面。

6.7 HttpServletRequest对象

既然有HttpServletResponse对象,那必然是有HttpServletRequest对象的,我们向webServer发送请求,然后服务器把这个Http请求转化为HttpServletRequest对象传给servlet,我们就可以获取客户端的信息。

同样的,我们也可以设置一些请求的信息。

6.7.1 请求转发

假如还是刚才那个例子。

public class Test05 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码防止乱码
        req.setCharacterEncoding("utf8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String[] hobbies = req.getParameterValues("hobbies");
        System.out.println(username);
        System.out.println(password);
        System.out.println(Arrays.toString(hobbies));

        req.getRequestDispatcher("/random").forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

注册:

<servlet>
    <servlet-name>login1</servlet-name>
    <servlet-class>vip.yangsf.Test05</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>login1</servlet-name>
    <url-pattern>/login1</url-pattern>
</servlet-mapping>

提交表单后:

java开发 word 换行 javaweb换行servlet_web_21

可以看到url不变,却是/random的response

java开发 word 换行 javaweb换行servlet_java_22

控制台输出:

java开发 word 换行 javaweb换行servlet_intellij-idea_23