JavaWeb学习笔记(七)--Servlet入门

1. Servlet简介

Servlet是Sun公司提供的一门用于开发动态web资源的技术。

Sun公司在其API中提供了一个Servlet接口,用户若想开发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下两个步骤:

  1. 编写一个Java类,实现Servlet接口
  2. 把开发好的Java类部署到web服务器上

按照一种也定俗称的称呼习惯,通常我们也把实现了Servlet接口的Java程序,称之为Servlet。

1.1 实现第一个Servlet

通过查看Servlet API文档,可以知道所有的请求都由service方法实现,只需要实现了sevice方法就可以向浏览器输出数据。由于Servlet是个接口,我们自己创建类需要实现所有的方法,好在Sun公司已经提供了实现Servlet接口的两个类GenericServletHttpServlet,我们只需要继承这两个中的其中一个即可。

void    service(ServletRequest req, ServletResponse res) // Called by the servlet container to allow the servlet to respond to a request.

1. 在tomcat中创建一个名为firstServlet的web应用,在web应用中创建WEB-INF/classes文件夹

2. 在classes文件夹中创建FirstServlet.java

 1 package com.servlet;
 2 
 3 import java.io.*;
 4 import javax.servlet.*;
 5 
 6 public class FirstServlet extends GenericServlet {
 7     
 8     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
 9         OutputStream out =  res.getOutputStream(); // 获取输出流
10         out.write("hello servlet".getBytes()); // 向浏览器输出数据
11     }
12 }

3. 在classpath中增加Servlet API对应的jar包,编译FirstServlet.java

直接编译会报错:

设置classpath,编译成功:

1 set classpath=%classpath%;E:\Program Files\apache-tomcat-8.5.37\lib\servlet-api.jar
2 javac -d . FirstServlet.java

4. 在WEB-INF中创建web.xml,配置Servlet的对外访问路径

web.xml可是可以参考tomcat中web示例examples,拷贝头尾和Servlet配置部分即可,修改Servlet名称和对外访问路径:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 3   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
 5                       http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
 6   version="3.1"
 7   metadata-complete="true">
 8   
 9     <servlet>
10       <servlet-name>FirstServlet</servlet-name>
11       <servlet-class>com.servlet.FirstServlet</servlet-class>
12     </servlet>
13     <servlet-mapping>
14         <servlet-name>FirstServlet</servlet-name>
15         <url-pattern>/FirstServlet</url-pattern>
16     </servlet-mapping>
17     
18 </web-app>

5. 启动Tomcat,访问http://localhost:8080/firstServlet/FirstServlet,验证结果

2. Servlet调用过程和生命周期

2.1 UML描述调用过程:

2.2 Servlet运行过程:

Servlet程序是由web服务器调用,web服务器收到客户端的Servlet访问请求后:

web服务器首先检查是否已经装载并创建了该Servlet实例对象。如果是,则执行第4步,否则,执行第二步。

装载并创建该Servlet的一个实例对象。

调用Servlet实例对象的init()方法。

创建一个用于封装HTTP请求的HttpServletRequest对象和一个代表HTTP响应的HttpServletResponse对象,然后调用Servlet的service()方法,并将请求和响应对象作为参数传递进去

web应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载前调用Servlet的destroy()方法

运行图解:

 

 

3. Servlet接口实现类

Servlet接口Sun公司定义了两个默认实现类,分别是:GenericServlet, HttpServlet

HttpServlet指能够处理HTTP请求的Servlet,它在原有Servlet接口上添加了一些与HTTP协议处理的方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。

HttpServlet在实现Servlet接口时,覆写了service()方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法;如为POST请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,

而不要去覆写service方法。详细信息可查看Servlet API文档

4. 使用IDEA开发Servlet

工欲善其事,必先利其器。开发web应用时,如果每次都是我们手动创建web应用目录,很麻烦,又浪费时间。我们需要一款IDE去帮助我们跳过这个步骤,推荐使用IDEA 专业版。

1. 在IDEA中新建一个java web工程(一般选择Java Enterprise),IDEA会自动创建如下目录结构:

2. 配置Tomcat

一般情况下IDEA已经把Tomcat已经配置好了,项目直接就可以运行。如果没有配置就需要手动添加:

1).点击菜单中Run-> Edit Configurations

2) 修改Tomcat名称(不改也没关系,好看而已),增加war包,配置浏览器打开路径,和web应用映射路径    

3) 启动Tomcat(点击右上角的启动图标--右三角),验证结果

3. 创建Servlet

1)在src目录下增加Servlet

2)配置Servlet包路径,并给Servlet起名

3)补充Servlet代码

在doGet方法中向浏览器输出"Hello Servlet"

1  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
2         response.getOutputStream().write("Hello Servlet!!!".getBytes());
3     }

4)在web.xml中配置Servlet的对外访问路径

 1  <!--
 2         映射的顺序: 浏览器获取到url,匹配到url-pattern(例如:/bbb),然后根据servlet-name去找我们的servlet-class
 3         所以servlet-name可以随便取,只要能映射上即可,但是习惯上还是和Servlet的类名保持一一致
 4     -->
 5     <servlet>
 6         <servlet-name>xxx</servlet-name>
 7         <servlet-class>com.servlet.ServletDemo</servlet-class>
 8     </servlet>
 9     
10     <servlet-mapping>
11         <servlet-name>xxx</servlet-name>
12         <url-pattern>/bbb</url-pattern>
13     </servlet-mapping>

5)打开浏览器,访问 http://localhost:8080/first/bbb,验证结果:

5. Servlet开发的一些重要细节

5.1 Servlet和URL的映射

由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把Servlet程序映射到一个URL地址上,这个工作在web.xml中使用<servlet>元素和<servlet-mapping>元素完成。

<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名

一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:</servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。例如:

<servlet>
    <servlet-name>AnyName</servlet-name>
    <servlet-class>com.servlet.ServletDemo</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>AnyName</servlet-name>
    <url-pattern>/index.html</url-pattern>
</servlet-mapping>

5.1.1 Servlet映射多个URL

同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的值可以是同一个Servlet的注册名。例如:

通过/ServletDemo、/servlet/helloServlet、/hello.html都可以访问到ServletDemo。

<servlet>
    <servlet-name>ServletDemo</servlet-name>
    <servlet-class>com.servlet.ServletDemo</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ServletDemo</servlet-name>
    <url-pattern>/ServletDemo</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>ServletDemo</servlet-name>
    <url-pattern>/servlet/helloServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>ServletDemo</servlet-name>
    <url-pattern>/hello.html</url-pattern>
</servlet-mapping>

5.1.2 URL使用*通配符

在Servlet映射到的URL中也可以使用*通配符,但是只有两种固定的格式:一种格式是“*.扩展名”,另一种格式是以正斜杠(/)开头并以“/*”结尾。例如:

<servlet-mapping>
    <servlet-name>ServletDemo</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>ServletDemo</servlet-name>
    <url-pattern>/action/*</url-pattern>
</servlet-mapping>

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

如果我们定义了如下一些映射关系:

  • Servlet1 映射到 /abc/*
  • Servlet2 映射到 /*
  • Servlet3 映射到 /abc
  • Servlet4 映射到 *.do

问题:

  • 当请求URL为“/abc/a.html”,“/abc/*” 和 “/*” 都匹配,哪个Servlet响应? Servlet1
  • 当请求URL为“/abc”,“/abc/*” 和 “/abc” 都匹配,哪个Servlet响应?  Servlet3
  • 当请求URL为“/abc/a.do”,“/abc/*” 和 “*.do” 都匹配,哪个Servlet响应? Servlet1
  • 当请求URL为“/a.do”,“/*” 和 “*.do” 都匹配,哪个Servlet响应? Servlet2
  • 当请求URL为“/xxx/yyy/a.do”, “/*” 和 “*.do”都匹配,哪个Servlet响应? Servlet2

5.1.3 配置缺省Servlet

如果某个Servlet的映射路径仅为一个正斜杠(/),那么这个Servlet就成为当前web应用程序的缺省Servlet。

凡是在web.xml中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省的Servlet处理,也就是说,缺省的Servlet用于处理其他Servlet都不处理的访问请求。

在Tomcat安装目录conf/web.xml中,注册了一个名为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个设置为了缺省Servlet。

当访问Tomcat服务器的某个静态的HTML文件和图片时,实际上在访问这个缺省的Servlet(所以一般情况下我们不会自己去写缺省的Servlet,否则web应用的静态资源就无法访问了)

5.2 Servlet生命周期和调用方式

Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。

针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其他请求服务,直至web容器退出,Servlet实例对象才会销毁。

在Servlet的整个生命周期过程中,Servlet的init()方法只会被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次Servlet的service()方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给调用它的Servlet的service()方法,service()方法再根据请求方式分别调用doXXX方法。

如果在<servlet>元素中配置一个<load-on-startup>元素,那么Web应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。<load-on-startup>元素的值(正整数)表示Servlet加载的顺序,值越小优先级越高。例如:

<servlet>
    <servlet-name>ServletDemo</servlet-name>
    <servlet-class>com.servlet.ServletDemo</servlet-class>

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

<servlet>
    <servlet-name>ServletDemo2</servlet-name>
    <servlet-class>com.servlet.ServletDemo2</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>ServletDemo2</servlet-name>
    <url-pattern>/ServletDemo2</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>ServletDemo1</servlet-name>
    <servlet-class>com.servlet.ServletDemo1</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/ServletDemo1</url-pattern>
</servlet-mapping>

可以看到配置<load-on-startup>元素的ServletDemo1、ServletDemo2在服务器启动的时候就调用了init方法,并且ServletDemo1优先加载,而ServletDemo在浏览器访问其URL映射时,才调用其init方法。

5.3 Servlet线程安全问题

Servlet是单实例对象,对于类实例变量,所有线程共享实例变量。当多个线程对共享资源同时访问就可能引发线程安全问题。

解决方案:使用局部变量,例如:

 1 public class ServletDemo5 extends HttpServlet {
 2     private  int classVariable = 0;
 3     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 4 
 5     }
 6 
 7     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 8         int localVariable = 0;
 9         try {
10             for (int i = 0; i < 1000; i++) {
11                 classVariable++;
12                 localVariable++;
13                 Thread.sleep(50);
14             }
15 
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         }
19         String outPut = "classVariable: " + classVariable + ", localVariable: " + localVariable;
20         response.getOutputStream().write(outPut.getBytes());
21     }
22 }

启动3个浏览器分别访问:

类实例变量classVariable不是期望的值1000,局部变量localVariable是期望的值1000。

posted @ 2019-02-26 20:52  暴躁的毛毛熊  阅读(252)  评论(0编辑  收藏  举报