Java Servlet学习总结

3 篇文章 0 订阅

从本篇博客开始,后续几篇文章会重点学习Java中另一个重量级的高级特性Servlet。

1、 Servlet概念及如何创建Servlet
(1)Servlet概念
Servlet是J2SE中的一个类,在启用 Java 的 Web 服务器上或应用服务器上运行并扩展了服务器的能力。Servlet和用户的通信采用请求/响应模式,用于以动态响应客户机请求形式扩展Web服务器的功能。。虽然servlet可以对任何类型的请求产生响应,但通常只用来扩展Web服务器的应用程序。

(2)Servlet的运行流程
这里写图片描述
1)客户端发送请求至服务器端;
2)服务端根据客户端的URL,再结合配置文件web.xml中Servlet的映射关系,将客户端的请求转发给相应的Servlet;
3)Servlet引擎调用Service()方法,然后根据HTTP请求报文方法,将请求转到doGet()或者doPost()中动态处理,产生客户端需要的数据,并封装到应答Response具体的实例中;
4)服务器端将应答报文沿原路返回给客户端。

(3)利用Eclipse创建一个Servlet
构造一个Web工程,然后新建一个Servlet

package com.wygu.servlet;

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

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

public class FirstServlet extends HttpServlet{

    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)  
            throws ServletException, IOException { 
        String strResponse = "Hello World!";
         System.out.println("发送客户端的应答报文为:"+strResponse);
         //向response返回应答报文
         PrintWriter out = response.getWriter();
         out.print(strResponse);
         //刷新缓冲流,使其立即发送
         out.flush();
         out.close();  
    }
}

新建web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <servlet>  
        <servlet-name>FirstServlet</servlet-name>  
        <servlet-class>com.wygu.servlet.FirstServlet</servlet-class>  
    </servlet>  

    <servlet-mapping>  
        <servlet-name>FirstServlet</servlet-name>  
        <url-pattern>/FirstServlet</url-pattern>  
    </servlet-mapping>   
</web-app>

程序运行后,在浏览器输入:http://localhost:8080/first-project/FirstServlet,结果为:
这里写图片描述

2、 Servlet的URL访问及两种特殊Servlet

(1)Servlet的URL匹配
在上面的例子web.xml中 …标签声明一个Servlet,包括Servlet的类名及对应的包名。
客户端发送请求至Servlet容器时,容器会去掉当前应用的上下文(工程名),利用剩下的作为Servlet的映射关系映射到具体的Servlet上。比如:上述URL地址:http://localhost:8080/first-project/FirstServlet,去掉上下文后FirstServlet映射到对应真实的FirstServlet上。
显然如果存在一对多(一个真实的Servlet存在多个映射关系),容器按照什么样的规则匹配,下面观察常见的有以下几种匹配规则:
1)精确匹配
精确匹配要求url必须和的配置项完全匹配

    <servlet-mapping>  
        <servlet-name>FirstServlet</servlet-name>  
        <url-pattern>/FirstServlet</url-pattern>  
        <url-pattern>/aaa</url-pattern>  
        <url-pattern>/bbb</url-pattern>  
    </servlet-mapping>  

在浏览器中输入以下URL都会被匹配:
http://localhost:8080/first-project/bbb
http://localhost:8080/first-project/aaa
可以正常加载页面
但是如果输入:http://localhost:8080/first-project/aac出现匹配失败

3)模糊匹配
如果按照如下方式添加配置项:

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

在浏览器中输入以下URL都会被匹配:
http://localhost:8080/first-project/hello/
http://localhost:8080/first-project/hello/world
http://localhost:8080/first-project/hello/kitty
或者按照下述配置:

    <servlet-mapping>  
        <servlet-name>FirstServlet</servlet-name>  
        <url-pattern>/FirstServlet</url-pattern>  
        <url-pattern>*.jsp</url-pattern>  
    </servlet-mapping>  

在浏览器中输入以下URL都会被匹配:
http://localhost:8080/first-project/hello.jsp
http://localhost:8080/first-project/hello/world.jsp
但是http://localhost:8080/first-project/hello.jspa就不匹配

(2)两种特殊的Servlet
1)默认Servlet
如果将写成如下形式:

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

我们把上述FirstServlet称为默认Servlet,可以匹配任一种URL(上下文相同),比如:
http://localhost:8080/first-project/
http://localhost:8080/first-project/hello
http://localhost:8080/first-project/dog/cat

2)自启动Servlet
如果将web.xml中的配置项加入1可以使用部署应用到容器时会自动加载该Servlet:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <servlet>  
        <servlet-name>FirstServlet</servlet-name>  
        <servlet-class>com.wygu.servlet.FirstServlet</servlet-class>  
            <!-- 部署应用时,自动加载 --> 
        <load-on-startup>1</load-on-startup> 
    </servlet>  

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

</web-app>

然后在FirstServlet中加入init()方法(在Servlet的生命周期中,仅执行一次init()方法)

public void init(){
        System.out.println("自启动Servlet......");
    }

在启动tomcat时,程序输出:自启动Servlet……
value,参数value默认值为负数,在加载web应用时,servlet实例不会被创建和加载,而在客户端第一次请求时被创建并被加载;若参数value为0或者正数,则在启动web应用时,servlet实例会被创建,且值越小越早被创建。

我们可以利用这种方式做一些预处理的操作,比如构造数据库连接池,加载数据库配置参数,建立和其它系统的socket连接等。

3、 Servlet的生命周期
Servlet生命周期如下图:
这里写图片描述
(1)实例化
在以下情况发生时,Web容器会创建Servlet实例
1)Servlet为自启动Servlet,即在web.xml中Servlet配置项被设置为:value ,其中value值为正数或0;
2)Servlet类文件被更新,容器重新加载;
3)Web容器启动后,客户端首次发送Servlet请求。

(2)初始化
Servlet一旦被创建后,容器会调用Servlet的init(ServletConfig config)方法,进行部分初始化的工作(比如连接数据库)。注意:在Servlet整个生命周期中init()只会被调用一次。
(3)服务
客户端发送请求到Web容器后,Servlet调用service(ServletRequest req, ServletResponse res)方法响应请求,然后根据Servlet的请求方式(GET,POST)调用不同的服务方法doGet(),doPost()等。

HttpServlet继承自抽象类GenericServlet,GenericServlet继承了接口Servlet的方法。但是Servlet和GenericServlet不特定于任何协议的,而HttpServlet是特定于HTTP协议的类,因此在HttpServlet中需要实现service()方法,将请求的ServletRequest,ServletResponse强转为HttpRequest和HttpResponse。

 /*     */   public void service(ServletRequest req, ServletResponse res)
/*     */     throws ServletException, IOException
/*     */   {
/*     */     try
/*     */     {
/* 724 */       HttpServletRequest request = (HttpServletRequest)req;
/* 725 */       response = (HttpServletResponse)res;
/*     */     } catch (ClassCastException e) { HttpServletResponse response;
/* 727 */       throw new ServletException("non-HTTP request or response"); }
/*     */     HttpServletResponse response;
/* 729 */     HttpServletRequest request; service(request, response);
/*     */   }
/*     */ }

在HttpServlet中重写service(HttpServletRequest req, HttpServletResponse resp)方法,然后根据请求方式调用doXXX()方法:

/*     */   protected void service(HttpServletRequest req, HttpServletResponse resp)
/*     */     throws ServletException, IOException
/*     */   {
/* 615 */     String method = req.getMethod();
/*     */     
/* 617 */     if (method.equals("GET")) {
/* 618 */       long lastModified = getLastModified(req);
/* 619 */       if (lastModified == -1L)
/*     */       {
/*     */ 
/* 622 */         doGet(req, resp);
/*     */       } else {
/*     */         long ifModifiedSince;
/*     */         try {
/* 626 */           ifModifiedSince = req.getDateHeader("If-Modified-Since");
/*     */         } catch (IllegalArgumentException iae) {
/*     */           long ifModifiedSince;
/* 629 */           ifModifiedSince = -1L;
/*     */         }
/* 631 */         if (ifModifiedSince < lastModified / 1000L * 1000L)
/*     */         {
/*     */ 
/*     */ 
/* 635 */           maybeSetLastModified(resp, lastModified);
/* 636 */           doGet(req, resp);
/*     */         } else {
/* 638 */           resp.setStatus(304);
/*     */         }
/*     */       }
/*     */     }
/* 642 */     else if (method.equals("HEAD")) {
/* 643 */       long lastModified = getLastModified(req);
/* 644 */       maybeSetLastModified(resp, lastModified);
/* 645 */       doHead(req, resp);
/*     */     }
/* 647 */     else if (method.equals("POST")) {
/* 648 */       doPost(req, resp);
/*     */     }
/* 650 */     else if (method.equals("PUT")) {
/* 651 */       doPut(req, resp);
/*     */     }
/* 653 */     else if (method.equals("DELETE")) {
/* 654 */       doDelete(req, resp);
/*     */     }
/* 656 */     else if (method.equals("OPTIONS")) {
/* 657 */       doOptions(req, resp);
/*     */     }
/* 659 */     else if (method.equals("TRACE")) {
/* 660 */       doTrace(req, resp);
/*     */ 
/*     */ 
/*     */     }
/*     */     else
/*     */     {
/*     */ 
/*     */ 
/* 668 */       String errMsg = lStrings.getString("http.method_not_implemented");
/* 669 */       Object[] errArgs = new Object[1];
/* 670 */       errArgs[0] = method;
/* 671 */       errMsg = MessageFormat.format(errMsg, errArgs);
/*     */       
/* 673 */       resp.sendError(501, errMsg);
/*     */     }
/*     */   }

(4)销毁
当Servlet容器终止运行,或者Servlet容器重新装载Servlet实例时,容器会调用Servlet的destroy()方法,在destroy()方法中释放掉Servlet所占用的资源。

4、 Servlet的线程安全问题
容器启动时,会创建一个线程池来服务请求,其中容器中一个调度线程负责管理线程池。每个客户端发送请求到容器时,调度线程会为该请求分配一个线程,并且创建一组新的Request和Respose实例。

通过上一节的介绍,我们知道在整个Servlet生命周期中,容器只会在处理客户端第一次请求时,创建Servlet实例,后面的请求会复用该实例——单例模式的懒汉模式。因而自定义Servlet类(继承HTTPServlet)中如果定义了一些成员变量会导致不同的线程间共享这些成员变量,导致出现线程不安全的问题。

创建服务端ServletSafty:

package com.wygu.servlet;

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

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

import com.wygu.model.Bank;

public class ServletSafty extends HttpServlet{

    private static final long serialVersionUID = 1L;

    private Bank bank = null;

    public void init(ServletConfig config){
        bank = new Bank(1000);
        System.out.println("银行初始化资金为1000");
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)  
            throws ServletException, IOException { 
        //设置请求的编码格式
        request.setCharacterEncoding("UTF-8");
        //获得HTTP请求参数中的内容
        String money = request.getParameter("money");
        int restMoney = bank.withdrawMoney(Integer.parseInt(money));
        System.out.println("rest money is:"+restMoney);
        PrintWriter out = response.getWriter();
        out.print(String.valueOf(restMoney));
        //刷新缓冲流,使其立即发送
        out.flush();
        out.close();  
    }
}

定义共享成员变量类Bank:

package com.wygu.model;

public class Bank {

    private int totalMoney;

    public Bank(int totalMoney){
        this.setTotalMoney(totalMoney);
    }

    public int withdrawMoney(int payMoney){
        if(payMoney<0){
            return -1;
        }else if(payMoney>this.getTotalMoney()){
            return -2;
        }else{
            int restMoney = this.getTotalMoney()-payMoney;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setTotalMoney(restMoney);
        }
        return this.getTotalMoney();

    }

    public int getTotalMoney() {
        return totalMoney;
    }

    public void setTotalMoney(int totalMoney) {
        this.totalMoney = totalMoney;
    }

}

利用Jemeter,1秒内发送10次客户端请求,程序运行结果为:
银行初始化资金为1000
rest money is:950
rest money is:950
rest money is:900
rest money is:850
rest money is:850
rest money is:800
rest money is:750
rest money is:750
rest money is:700
rest money is:700

首先上述程序验证了:(1)Servlet只会在第一次客户端请求时被创建,(2)init()方法在整个Servlet生命周期只被调用一次,(3)多个请求构造的多线程共享成员变量bank。然而程序运行的结果出现了剩余金额相同的情况,出现了线程不安全问题。

根据前面的博客知道,要想解决多线程运行的不安全问题,可以加入关键字synchronized 修饰类Bank中的方法withdrawMoney为,volatile 修饰成员变量totalMoney,保证可见性:

private volatile int totalMoney;
public int withdrawMoney(int payMoney){
        synchronized (this) {
            if(payMoney<0){
                return -1;
            }else if(payMoney>this.getTotalMoney()){
                return -2;
            }else{
                int restMoney = this.getTotalMoney()-payMoney;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.setTotalMoney(restMoney);
            }
            return this.getTotalMoney();
        }
    }

程序运行结果为:
银行初始化资金为1000
rest money is:950
rest money is:900
rest money is:850
rest money is:800
rest money is:750
rest money is:700
rest money is:650
rest money is:600
rest money is:550

**注意:**Servlet的线程安全性主要是由Servlet实例的成员变量引起的,因而在实际开发中尽量避免使用成员变量。如果无法避免时,可以使用多线程的同步机制确保线程的安全性。

总结:本篇博客从Servlet的概念,第一个Servlet实例,Servlet的访问控制,常见的两种特殊Servlet,Servlet的生命周期,Servlet的线程安全性方面深入的剖析的Servlet。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值