servlet入门教程

146 阅读6分钟

一、什么是Servlet?

Servlet 是 Java Web 应用中最核心的组件

Servlet 运行在 Servlet 容器中,能够为各种各样的客户请求提供相应服务。Servlet 能够轻而易举地完成以下任务:

  • 动态生成 HTML 文档
  • 把请求转发给同一个 Web 应用中的其他 Servlet 组件
  • 把请求转发给其他 Web 应用中的 Servlet 组件
  • 读取客户端的 Cookie,以及向客户端写入 Cookie
  • 访问其他服务器资源(如数据库或基于 Java 的应用程序)

Servlet 之所以有如此高的本领,主要有两个原因:

  • Servlet 是用 Java 语言编写出来的类,只要开发人员有着深厚的 Java 编程功底,就可以编写出各类复杂的 Servlet
  • Servlet 对象由 Servlet 容器缔造,能驾轻就熟地调用容器中的各种资源

但Servlet主用作用是来处理前端发来的请求,实现前后端分离。 Servlet只是古老的CGI技术的替代品,直接使用Servle开发还是很麻烦,所以Java后来又对Servlet 进行了升级,推出了 JSP 技术。JSP 只是对 Servlet 加了一层壳,JSP 经过编译后还是 Servlet。

二、Servlet开发流程

从狭义上来说,servlet是servlet是java语言实现的一个类,所以我们就要根据这个类进行相应的扩展开发。 开发流程如下:

  1. 编写一个java类,继承自HttpServlet类
  2. 重写HttpServlet类的doGet方法和doPost方法
  3. 配置web.xml文件,或者使用注解对servlet进行配置

三、Servlet入门实例

  1. 创建一个JavaWeb项目,并创建一个servlet类-HelloServlet,实现接口httpServlet。
package com.weiliu.servlet;
import jakarta.servlet.ServletException;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServlet;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;

/**

* @author liuwei

* @date 2021/10/1

* @apiNote

*/

public class FileServlet extends HttpServlet {

@Override

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

//String realpath = this.getServletContext().getRealPath("/1.png");

String realpath = "/Users/liuwei/IDEProjects/java-web-03/servlet-01/src/main/resources/1.png";

System.out.println("下载文件的路径:"+realpath);

String fileName = realpath.substring(realpath.lastIndexOf('/')+1);

resp.setHeader("Content-Disposition","attachment;filename="+fileName);

FileInputStream in = new FileInputStream(realpath);

int len = 0;

byte[] buffer = new byte[1024];

ServletOutputStream out = resp.getOutputStream();

while((len=in.read(buffer)) != -1){

out.write(buffer,0,len);

}

in.close();

out.close();

}

}

继承HTTPServlet类,并重写doGet和doPOST方法,也就是根据请求的方式是get还是post,然后用不同的方式来处理请求。

  1. 在web.xml文件中配置上述Servlet的映射关系
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0"
         metadata-complete="true">
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.weiliu.servlet.helloservlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>

  <servlet>
    <servlet-name>url</servlet-name>
    <servlet-class>com.weiliu.servlet.ServletDemo3</servlet-class>
  </servlet>

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

  <servlet>
    <servlet-name>demo4</servlet-name>
    <servlet-class>com.weiliu.servlet.ServletDemo4</servlet-class>
  </servlet>

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

  <servlet>
    <servlet-name>fileservlet</servlet-name>
    <servlet-class>com.weiliu.servlet.FileServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>fileservlet</servlet-name>
    <url-pattern>/file</url-pattern>
  </servlet-mapping>

  <servlet>
    <servlet-name>Image</servlet-name>
    <servlet-class>com.weiliu.servlet.ImageServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Image</servlet-name>
    <url-pattern>/image</url-pattern>
  </servlet-mapping>

  <servlet>
    <servlet-name>cookie</servlet-name>
    <servlet-class>com.weiliu.servlet.CookieDemo01</servlet-class>
  </servlet>

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

  <servlet>
    <servlet-name>session</servlet-name>
    <servlet-class>com.weiliu.servlet.SessionDemo01</servlet-class>
  </servlet>

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

  <context-param>
    <param-name>url</param-name>
    <param-value>www.weiliu.com</param-value>
  </context-param>

  <session-config>
    <session-timeout>15</session-timeout>
  </session-config>

</web-app>

首先配置,输入name和servlet文件的路径,然后配置映射,将servlet文件映射到指定路径,访问的时候直接访问服务器上的该路径就可以了。

image.png

访问不同的路径,即可访问相应的页面。

四、servlet生命周期

我们知道,HttpServlet是对Servlet类的封装,而Servlet类在第一次装载并创建的时候,会调用init()方法,后面每次都是调用service()方法,调用流程图如下所示:

image.png

上面这幅图可以这样理解:

1、客户端向 Web 服务器发送请求,服务器查询 web.xml 文件配置。根据请求信息找到对应的 Servlet。

2、Servlet 引擎检查是否已经装载并创建了该 Servlet 的实例对象,如果有,则直接执行第4步,否则执行第3步,

3、Web 服务器加载 Servlet,并调用 Servlet 构造器(只会调用一次),创建 Servlet 的实例对象。并调用 init() 方法,完成 Servlet 实例对象的初始化(只会调用一次)。

4、Web 服务器把接收到的 http 请求封装成 ServletRequest 对象,并创建一个 响应消息的 ServletResponse 对象,作为 service() 方法的参数传入。(每一次访问都会调用一次该方法)

5、执行 service() 方法,并将处理信息封装到 ServletResponse 对象中返回

6、浏览器拆除 ServletResponse 对象,形成 http 响应格式,返回给客户端。

7、Web 应用程序停止或者重新启动之前,Servlet 引擎将卸载 Servlet实例,并在卸载之前调用 destory() 方法

五、创建Servlet的三种方法

  1. 就是我们上面写的继承HttpServlet类
  2. 实现最初的接口Servlet,Servlet接口定义了5个方法:
方法相关说明
init(ServletConfig config)该方法负责初始化 Servlet 对象;容器在创建好 Servlet 对象后,就会调用该方法
service(ServletRequest req, ServletResponse req)负责响应客户端的请求,为客户端提供相应的服务;容器接收到客户端要求访问特定 Servlet 对象的请求时,就会调用该 Servlet 对象的 service() 方法
destroy()负责释放 Servlet 对象占用的资源;当 Servlet 对象结束生命周期时,容器会调用此方法
getServletConfig()返回一个包含 Servlet 初始化参数信息的 ServletConfig 对象
getServletInfo()返回包含 Servlet 的创建者、版本和版权等信息的 String 对象
  1. 由于实现接口我们需要实现里面所有的方法,里面有一些方法我们可能并不想实现,那么我们就继承 GenericServlet 类

其实上述三种方法,其他两种都是对Servlet接口的封装,GenericServlet实现了Servlet接口,而HttpServlet继承自GenericServlet类。

六、Servlet的多线程问题

我们通过 Servlet 的生命周期可以知道,Servlet 类的构造器只会在第一次访问的时候调用,后面的请求都不会再重新创建 Servlet 实例。即 Servlet 是单例,那么既然是单例的,那就要注意多线程访问所造成的安全问题。如下:

package com.ys.servlet;

import java.io.IOException;

import javax.servlet.GenericServlet;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class HelloServlet extends GenericServlet{

//多线程共享资源

private int i = 0;

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

i++;

//为了使多线程访问安全问题更加突出,我们增加一个延时程序

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(i);

}

}

我们用两个浏览器,输入 http://localhost:8080/ServletImprove/hello,然后一起访问,不断刷新,结果如下:

image.png

结果分析:显然,我们用两个浏览器访问,便相当于两个线程,第一个访问,已经执行了 i++,但是还没来得及打印 i 的值,就马上就睡眠了;接着第二个浏览也来访问,执行 i++,那么i的值相当于增加加了两次1,然后这两个浏览器输出最终结果。这便造成了多线程访问共享资源造成冲突。那么如何解决多线程冲突呢?

这个在Servlet中如何处理呢?

  1. 把使用到共享数据的代码块进行同步(使用synchronized关键字进行同步)
  2. 建议在servlet类中尽量不要使用成员变量。