Java 杂谈程序员技术栈每日一篇Java

JavaWeb基础(五)-Servlet详解

2018-08-17  本文已影响59人  sixleaves

这篇文章, 我主要分享JavaWeb动态网页开发.要谈JavaWeb动态网页开发.我们就要来了解Servlet, 所以我会按如下进行分享

1.什么是Servlet规范

Servlet是一套规范, 所谓的规范就是做了约束.

Servlet的规范

2.如何编写一个最简单的Servlet的步骤

3.如何研究Servlet对象的生命周期

Servlet对象生命周期依赖于容器, 因为Servlet规范已经约定了Servlet对象必须放在实现Servlet规范的容器中运行, 换区话说Servlet对象从出生到死亡都由容器调度。所以容器负责Servlet的创建、初始化、运行、释放.

要了解Servlet具体的生命周期, 我们先介绍以下上个方法.这三个方法是Servlet的生命周期方法.

生命周期方法

观察Servlet的生命周期

我们可以创建一个HelloServlet如下, 并且给它提供一个无参构造方法, 分别在上面上个生命周期方法中做打印, 来研究其调用顺序.

创建HelloServlet, 继承自HttpServlet.复写生命周期方法

package com.sweetcs.web.servlet;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet{
    
    public HelloServlet() {
        // TODO Auto-generated constructor stub
        System.out.println("Servlet构造方法");
    }
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        // TODO Auto-generated method stub
        super.init(config);
        
        System.out.println("Servlet init");
    }
    
    
    @Override
    protected void service(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
        System.out.println("Servlet service");
    }
    
    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        super.destroy();
        System.out.println("Servlet destroy");
    }   
}

web.xml中配置资源名的要交个哪个servlet处理

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0"
  metadata-complete="true">
  <display-name>Welcome to Tomcat</display-name>
  <description>
     Welcome to Tomcat
  </description>


    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.sweetcs.web.servlet.HelloServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
  
</web-app>

第一次访问http://127.0.0.1:8080/hello打印输出

第一次访问

第二、三次访问http://127.0.0.1:8080/hello打印输出

第二、三次访问

通过上述观察可以发现,第一次访问是依次调用了构造方法init方法service方法.但是什么时候Servlet对象的destory方法才会调用呢?我们进行以下两种方式验证。

点击带有叉号的猫

输出如下,说明调用了destory方法, Servlet被正确的释放掉.

打印出destory
点击小红点终止程序

输出如下, 说明Servlet对象的destory方法没有被容器调用, 也就是说Servlet没有被正确的释放掉.

根据上述实验验证,我们可以得出以下结论

总结

4.Servlet请求流程

我们了解了Servlet的规范、Servlet的生命周期、接着我们研究下我们从浏览器通过http协议请求相应的资源的时候。服务器端的Servlet从接受到请求的到响应的流程.

注意

5.Servlet初始化参数

了解完Servlet的请求流程, 我们也就明白了底层是如何创建Servlet对象, 同时知道了配置文件的作用。接着我们就顺着该流程的第一个步骤, 创建并初始化Servlet对象, 想来了解下Servlet的初始化参数.

5.1 init(config)

什么是初始化参数, 即Servlet在创建后, 我们可能会需要它附带一些属性, 并且我们用配置好的参数来初始化这些Servlet.这些参数都是配置在web.xml<Servlet元素>中.
如下我们给HelloServlet配置初始化参数, 并读取出来查看.

web.xml配置, 在servlet元素中添加init-param元素

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.sweetcs.web.servlet.HelloServlet</servlet-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </servlet>

Java代码

public class HelloServlet extends HttpServlet{
    
    private ServletConfig config;
    
    public HelloServlet() {
        // TODO Auto-generated constructor stub
        System.out.println("Servlet构造方法");
    }
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        // TODO Auto-generated method stub
        super.init(config);
        this.config = config;
        System.out.println("Servlet init");
    }
    
    
    @Override
    protected void service(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
        System.out.println("Servlet service");
        
        String encoding = config.getInitParameter("encoding");
        System.out.println(encoding);
        
    }
    
    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        super.destroy();
        System.out.println("Servlet destroy");
    }   
}

启动tomcat, 并浏览器输入http://127.0.0.1:8080/hello.打印如下,可以正确的将配置参数读取出来.

Servlet构造方法
Servlet init
Servlet service
UTF-8

5.2

根据5.1我们知道一个ServletConfig对象 对应的就是其WEB-INF下的web.xml配置文件.其主要作用就是用来读取配置文件中的Servlet元素的初始化信息

其有以下两个常用接口

6 Servlet继承体系的设计原因

上面我们分享了如何编写一个Servlet程序、以及Servlet的生命周期过程、Servlet的请求流程和Servlet的初始化参数。接着我们就来研究下Servlet的继承体系。我主要借助eclipse和源码来和大家分享下Servlet的继承体系的结构和设计原因.

1.Servlet的是一个规范, 我们如何遵循其规范来编写程序呢?

我们通过查看Servlet接口可以看到其对应的接口代码.如果让我们自己来实现一个Servlet程序,那么我们首先得实现其Servlet接口.
并且我们得实现其生命周期方法, 来让tomcat能管理Servlet对象、并且我们得实现ServletConfig接口.让Servlet能得到正确的初始化.依照这个思路我们可以写出第一个版本实现Servlet接口.

package com.sweetcs.web.servlet;

import java.io.IOException;

import javax.security.auth.login.Configuration;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class MyServlet implements Servlet{

    private ServletConfig config;
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        // TODO Auto-generated method stub
        System.out.println("初始化参数");
        this.config = config;
    }

    @Override
    public ServletConfig getServletConfig() {
        // TODO Auto-generated method stub
        return this.config;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        // TODO Auto-generated method stub
        
        System.out.println("处理请求");
    }

    @Override
    public String getServletInfo() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        System.out.println("desotry servlet");
    
    }
    
}

为了让tomcat能正确找到对应的Servlet我们还需要配置web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0"
  metadata-complete="true">
  <display-name>Welcome to Tomcat</display-name>
  <description>
     Welcome to Tomcat
  </description>

    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>com.sweetcs.web.servlet.MyServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/test</url-pattern>
    </servlet-mapping>
  
</web-app>

启动tomcat.并在浏览器中输入http://127.0.0.1:8080/test进行测试.

初始化参数
处理请求

2.如何读取初始化参数

我们现在遵循Servlet接口开发的Servlet程序能正常运行了,但是我们如何读取配置呢?我们可以这样做,用一个ServletConfig属性存储该ServletConfig对象, 在service方法需要的时候进行读取.我们在service方法中读取
如下代码

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        // TODO Auto-generated method stub
        
        System.out.println("处理请求");
        String encoding = config.getInitParameter("encoding");
    }

为什么上述实现需要重构
上述代码并不存在问题, 但是如果我们现在有10个Servlet要编写, 我们就得重复的在每个Servlet中存储一个ServletConfig属性用来存储ServletConfig.而且我们还得为每个Servlet实现上述代码中的所有接口。这时候代码就重复了, 一旦代码重复, 我们就要考虑进行重构,如何进行重构呢?对于重复代码我们可以采用继承方式或者组合方法.一般我们都使用组合方式进行重构, 但是在这里由于我们编写的Servlet都是必须实现该规范的, 所以我们并没必要去用组合的方式.
由此思路我们可以将这些代码抽取到一个类中, 我们把它叫MyGenericServlet

3.消除Servlet接口重复代码,实现MyGenericServlet

package com.sweetcs.web.servlet;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public abstract class MyGenericServlet implements Servlet{
    private ServletConfig config;
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        // TODO Auto-generated method stub
        System.out.println("初始化参数");
        this.config = config;
    }

    @Override
    public ServletConfig getServletConfig() {
        // TODO Auto-generated method stub
        return this.config;
    }

    @Override
    public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
    

    @Override
    public String getServletInfo() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        System.out.println("desotry servlet");
    
    }
}

4.现在考虑子类如何读取初始化参数?

public abstract class MyGenericServlet implements Servlet, ServletConfig{
    private ServletConfig config;
    
    @Override
    public String getInitParameter(String name) {
        // TODO Auto-generated method stub
        return config.getInitParameter(name);
    }
    
    @Override
    public Enumeration<String> getInitParameterNames() {
        // TODO Auto-generated method stub
        return config.getInitParameterNames();
    }
.......// 省略后面代码
}

5.需要定制专门处理Http的Servlet

经过1--4 我们已经抽取了MyGenericServlet, 进一步让代码重用性得到提高.现在的问题是, MyGenericServlet是处理一般的资源的Servlet。其service方法中的request和response对象并不是HttpRequest和HttpResponse对象.也就是说MyGenericServlet并不适合处理Http类型的请求,它是一个处理通用的请求,子类还需要继承MyGenericServlet做进一步订制.

package com.sweetcs.web.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyHttpServlet extends MyGenericServlet{

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

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        service(request, response);
    }
    
    protected void service(HttpServletRequest req, HttpServletResponse resp) {
        
        String method = req.getMethod();
        
        if ("GET".equalsIgnoreCase(method)) {
            doGet();
        }else if ("POST".equalsIgnoreCase(method)) {
            doPost();
        }else {
            
        }
        
    }

    private void doPost() {
        // TODO Auto-generated method stub
        
    }

    private void doGet() {
        // TODO Auto-generated method stub
        doPost();
    }
}

继承体系图

通过上述代码,我们可以可以对比下我们实现的继承体系HttpServlet的继承体系。(按command + t查看类的继承结构)
其之所以这样设计,就是为了消除代码重复,为了提供专门处理Http请求的Servlet

MyHttpServlet的继承结构
HttpServlet继承结构
总结Servlet继承结构

7. Servlet请求和相应

我们了解了整个Servlet的请求流程和其体系结构,其实现在我们就可以进行更多复杂的业务逻辑开发。在这之前我们需要了了解下HttpServletRequest和HttpServletResponse的常用API.
由于是API的使用, 我们将简要的介绍完API功能后, 使用代码演示一个注册用户的功能.

HttpServletRequest

是什么:HttpServletRequest是ServletRequest的子类, 主要用于处理Http请求, 其表示一个Http请求对象.

常用API
获取请求参数的方法
request的编码问题
byte[] data = username.getBytes("ISO-8859-1");
username = new String(data, "UTF-8"); // 这时候就可以正确的输出

HttpServletResponse

常用API
注意

需求,写代码

我们需要完成这样一个需求, 通过servlet完成一个网页版的计算器。具体页面如下, 因为我们还没介绍jsp, 所以需要在servlet中完成网页页面的绘制.

页面

计算器页面

代码

package com.sweetcs.calc.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/calc")
public class CalcServlet extends HttpServlet{

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        req.setCharacterEncoding("UTF-8"); // 只针对post请求有效
        resp.setContentType("text/html; charset=UTF-8"); // 设置输出的类型和编码
        String op = req.getParameter("op");
        String leftNum =  req.getParameter("leftNum");
        String rightNum = req.getParameter("rightNum");
        
        if (leftNum == null || "".equals(leftNum)) leftNum = "0";
        if (rightNum == null || "".equals(rightNum)) rightNum = "0";
        
        String result = "0";
        System.out.println(op);
        System.out.println(leftNum + "," + rightNum);
        
        
        
        if (op != null) {
            switch (op) {
            case "+":
                result = (Double.parseDouble(leftNum) + Double.parseDouble(rightNum))+"";
                break;
            case "-":
                result =  (Double.parseDouble(leftNum) - Double.parseDouble(rightNum))+"";
                break;
            case "*":
                result =  (Double.parseDouble(leftNum) * Double.parseDouble(rightNum))+"";
                break;
            case "/":
                result =  (Double.parseDouble(leftNum) / Double.parseDouble(rightNum))+"";
                break;
            default:
                break;
            }
        }
        
        PrintWriter pw = resp.getWriter();
        pw.write("<!DOCTYPE html> <html>"
                + " <head> "
                + "<meta charset='UTF-8'> "
                        + "<title>Insert title here</title> "
                        + "</head> "
                        + "<body> " + "<form action='/calc' method='GET'>"
                        + "<input type='text' name='leftNum' /> "
                        + "<select name = 'op'> "
                        + "<option value='+'>+</option> "
                        + "<option value='-'>-</option> "
                        + "<option value='*'>*</option> "
                        + "<option value='/'>/</option> "
                        + "</select> "
                        + "<input type='text' name='rightNum' /> "
                        + "<input type='submit' value='=' /> "
                                + "<input type='text' name='result' value="+ result +" /> "
                                + "</form> "
                                + "</body> "
                                + "</html>");
        pw.close();
        
    }
    
}

后续

JavaWeb基础(六)中我将分享Web开发中的Cookie和Session.

上一篇下一篇

猜你喜欢

热点阅读