前言

感谢

Servlet简介

Servlet,其实就是一个接口,这个接口定义了Java类被浏览器访问到服务器的规则

如果我们定义一个类去实现Servlet接口,那么我们实现的这个类也可以被浏览器识别

起步

HelloServlet

我们用一个新的项目来说明如何编写一个Servlet类

1、新建一个项目,这个项目需要选择项目的原型,首先肯定是maven项目,然后我们需要选择项目的骨架,next

Servlet - 图1

2、随便起个什么名字,然后一直下一步,直到项目创建完毕

3、我们看到了pom.xml文件,需要导入servlet的依赖,依赖如下

  1. <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
  2. <dependency>
  3. <groupId>javax.servlet</groupId>
  4. <artifactId>javax.servlet-api</artifactId>
  5. <version>4.0.1</version>
  6. <scope>provided</scope>
  7. </dependency>

4、补全目录

或许javaweb项目创建出来之后是不完整的,所以我们需要手动补全目录结构,结构如下: Servlet - 图2

5、web.xml

现在的web.xml貌似有一些改版,不过影响不大,现在的改版之后貌似是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

</web-app>

不过为了我们的学习使用,我们还是使用老版本的

<?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"
         metadata-complete="true">

</web-app>

不管使用哪个版本,我们在一开始学习的时候,除了上述的几个标签之外,其余的全部都删除,保留一个原始纯净的样子,其他的标签后面会慢慢讲到

6、HelloServlet

我们在之前讲过,只要实现Servlet就可以被浏览器访问,现在我们新建一个类实现Servlet类,遵循传统,我们的名字起名为HelloServlet

package com.howling;

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

public class HelloServlet implements Servlet {
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * Servlet为我们提供服务的方法
     *
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}

service,就是为我们提供服务的方法

6、在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"
         metadata-complete="true">

    <servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.howling.HelloServlet</servlet-class>
    </servlet>


    <servlet-mapping>
        <servlet-name>helloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

其中,servlet-class标签里的内容填充为你自己的全类名 注意,servlet-mapping可以定义多个,这说明我们可以通过多个路径来访问到资源

7、设置tomcat服务器

1、idea右上方有一个添加配置 Servlet - 图3 2、点击加号,选择你的tomcat服务器 Servlet - 图4 3、配置你的tomcat服务器的本地地址,url,http端口号 Servlet - 图5 4、点击部署,添加你的项目到部署中 Servlet - 图6 5、选择完成之后,会出现一个地址 Servlet - 图7 6、然后我们在回到服务器选项中,发现url改动了 Servlet - 图8 7、保存这个设置

8、启动

出现了这个页面,代表着我们的服务已经成功了 Servlet - 图9

9、在tomcat启动后日志打印乱码

在你的 tomcat/conf/logging.properties 下面改动如下部分

1catalina.org.apache.juli.AsyncFileHandler.encoding = GBK
2localhost.org.apache.juli.AsyncFileHandler.encoding = GBK
3manager.org.apache.juli.AsyncFileHandler.encoding = GBK
4host-manager.org.apache.juli.AsyncFileHandler.encoding = GBK
java.util.logging.ConsoleHandler.encoding = GBK

10、修改Tomcat的配置内容

我们在访问tomcat的时候如果前方要加上一长串内容总是让我们很不爽,所以更改一下 Servlet - 图10 Servlet - 图11 改动完成之后在执行一次

11、访问Servlet

我们在xml的配置是:/hello,这个是访问的路径,前面要加上ip和端口号,我们已经在上一步就配置好了,所以完整的路径是:127.0.0.1:8080/hello,其中127.0.0.1可以使用localhost来代替,代表着本机,所以 Servlet - 图12 没有内容很正常,毕竟我们没有编写页面内容,但是我可以提前透露一个内容,只需要修改service方法即可

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    servletResponse.getWriter().println("Servlet");
    servletResponse.getWriter().close();
}

然后我们重启服务器,查看这个网址,发现 Servlet - 图13


Servlet生命周期

Servlet的生命周期

1、init:创建,执行了init方法,在生命周期中只执行一次

Servlet创建的时间: 1、第一次被访问的时候创建Servlet,也是默认情况 2、可以在web.xml下面配置Servlet的创建时间:xml中使用<load-on-startup>

  • 第一次被访问时创建,值为-1
  • 服务器启动被创建:只要是大于等于0的数即可
<servlet>
    <servlet-name>helloServlet</servlet-name>
    <servlet-class>com.howling.HelloServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

Servlet创建只执行一次说明了 1、一个Servlet在内存中只存在一个 2、Servlet是单例的 3、多个用户同时访问的时候,可能存在线程问题 4、为了防止线程不安全,尽量不要在Servlet中定义成员变量,即使定义,也不要修改这个变量的值,否则会出现线程问题 5、我们要尽量使用局部变量来代替成员变量

2、service方法:执行多次,每次访问service时都会调用一次

3、destory方法:只执行一次

1、在被销毁之前执行一次,服务器关闭的时候会被销毁 2、只有服务器正常关闭的时候才会执行这个方法,非正常关闭不会执行 3、在Servlet被销毁之前执行,一般用于释放资源

证明Servlet生命周期

为了证明我说的是对的,我们分别在这几个方法中打印几句话

public class HelloServlet implements Servlet {
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init...");
    }

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service...");
    }

    public void destroy() {
        System.out.println("destory...");
    }
}

然后我们重新启动服务器,多次访问servlet,最后关闭服务器,看它打印的结果 Servlet - 图14 通过测试我们可以发现 1、在调用servlet的一瞬间,首先打印了init,然后打印了service 2、多次访问servlet,只打印了service 3、在关闭服务器的时候,首先打印了一串日志,然后打印了destory

Servlet接口各个方法的含义

package com.howling;

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

public class HelloServlet implements Servlet {


    /**
     * Servlet生命周期的初始化,只执行一次,默认在访问Servlet的时候执行
     *
     * @param servletConfig
     * @throws ServletException
     */
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init...");
    }

    /**
     * Servlet的配置对象
     *
     * @return
     */
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * Servlet提供服务的方法,访问Servlet的时候执行
     *
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service...");
    }

    /**
     * 获取Servlet的信息,版本,作者等等
     *
     * @return
     */
    public String getServletInfo() {
        return null;
    }

    /**
     * Servlet销毁时的方法,只执行一次
     */
    public void destroy() {
        System.out.println("destory...");
    }
}

Servlet3.0

为什么要单独拿出Servlet3.0呢,因为在Servlet3.0中,我们可以直接使用注解来代替xml

也就是说,我们不需要web.xml了,下面我来演示一下

1、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"
         metadata-complete="false">
</web-app>

主要是metadata-complete更改为false 当这个属性为true的时候,忽略所有注解 当这个属性为false的时候(或者删除这个属性),注解和xml同时生效,但是如果使用xml的方式访问的时候,注解会失效

2、删除web.xml中servlet和servlet-mapping的配置

3、修改Servlet

package com.howling;

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

@WebServlet("/hello")
public class HelloServlet implements Servlet {


    /**
     * Servlet生命周期的初始化,只执行一次,默认在访问Servlet的时候执行
     *
     * @param servletConfig
     * @throws ServletException
     */
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init...");
    }

    /**
     * Servlet的配置对象
     *
     * @return
     */
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * Servlet提供服务的方法,访问Servlet的时候执行
     *
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service...");
    }

    /**
     * 获取Servlet的信息,版本,作者等等
     *
     * @return
     */
    public String getServletInfo() {
        return null;
    }

    /**
     * Servlet销毁时的方法,只执行一次
     */
    public void destroy() {
        System.out.println("destory...");
    }
}

或许你们注意到了,我们在类上加了一个注解:@WebServlet,这个注解可以代替我们之前的

<servlet>
    <servlet-name>helloServlet</servlet-name>
    <servlet-class>com.howling.HelloServlet</servlet-class>
</servlet>


<servlet-mapping>
    <servlet-name>helloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

重启服务器,来看一下

4、注解的多个路径

注解可以定义多个路径,这多个路径都可以访问这一个Servlet,比如: @WebServlet({"/hello","/demo1","/demo"}) 路径的匹配规则 1、/xxx:最基础的访问路径 2、/xxx/xx:多层访问路径,在访问的时候要访问/xxx/xx 3、/xx/:在访问的时候只需要访问/xx/xxx,其中xxx可以随便填 4、.do:扩展名的匹配,这里不用加斜杠,只要访问xxx.do就可访问到Servlet,xxx可以随便填,do也可以换别的

5、对于Service3.0

我们在学习阶段,建议首先使用xml的方式来配置javaweb项目,以后在快速开发的时候不妨使用3.0的注解


Servlet的更多内容

Servlet原理

浏览器向web容器(服务器)发出http请求

1、web容器生成一个Servlet(只有首次请求会生成)

2、web容器生成两个对象

  • 请求:处理客户端请求
  • 响应:响应客户端请求

3、web容器激活servlet的service方法,传递请求和响应对象作为参数

4、service()获取请求对象的信息,处理请求信息

5、service()使用响应对象的方法将响应传递回web容器

6、web容器响应给客户端

7、重复3-5的过程

8、销毁

Servlet的体系结构

其实关于Servlet,并不是只有一个接口,它的结构是这样的:

Servlet - 图15

从图中可以非常清楚的看到,Servlet作为一个接口,GenericServlet和HttpServlet都直接或者间接的实现了它

我们最常使用的就是HttpServlet,因为它对Servlet做了很多的封装和默认参数调整,我们只需要继承HttpServlet然后就可以非常轻松的编写代码了


HttpServlet

HttpServlet起步

1、继承HttpServlet

2、重写doGet和doPost方法

package com.howling;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }

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

关于重写doGet和doPost的这个事情,不知道是否还记得我们曾经在学习前端的时候,在表单提交那一块内容我们曾经讲过,表单的提交有两种方式,一种叫get方式,一种叫post方式

那么这两种方式就对应着这两种方法

post提交会访问doPost方法,get提交会访问doGet方法


HttpServlet源码分析

虽然我们知道Servlet的体系结构,但其实GenericServlet没有什么好分析的,我们只需要看HttpServlet即可

Servlet - 图16

图中的几个方法都是比较重要的,我们其实可以看到,除了doGet和doPost还有这么多的请求方式 但是在这里我们先不做探讨,以后早晚会用到的

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

    //看看请求和响应是不是HTTP的请求和响应,如果是就直接转换,如果不是抛出异常,说明这个请求响应不是http协议的
    if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        this.service(request, response);
    } else {
        throw new ServletException("non-HTTP request or response");
    }
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //获取请求方式
    String method = req.getMethod();
    long lastModified;
    //假如为get请求,那么...
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if (ifModifiedSince < lastModified) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
        //假如为head请求,那么...
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
        //假如为post请求,那么调用doPost
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
        //假如为put请求,那么调用doPut
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
        //....
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

这段代码虽然看起来多,但是逻辑非常清晰,到了最后总是要调用我们这几个doxxx的方法的 而这几个doxxx的方法我们是要进行重写的


ServletContext

web容器在启动的时候,会给每一个web程序都创建一个ServletContext,它代表着当前的web应用,和servlet不同,ServletContext在每个web应用中都只有一个

ServletContext有几个作用

1、共享数据

2、获取初始化参数

3、请求转发

4、读取资源文件


共享数据

package com.howling;

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("/world")
public class WorldServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

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

        ServletContext servletContext = this.getServletContext();
        servletContext.setAttribute("username","howling");

    }
}
package com.howling;

import javax.servlet.*;
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("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);

        ServletContext servletContext = this.getServletContext();
        System.out.println(servletContext.getAttribute("username"));
    }
}

获取初始化参数

<?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"
         metadata-complete="false">

    <context-param>
        <param-name>url</param-name>
        <param-value>jdbc:mysql:///student</param-value>
    </context-param>

    <servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.howling.HelloServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>helloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>
package com.howling;

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


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);

        ServletContext servletContext = this.getServletContext();

        String url = servletContext.getInitParameter("url");
        System.out.println(url);
    }
}

请求转发

package com.howling;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("Hello");

        ServletContext servletContext = this.getServletContext();

        RequestDispatcher dispatcher = servletContext.getRequestDispatcher("/world");
        dispatcher.forward(req, resp);

    }
}
package com.howling;

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;
import java.io.PrintWriter;

@WebServlet("/world")
public class WorldServlet extends HttpServlet {

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

        System.out.println("World");
    }
}

读取资源文件

首先要说明一下,在编译之前和编译之后的资源文件地址

Servlet - 图17

这里打包后的classes我们俗称为classpath

Servlet - 图18

我们在resources中添加一个properties文件,根据上面的图片,我们可以得到最终这个文件会在WEB-INF/classes中

package com.howling;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        ServletContext servletContext = this.getServletContext();

        InputStream stream = servletContext.getResourceAsStream("/WEB-INF/classes/hello.properties");

        Properties properties = new Properties();

        properties.load(stream);


        resp.getWriter().print(properties.getProperty("username") + "--"+properties.getProperty("password"));

        System.out.println();
        stream.close();


    }
}

HttpServletRequest

HttpServletRequest,代表的是客户端的请求,用户使用Http协议请求服务器,Http请求中的信息会被封装到HttpServletRequest中,通过这个方法获得客户端的所有信息

获取请求行

  • String getMethod():获取请求的方式
  • String getContextPath():获取站点的根目录
  • String getQueryString():获取get方式的请求参数
  • String getRequestURI():获取请求的URI
  • StringBuffer getRequestURL():获取URL

    URL:统一资源定位符 URI:统一资源标识符 URI比URL的范围更大

  • String getProtocol():获取协议和版本

  • String getRemoteAddr():获取客户机的IP地址
  • String getScheme():获取当前页面使用的协议,默认是http,SSL时返回https
  • String getServerName():可以返回当前页面所在的服务器的名字
  • String getServerPort():获取当前页面服务器所使用的端口号

在敲代码之前,首先我们要将tomcat的前缀名加上

Servlet - 图19

package com.howling;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        PrintWriter writer = resp.getWriter();

        //获取请求方式
        writer.write("getMethod--" + req.getMethod() + "\n");

        //获取站点的根目录
        writer.write("getContextPath--" + req.getContextPath() + "\n");

        //获取get方式的请求参数
        writer.write("getQueryString--" + req.getQueryString() + "\n");

        //获取请求的uri
        writer.write("getRequestURI--" + req.getRequestURI() + "\n");

        //获取请求的url
        writer.write("getRequestURL--" + req.getRequestURL() + "\n");

        //获取协议和版本
        writer.write("getProtocol--" + req.getProtocol() + "\n");

        //获取客户端的ip地址
        writer.write("getRemoteAddr--" + req.getRemoteAddr() + "\n");

        //获取协议
        writer.write("getScheme--" + req.getScheme() + "\n");

        //获取当前页面所在的服务器的名字
        writer.write("getServerName--" + req.getServerName() + "\n");

        //获取页面服务器所使用的端口号
        writer.write("getServerPort--" + req.getServerPort() + "\n");

        writer.close();


    }
}

Servlet - 图20

获取请求头

  • Enumeration<String> getHeaderNames():获取请求头的名称
package com.howling;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        PrintWriter writer = resp.getWriter();


        Enumeration<String> headerNames = req.getHeaderNames();

        while (headerNames.hasMoreElements()){
            writer.write(headerNames.nextElement()+"\n");
        }

        writer.close();


    }
}

Servlet - 图21

获取请求体

  • BufferedReader getReader():获取字符输入流,只能操作字符数据
  • ServletInputStream getInputStream():获取字节输入流,可以操作所有类型的数据
  • String getParameter(String name):根据参数名称获取参数值
  • String[] getParameterValues():获取所有请求的参数名称
  • Map<String,String[]> getParameterMap():获取所有参数的Map集合

中文乱码问题

  • get方式:tomcat8之后已经解决了get乱码
  • post方式:乱码的解决需要手动解决

    在获取参数之前,设置request的编码request.setCharacterEncoding(“utf-8”) 这个作用是设置客户端请求和数据库取值时的编码

请求转发

1、步骤

1、通过request对象获取请求转发对象:RequestDispatcher getRequestDispatcher(String path) 2、使用RequestDispatcher对象来进行转发:forward(ServletRequest request,ServletResponse response)

2、特点

1、浏览器地址栏路径不发生变化 2、只能转发到当前服务器内部资源中,不能再访问其他服务器资源 3、转发是一次请求,虽然两个资源同时被访问,但其实是一次请求

package com.howling;

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


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        req.getRequestDispatcher("/world").forward(req,resp);

    }
}

HttpServlerResponse

web服务器接受到客户端的请求,针对这个请求,分别创建一个代表请求的HttpServletRequest对象,代表着响应的一个HttpServletResponse

向浏览器发送数据

  • ServletOutputStream getOutputStream()
  • PrintWriter getWriter()

向浏览器发送响应头

  • setContentLength(int val)
  • setContentLengthLong(long var)
  • setDateHeader(String var1, long var2)
  • setHeader(String var1, String var2)
  • setIntHeader(String var1, int var2)

中文乱码问题

response.setCharacterEncoding(“utf-8”)

这个作用是设置服务器响应给浏览器的编码

response.setContentType(“text/html;charset=utf-8”)

这个作用是设置服务器响应给浏览器的编码,并且浏览器也根据这个参数来进行解码

Servlet - 图22

下载文件

1、获取下载文件的路径

2、获取下载的文件名字

3、设置浏览器的编码,否则可能会乱码

4、获取下载文件的输入流

5、file和io那一套东西

Servlet - 图23 Servlet - 图24

package com.howling;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {


        String realPath = this.getServletContext().getRealPath("\\img\\1.png");

        //D:\Environment\Tomcat\apache-tomcat-9.0.14\webapps\servlet\img\1.png
        System.out.println(realPath);

        String filename = realPath.substring(realPath.lastIndexOf("\\"));

        //\1.png
        System.out.println(filename);

        //设置之后会自动读取文件类型
        resp.setContentType("multipart/form-data");

        //设置请求头,其中filename是文件的名字
        resp.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(filename, "UTF-8"));

        FileInputStream inputStream = new FileInputStream(realPath);

        int len = 0;
        byte[] buffer = new byte[1024];

        ServletOutputStream outputStream = resp.getOutputStream();

        while ((len = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, len);
        }

        inputStream.close();
        outputStream.close();
    }
}

后端实现验证码

1、设置浏览器的刷新

2、通过BufferedImage创建图片

3、Graphics2D得到图片并且写入数据

4、response告诉浏览器编码方式

5、取消浏览器缓存

6、写入到浏览器

package com.howling;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //让浏览器三秒自动刷新一次
        resp.setHeader("refresh", "3");

        BufferedImage image = new BufferedImage(80, 20, BufferedImage.TYPE_INT_BGR);


        Graphics2D graphics = (Graphics2D) image.getGraphics();

        //背景颜色
        graphics.setBackground(Color.white);


        graphics.fillRect(0, 0, 80, 20);

        //给图片写数据
        graphics.setColor(Color.BLUE);

        graphics.setFont(new Font(null, Font.BOLD, 20));

        graphics.drawString(makeNum(), 0, 20);


        //告诉浏览器,请求用图片打开
        resp.setContentType("image/jpeg");

        //网站存在缓存,不让浏览器缓存
        resp.setDateHeader("expires", -1);
        resp.setHeader("Cache-Control", "no-cache");
        resp.setHeader("Pragma", "no-cache");

        ImageIO.write(image, "jpg", resp.getOutputStream());


    }

    private String makeNum() {

        Random random = new Random();
        String num = random.nextInt(99999) + "";

        return num;
    }
}

重定向

  • sendRedirect()

重定向和转发 相同点:页面都会实现跳转 不同点 1、请求转发时url不会变化,而重定向url会变化 2、转发是服务器的行为,重定向是客户端的行为 3、请求转发是一次请求,重定向是多次请求 4、请求转发共享数据,重定向不共享数据 5、请求转发只能定位到本服务器资源,重定向可以定位到任意的url

package com.howling;

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


public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.sendRedirect("https://www.baidu.com");

    }
}

Servlet线程安全问题

首先我要说,Servlet不是线程安全的。

1、首先,Servlet是单例的

2、然后,仅有HttpServletRequest是针对不同请求(线程)而有不同实例的,其他都是共享的。

3、根据以上两点情况,Servlet不是线程安全的

当然了,除了HttpServletRequest是线程安全的,HttpContext和HttpSession都不是线程安全的