Servlet入门

什么是Servlet

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

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

简单的概括:

  • Servlet 是 JavaEE 规范之一。规范就是接口

  • Servlet 就 JavaWeb 三大组件之一。三大组件分别是:Servlet 程序、Filter 过滤器、Listener 监听器。

  • Servlet 是运行在服务器上的一个 java 小程序,它可以接收客户端发送过来的请求,并响应数据给客户端。

有以下几个特点:

  • Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
  • Servlet 是独立于平台的,因为它们是用 Java 编写的。
  • 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
  • Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。

Servlet的基本使用

  1. 编写一个类去实现 Servlet 接口

  2. 实现 service 方法,处理请求,并响应数据

  3. 到 web.xml 中去配置 servlet 程序的访问地址

这里我们service的方法体为一条打印语句,方便观察

public class ServletDemo1 implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("ServletDemo1对象被创建了");
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("ServletDemo1的service方法被执行了");
    }

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

    @Override
    public void destroy() {
        System.out.println("ServletDemo1对象被销毁了");
    }
}

web.xml

servlet设置对象名,然后给servlet取一个别名。紧接着写一个servlet-mapping标签,写入别名和处理的请求路径。当url-pattern的请求被Tomcat接收时,我们的ServletDemo1就会处理请求,并执行service()方法。

    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.kaikeba.course06.ServletDemo1</servlet-class>
    </servlet>

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

Servlet的生命周期方法

初始化

当对应Servlet的处理请求的URL路径被初次访问时,Tomcat会创建Servlet的实例对象,并调用init()初始化方法,之后处理Request请求,用的都是同一个Servlet。

@Override
public void init(ServletConfig servletConfig) throws ServletException {
    System.out.println("ServletDemo1对象被创建了");
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("ServletDemo1的service方法被执行了");
}

我们看到,service方法和init方法一起执行了

image-20200922170734628

对于比较核心的Servlet,比如SpringMVC 的DispathServlet,需要在web.xml配置文件中添加一条load on startup配置,让Servlet随着服务器的启动而启动。该属性值取0~10之间的值,Servlet随服务器启动而创建。如果不配置该属性(一般情况),实际上会给其一个默认值为-1。

<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.kaikeba.course06.ServletDemo1</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

可以看到我的Servlet对象在Tomcat启动的时候就已经创建了

image-20200922170859984

销毁

当Tomcat服务器关闭时,Servlet随服务器的关闭而消亡,会调用destroy()方法,表示Servlet对象被销毁

image-20200922172224540

Servlet的执行流程

  • 用户发出Request请求被Tomcat服务器解析
  • 请求解析器去servlet容器寻找有没有能够处理请求的处理器(Servlet)
  • 如果有则执行对应处理器的service方法,将结果(response)返回
  • 如果没有对应处理器,则加载web.xml配置信息,寻找是否存在可以处理请求的处理器的配置信息
  • 如果仍然没有对应处理器,返回错误信息给用户(404)
  • 如果找到对应处理器的配置信息,通过类加载器将该Servlet加载进内存,创建好Servlet之后,执行service方法,将结果(response)返回

image-20200922165459228

Servlet的继承体系

在这里插入图片描述

GenericServlet

GenericServlet是一个对Servlet接口的空实现抽象子类,GenericServlet的方法void方法方法体都是空的,返回对象的方法的返回值都是null。

我们实现这个类只要重写我们需要的方法即可。

public class GenericServletDemo1 extends GenericServlet {

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("服务来了");
    }
}

HttpServlet

GenericServlet虽然很方便,但是我们实际开发使用的最多的是HttpServlet。

除了Servlet自带的五种方法以外,HttpServlet自带了一些Http相关的方法,比如处理POST请求的方法doPost()方法,处理GET请求的doGet()方法。

public class HttpServletDemo2 extends HttpServlet {

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("HttpServlet开始服务了");
    }

    @Override
    public void init() throws ServletException {
        System.out.println("HttpServlet来了");
    }

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

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("get方法");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post方法");
    }
}

Servlet3.0注解配置

注解配置的使用

我们删除之前的web.xml的配置文件,在我们要配置URL路径的Servlet类上添加@WebServlet()注解,括号里面传入我们要处理的请求的URL路径,以/开头

@WebServlet("/hello")
public class ServletDemo1 implements Servlet {
    ...
}

原来的xml,我们看到别名是冗余的只是为了识别,一个servlet就8行,阅读性很差。

<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.kaikeba.course06.ServletDemo1</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

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

URL路径

我们看一下@WebServlet注解的源码,我们知道如果注解中只有一个属性,那么默认就是value()属性。其中value()属性指的就是urlPatterns(),我们看到其实传入的是一个数组,所以我们的Servlet3.0的注解配置支持一个Servlet处理多个请求。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    String name() default "";

    String[] value() default {};

    String[] urlPatterns() default {};

    int loadOnStartup() default -1;

    WebInitParam[] initParams() default {};

    boolean asyncSupported() default false;

    String smallIcon() default "";

    String largeIcon() default "";

    String description() default "";

    String displayName() default "";
}

如果要指定load-on-startup,我么可添加该参数,我们看到默认是-1,和web.xml的文件配置是一样的。

@WebServlet(urlPatterns = "/hello",loadOnStartup = 1)
public class ServletDemo1 implements Servlet {
    ...
}

URL的使用方式

  1. 普通方式
  • /hello:处理URL路径为/hello的请求
  • ``/user/hello`:处理URL路径为/hello的请求

很好理解,我们的URL和请求一一对应,不区分大小写。

  1. 添加通配符
  • /*:处理所有请求
  • /user/*:处理第一层为hello的所有请求
  • *.do:处理任何以.do结尾的请求

注意事项:

通配符的优先级是非常低的,所以默认情况下。用户发来一个请求,如果我们不是使用通配符的URL也能处理请求,则通配符是不生效的,但是如果没有一个Servlet可以处理一个请求,则通配符*才会生效的。

特殊情况演示:

请求:/hello

执行顺序:/hello/* > /hello > /*

请求:/user/hello

执行顺序:/user/hello/* > /user/hello > /user/* > /* >

我们如果定义了多级目录,比如/a/b 就不要使用/a这样的请求了!

  1. 错误方式
  • /*.do

image-20200922180014744

  • /*/*

    这种方式会失去匹配功能,也根据浏览器而异,一般情况下,该URL什么都无法接收。

  • /he*

image-20200922180042088

  1. 特殊方式
  • /

    在不同的浏览器对/请求处理不同。谷歌浏览器不会将/作为请求,而在火狐浏览器的//*有相同的作用,但是如果处理//*的Servlet都存在,则优先执行/*,所以我们尽量减少

HttpServlet

HttpServlet是GenericServlet的实现类,GenericServlet是Servlet接口的空实现抽象类,HttpServlet是我么最常用的Servlet了。

image-20200922182024154

常量

HttpServlt定义了8个常量,对应浏览器的7种请求(少了一个HEAD请求)。

public abstract class HttpServlet extends GenericServlet {

    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";
    ......
}

处理HTTP请求

源码比较长,简单看一下:

  • 使用request.getMethod()方法获取请求类型,值是“POST”,"GET"这样的大写。
  • 使用一堆if – else if 来判断是哪种请求,然后执行对应方法,比如POST请求对应doPost()方法。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
                ifModifiedSince = -1L;
            }

            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } 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);
    }
}

一般操作

我们只要复写对应的doGet(),doPost()方法即可,Servlet收到请求时,会根据requset.getMethod()获得请求类型,我们不要去干重写service方法的迷惑操作。

@WebServlet("/service")
public class HttpServletDemo2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("get方法");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post方法");
    }
}

中文乱码

Http之间传输数据可能会出现乱码问题,这需要我们统一编码:

接收到浏览器请求参数乱码

request.setCharacterEncoding("UTF-8");

接收用户数据乱码问题:

String username=request.getParameter("username");
//转码
username = new String(username.getBytes("ISO-8859-1") , "UTF-8");
System.out.println("userName="+username+"==password="+password);

发送数据给用户乱码问题:

//告诉服务器用编码来解析什么来解析
response.setCharacterEncoding("UTF-8");
//告诉客户端用什么编码
response.setHeader("content-type", "text/html;charset=UTF-8");

或者使用这个,一个顶两

response.setContentType("text/html;charset=UTF-8")
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐