Servlet

180 阅读7分钟

(关于如何创建动态web工程,参考文章:juejin.cn/post/714320… 的第3部分)

Servlet简介

Servlet概述

动态网页开发技术(Servlet 和 JSP)中的其中一项;是运行在web服务器上的java程序,可以用于处理web客户端发送的请求,并且可以对请求做出响应。

案例入门

目录结构:

image.png
package com.servlet.demo;

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

public class HelloServlet implements Servlet {
    
    // 在 Servlet 对象创建之后马上执行,并只执行一次。
    // ServletConfig是Tomcat传回来的
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    // 获取 Servlet 的位置信息
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    // 可以被调用多次,且每次处理请求都是在调用这个方法
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        servletResponse.getWriter().println("Hello Servlet...");
    }

    // 获取 Servlet 的信息
    @Override
    public String getServletInfo() {
        return null;
    }

    // 在 Servlet 被销毁之前调用,且只会调用一次
    @Override
    public void destroy() {
    }
}
<?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">

    <display-name>web_test2</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>

    <!--配置Servlet-->
    <servlet>
        <!--配置Servlet名称-->
        <servlet-name>HelloServlet</servlet-name>
        <!--配置Servlet全路径-->
        <servlet-class>com.servlet.demo.HelloServlet</servlet-class>
    </servlet>

    <!--配置Servlet的一个映射-->
    <servlet-mapping>
        <!--配置Servlet名称-->
        <servlet-name>HelloServlet</servlet-name>
        <!--配置访问的路径-->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

验证:

image.png

Servlet原理

Servlet执行流程

image.png

Servlet接口实现关系

  • servlet接口
  • GenericServlet实现类(抽象类,通用的Servlet)
  • HttpServlet实现类(HTTP专用的Servlet)

HttpServlet是与协议相关的,专门处理HTTP协议的请求的。一般都会继承这个类,然后重写service方法

service()内部根据请求方式的不同去执行不同的方法,get请求执行doGet()方法,post请求执行doPost()方法

所以一般不重写service()方法,一般重写doGet()方法和doPost()方法即可

public class AServlet extends HttpServlet {

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


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("Hello Servlet,Hello doGet...");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("Hello Servlet,Hello doPost...");
    }
}

doPost可以用表单进行测试

Servlet生命周期

  • init()
    • Servlet在第一次被访问时被实例化,调用此方法
  • service()
    • 每一次从客户端发来的请求,就会执行此方法
  • destory()
    • Servlet从服务器移除或者服务器关闭的时候,Servlet对象会被销毁,执行此方法,然后垃圾回收

Servlet的启动时加载

servlet默认是在第一次访问的时候创建对象,现在通过配置将servlet的实例化过程放在服务器启动的时候(不过一般不这么用)

  <servlet>
    <servlet-name>ServletDemo2</servlet-name>
    <servlet-class>com.servlet.demo.ServletDemo2</servlet-class>
    <!--配置启动时加载,数值越小,启动优先级越高,一般是2或以后的数字-->
    <load-on-startup>2</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletDemo2</servlet-name>
    <url-pattern>/demo2</url-pattern>
  </servlet-mapping>

效果:

image.png

Servlet的访问路径配置

<url-pattern>配置方式:

  • 完全路径匹配
    • 以/开始,比如/demo2 /aaa/demo2
  • 目录匹配
    • 以/开始,以/*结束,比如/* /aaa/*
  • 扩展名匹配
    • 不能以/开始;以*开始,比如*.action *.do *.jsp
  • 访问优先级: 完全路径匹配 > 目录匹配 > 扩展名匹配
  <servlet>
    <servlet-name>ServletDemo3</servlet-name>
    <servlet-class>com.servlet.demo1.ServletDemo3</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletDemo3</servlet-name>
    <!--完全路径匹配-->
    <!-- <url-pattern>/demo3</url-pattern> -->
    <!--目录匹配-->
    <!-- <url-pattern>/*</url-pattern> -->
    <!--扩展名匹配-->
    <url-pattern>*.abc</url-pattern>
  </servlet-mapping>

Servlet常用对象

ServletConfig(接口)

ServletConfig是用来获得Servlet的相关配置的对象(一个ServletConfig对象对应一段web.xml中Servlet的配置信息)

获得ServletConfig对象:getServletConfig()

  • String getServletName():获取的是<servlet-name>中的内容;
  • ServletContext getServletContext():获取Servlet上下文对象;
  • String getInitParameter(String name):通过名称获取指定初始化参数的值;
  • Enumeration getInitParameterNames():获取所有初始化参数的名称;
<!--配置Servlet-->
<servlet>
    <!--配置Servlet名称-->
    <servlet-name>BServlet</servlet-name>
    <!--配置Servlet全路径-->
    <servlet-class>com.servlet.demo.BServlet</servlet-class>
    <init-param>
        <param-name>username</param-name>
        <param-value>root</param-value>
    </init-param>
    <init-param>
        <param-name>password</param-name>
        <param-value>root1234</param-value>
    </init-param>
</servlet>

<!--配置Servlet的一个映射-->
<servlet-mapping>
    <!--配置Servlet名称-->
    <servlet-name>BServlet</servlet-name>
    <!--配置访问的路径-->
    <url-pattern>/btest</url-pattern>
</servlet-mapping>
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().append("Served at:").append(req.getContextPath()); //Served at:/web_test1_war_exploded
        // 获得servletConfig对象
        ServletConfig config = this.getServletConfig();
        String username = config.getInitParameter("username");
        String password = config.getInitParameter("password");
        System.out.println("getInitParameter:" + username + "--" + password); // getInitParameter:root--root1234
        // 获得所有初始化参数的名称
        Enumeration<String> names = config.getInitParameterNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            String value = config.getInitParameter(name);
            System.out.println("getInitParameterNames:" + name + "--" + value);
            // 输出:
            // getInitParameterNames:password--root1234
            // getInitParameterNames:username--root
        }
        // 获得servlet的名称
        String servletName = config.getServletName();
        System.out.println("getServletName:" + servletName); // getServletName:BServlet
    }
}

ServletContext

Servlet的上下文对象。此对象一个web项目只有一个(在服务器启动的时候创建,当web项目从服务器移除或者关闭服务器时,对象被销毁)

public class CServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获得ServletContext
        ServletContext context = this.getServletContext();
        // 获取文件的MIME类型
        String mimeType = context.getMimeType("aa.txt");
        System.out.println("mimeType:" + mimeType); // mimeType:text/plain
        // 获得请求路径的工程名
        String path = context.getContextPath();
        System.out.println("ContextPath:" + path); // ContextPath:/web_test1_war_exploded
        // 获得全局初始化参数
        String username = context.getInitParameter("username");
        String password = context.getInitParameter("password");
        System.out.println("InitParameter: username:" + username + " password:" + password);
        // 获得所有初始化参数的名称
        Enumeration<String> names = context.getInitParameterNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            System.out.println("getInitParameterNames:" + name);
        }
    }
}

还可以读取项目下的文件:

// 读取web项目下的文件
InputStream inputStream = context.getResourceAsStream("/WEB-INF/classes/db.properties");

ServletContext作为域对象存取数据:(域对象内部有一个Map)

  • 存入数据:setAttribute(String name, object value)
  • 获取数据:getAttribute(String name)
  • 移除数据:removeAttribute(String name)
  • 获取所有域属性的名称:Enumeration gettributeNames()

当ServletContext对象被销毁时,数据才会失效。否则数据会一直存在。数据引用范围是整个web应用

public class ServletDemo7 extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	@Override
	public void init() throws ServletException {
		this.getServletContext().setAttribute("name", "张三");
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 获得ServletContext
		ServletContext context = getServletContext();
		String name = (String)context.getAttribute("name");
		System.out.println("姓名:" + name);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}
public class ServletDemo8 extends HttpServlet {
	private static final long serialVersionUID = 1L;
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 获得ServletContext
		ServletContext context = getServletContext();
		String name = (String)context.getAttribute("name");
		System.out.println("姓名:" + name);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

Response对象(▲)

HttpServletResponse常用API

关于响应行的方法:

  • void setStatus(int sc):设置响应的状态码

关于响应头的方法:

  • void setHeader(String name, String value):针对一个key对应一个value的情况
  • void addHeader(String name, String value):针对一个key对应多个value的情况

关于响应体的方法:

  • ServletOutputStream getOutputStream():使用字节流输出
  • PrintWriter getWriter():使用字符流输出

其他方法:

  • void sendRedirect(String location):重定向
  • void setContentType(String type):设置浏览器显示页面时,所采用的字符集
  • void setharacterEncoding(String charset):设置响应的缓冲区的字符集
  • void addCookie(Cookie cookie):服务器向浏览器回写cookie的方法
public class Servlet1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置响应状态码:重定向
        resp.setStatus(302);
        // 设置字符集
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().println("页面将于5秒后跳转");
        // 添加定时跳转
        resp.setHeader("Refresh","5;url=/web_test1_war_exploded/test2");
        // 完成重定向(302状态码和Location响应头结合使用的效果)
	// response.setHeader("Location", "/web_test/ResponseDemo2");
		
	// 或者直接设置重定向-sendRedirect
    }

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




public class Servlet2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 防止中文乱码
        resp.setContentType("text/html;charset=UTF-8"); 
        resp.getWriter().println("I'm Servlet2,欢迎光临");
    }

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

Request对象(▲)

HttpServletRequest常用API

获得客户端的信息:

  • String getMethod():获得请求方式
  • String getQueryString():获得请求路径后的参数内容
  • String getRequestURI():获得请求的路径
  • StringBuffer getRequestURL():获得请求的路径
  • 父类中:String getRemoteAddr():获得客户端的IP地址

获得请求头的方法:

  • String getHeader(String name):针对一个key对应一个value的情况
  • Enumeration getHeaders(String name):针对一个key对应多个value的情况

获得请求参数的方法:(父类里)

  • String getParameter(String name):获得提交的参数,一个名称对应一个值的情况
  • Map getParameterMap():获得提交的参数的名称和对应的值存在Map集合中
  • String[] get ParameterValues (String name) :获得提交的参数,一个名称对应多个值的情况

request作为域对象存取数据的方法:

  • void setAttribute(String name, Object o):向request域中存数据
  • Object get Attribute(String name):从 request域中取数据
  • void removeAttribute(String name):向request域中存数据
public class Servlet3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 请求方法:GET
        System.out.println("请求方法:" + req.getMethod());
        // 客户端的IP地址:0:0:0:0:0:0:0:1
        System.out.println("客户端的IP地址:" + req.getRemoteAddr());
        // 请求参数的字符串:name=aaa&pwd=bbb
        System.out.println("请求参数的字符串:" + req.getQueryString());
        // 请求路径的URL:http://localhost:8080/web_test1_war_exploded/test3
        System.out.println("请求路径的URL:" + req.getRequestURL());
        // 请求路径的URI:/web_test1_war_exploded/test3
        System.out.println("请求路径的URI:" + req.getRequestURI());
        // 获得请求头的信息
        System.out.println("请求头的信息:" + req.getHeader("User-Agent"));
    }

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

接收表单请求参数:

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 接收用户名和密码
		String username = request.getParameter("username"); 
		String password = request.getParameter("password");
		System.out.println(username + "-" + password); // hahah-1111222
		// 接收性别(单选)和籍贯(下拉列表)
		String sex = request.getParameter("sex");
		String city = request.getParameter("city");
		System.out.println("性别:" + sex); // 性别:woman
		System.out.println("籍贯:" + city); // 籍贯:shenzhen
		// 接收爱好(多选)
		String[] hobby = request.getParameterValues("hobby");
		System.out.println("爱好:" + Arrays.toString(hobby)); //  爱好:[basketball, football]
		// 接收自我介绍
		String info = request.getParameter("info"); 
		System.out.println("自我介绍:" + info); // 自我介绍:sfafasf
		
		// 获取所有参数
		Map<String, String[]> map = request.getParameterMap();
		for(String key : map.keySet()) {
			String[] value = map.get(key);
			System.out.println(key + ":" + Arrays.toString(value));
		}
	}

最佳实践

网站访问量统计

public class VisitServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        Integer count = (Integer) context.getAttribute("count");
        if (count == null) {
            context.setAttribute("count", 1);
        } else {
            context.setAttribute("count", count++);
        }

        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().println("本页面一共被访问" + count + "次");
        context.setAttribute("count", count);
    }
}