Servlet技术

18 篇文章 0 订阅

Servlet简介

• Java Servlet是和平台无关的服务器端组件,它运行在Servlet容器中。Servlet容器负责Servlet和客户的通信以及调用Servlet的方法,Servlet和客户的通信采用“请求/响应”的模式。
• Servlet可完成如下功能:
– 创建并返回基于客户请求的动态HTML页面。
– 创建可嵌入到现有 HTML 页面中的部分 HTML 页面(HTML 片段)。
与其它服务器资源(如数据库或基于Java的应用程序)进行通信。

Servlet API

• Servlet的框架是由两个Java包组成:
– javax.servlet包:定义了所有的Servlet类都必须实现或扩展的通用接口和类。
– javax.servlet.http 包 : 定 义 了 采 用 HTTP 协 议 通 信 的HttpServlet类。

package learn.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class ServiceServlet
 */
public class ServiceServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	String username = request.getParameter("username");
    	String password = request.getParameter("password");
    	
    	System.out.println(username + "=" + password);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	System.out.println("doGet");
    	String username = request.getParameter("username");
    	String password = request.getParameter("password");
    	
    	System.out.println(username + "=" + password);
    }
}

结果是:
null=null
我们可以发现当我们重写service时程序就不默认启动doget方法了,而是启用service方法

每一个 Servlet 都必须要实现 Servlet 接口,GenericServlet 是个通用的、不特定于任何协议的 Servlet,它实现了 Servlet 接口,而HttpServlet继承于GenericServlet,因此HttpServlet也实现了Servlet接口,所以我们定义的 Servlet 只需要继承 HttpServlet 父类即可。

Servlet 接口中定义了一个 service 方法,HttpServlet 对该方法进行了实现,实现方式就是将 ServletRequest 与 ServletResponse 转换为HttpServletRequest 与 HttpServletResponse
源码:

@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);
    }

转换完毕后,会调用 HttpServlet 类中自己定义的 service 方法,如下所示

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

在该 service 方法中,首先获得到请求的方法名,然后根据方法名调用对应的 doXXX 方法,比如说请求方法为 GET,那么就去调用 doGet方法;请求方法为 POST,那么就去调用 doPost 方法。

 String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }

在 HttpServlet 类中所提供的 doGet、doPost 等方法都是直接返回错误信息,所以我们需要在自己定义的 Servlet 类中 override 这些方法

 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

源码面前,了无秘密

• Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这一接口。在Servlet接口 中 定 义 了 五 个 方 法 , 其 中 有 三 个 方 法 代 表 了Servlet的生命周期:
– init方法:负责初始化Servlet对象;
– service方法:负责响应客户的请求;
– destroy方法:当Servlet对象退出生命周期时,负责释放占用的资源。
在这里插入图片描述
如果你的Servlet类扩展了HttpServlet类,你通常不必实现service方法,因为HttpServlet类已经实现了service方法,
该方法的声明形式如下:
protected void service(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException;
• 在 HttpServlet 的 service 方 法 中 , 首先从HttpServletRequest对象中获取HTTP请求方式的信息,然后再根据请求方式调用相应的方法。例如:如果请求方式为GET,那么调用doGet方法;如果请求方式为POST,那么调用doPost方法。

ServletRequest接口

  1. ServletRequest接口中封装了客户请求信息,如客户请求方式、参数名和参数值、客户端正在使用的协议,以及发出客户请求的远程主机信息等 。ServletRequest接口还为Servlet提供了直接以二进制方式读取客户请求数据流的ServletInputStream。
  2. ServletRequest的子类可以为Servlet提供更多的和特定协议相关的数据. 例如: HttpServletRequest 提供了读取HTTP Head信息的方法。

ServletRequest接口中常用的方法

• getAttribute 根据参数给定的属性名返回属性值
• getContentType 返回客户请求数据MIME类型
• getInputStream 返回以二进制方式直接读取客户请求数据的输入流
• getParameter 根据给定的参数名返回参数值
• getRemoteAddr 返回远程客户主机的IP地址
• getRemoteHost 返回远程客户主机名
• getRemotePort 返回远程客户主机的端口

ServletResponse接口

• ServletResponse 接口为Servlet提供了返回响应结果的方法。它允许Servlet设置返回数据的长度和MIME类型, 并且提供输出流ServletOutputStream。
• ServletResponse子类可以提供更多和特定协议相关的方法。例如: HttpServletResponse 提供设定HTTP HEAD信息的方法。

ServletResponse接口的主要方法

• getOutputStream 返回可以向客户端发送二进制数据的输出流对象ServletOutputStream
• getWriter 返回可以向客户端发送字符数据的PrintWriter对象
• getCharacterEncoding 返回Servlet发送的响应数据的字符编码
• getContentType 返回Servlet发送的响应数据的MIME类型
• setContentType 设置Servlet发送的响应数据的MIME类型

Servlet的生命周期

• Servlet 的生命周期可以分为三个阶段:
– 初始化阶段
– 响应客户请求阶段
– 终止阶段
• 在javax.servlet.Servlet接口中定义了三个方法init(), service(), destroy(),它们将分别 在 Servlet 的 不 同 阶 段 被 调 用 。

Servlet的初始化阶段

• 在下列时刻Servlet容器装载Servlet:
– Servlet容器启动时自动装载某些Servlet
– 在Servlet容器启动后,客户首次向 Servlet 发出请求
– Servlet的类文件被更新后,重新装载Servlet

• Servlet被装载后,Servlet容器创建一个 Servlet 实例并且调用 Servlet 的 init()方法进行初始化。在Servlet的整个生命周期中,init方法只会被调用一次。

package learn.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 InitServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    @Override
    public void init(ServletConfig config) throws ServletException {
    	System.out.println("init invoked!");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    	System.out.println("doGet invoked!");
    }
}

刷新几次结果是:
init invoked!
doGet invoked!
doGet invoked!
doGet invoked!
doGet invoked!
doGet invoked!
doGet invoked!
重启或修改后结果是:
init invoked!
doGet invoked!
init invoked!

在web.xml中加入代码里面的数字随意,系统会按数字顺序启动,如果23前面没有数字,那么23这个程序先启动

<servlet>
		<description>
		</description>
		<display-name>InitServlet</display-name>
		<servlet-name>InitServlet</servlet-name>
		<servlet-class>learn.servlet.InitServlet</servlet-class>
		<load-on-startup>23</load-on-startup>
	</servlet>

Servlet的响应客户请求阶段

• 对于到达Servlet容器的客户请求,Servlet容器创建特定于这个请求的ServletRequest对象和ServletResponse对象,然后调用 Servlet 的service方法。service方法从ServletRequest对象获得客户请求信息、处理该请求,并通过ServletResponse对象向客户返回响应结果。

Servlet的终止阶段

• 当Web应用被终止,或Servlet容器终止运行,或Servlet容器重新装载Servlet的新实例时,Servlet容器会先调用 Servlet的destroy方法。在destroy方法中,可以释放Servlet所占用的资源。

Tomcat请求参数源代码深度解析

  1. 对于 Tomcat 来说,它会将客户传递过来的参数放在一个 Hashtable中,该 Hashtable 的定义
    在这里插入图片描述
    这是一个 String->String[]的键值映射。
  2. 某 些 Servlet 在 web.xml 文 件 中 只 有 < servlet> 元 素 而 没 有< servlet-mapping>元素,这样我们就无法通过 url 地址的方式访问这个 Servlet 了,这种 Servlet 通常会在< servlet>元素中配置一个< load-on-startup>子元素,让容器在启动的时候自动加载该 Servlet,并且调用其 init 方法完成一些全局性的初始化工作。

Web应用何时会被启动

  1. 当Servlet容器启动时,会启动所有的Web应用
  2. 通过控制台启动Web应用
  3. apache-tomcat-9.0.11\conf\tomcat-users.xml找到这个文件根据文档要求,创建管理员的用户名和密码,然后登录
    在这里插入图片描述
    登录后的界面
    在这里插入图片描述
    这就是tomcat的后台管理
    在这里插入图片描述
    接下来我们可以将eclipse里我们自己写的项目部署到这里面来,因为我是用eclipse复制tomcat到一个新目录下,所以这个manager看不到我之前写的程序,现在我给它导进来,首先生成war文件,右键项目,export,war file
    在这里插入图片描述
    然后点击在这里插入图片描述
    在这里插入图片描述
    部署成功,我们看看我们的webapps文件夹
    在这里插入图片描述
    点击Undeploy是卸载
    在这里插入图片描述
    我们再看一下webapps
    在这里插入图片描述

ServletContext和Web应用关系

当Servlet容器启动Web应用时,并为每个Web应用创建唯一的ServletContext对象。你可以把ServletContext看成是一个Web应用的服务器端组件的共享内存。在ServletContext中可以存放共享数据,它提供了读取或设置共享数据的方法:
– setAttribute(String name,Object object)把一个对象和一个属性名绑定,将这个对象存储在ServletContext中。
– getAttribute(String name)根据给定的属性名返回所绑定的对象

package learn.servlet;

import java.io.IOException;

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


public class CounterServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		ServletContext context = request.getSession().getServletContext();
		
		if(null == context.getAttribute("counter")){
			context.setAttribute("counter", 1);
		}
		else {
			int counter = (Integer)context.getAttribute("counter");
			
			context.setAttribute("counter", counter + 1);
		}
		
		request.getRequestDispatcher("counter.jsp").forward(request, response);
	}

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

}

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
计数器:<%= application.getAttribute("counter") %>
</body>
</html>

在这里插入图片描述

Servlet的多线程同步问题(重要)

• Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。
• 由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的同步问题。
• 如果在编写Servlet/JSP程序时不注意到多线程同步的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题,对于这类随机性的问题调试难度也很大。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="HelloServlet">
username:<input type="text" name="username">
<br>
<input type="submit" value= "submit">
</form>
</body>
</html>
package learn.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
    private String username;   
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		this.username = request.getParameter("username");
		
		//进行一些后端的业务处理
		try {
			Thread.sleep(10000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		request.setAttribute("username", this.username);
		request.getRequestDispatcher("hello.jsp").forward(request, response);
	}
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
username:<%= request.getAttribute("username") %>
</body>
</html>

模拟两个用户访问:
在这里插入图片描述
先提交zhangsan然后提交lisi结果是:
在这里插入图片描述
出现这个结果的原因是因为,servlet是单实例的,所有的请求都是由一个servlet执行,所以成员变量username被后来请求的页面刷新了。
如果将hello.jsp改为

<%= request.getParameter("username") %>

在这里插入图片描述
结果正确,原因是,用户提交一次请求产生一个request对象,用户提交两次,产生了两个request对象,两个request对象互不干扰,虽然成员变量username的值都是lisi,但是getParameter是由请求中提取username,换句话说是直接从第一个页面提交的表单的username获取的值,所以不会出错。
将成员变量username改为局部变量结果也是正确的。

Servlet 的多线程同步问题:Servlet 本身是单实例的,这样当有多个用户同时访问某个 Servlet 时,会访问该唯一的 Servlet 实例中的成员变量,如果对成员变量进行写入操作,那就会导致 Servlet的多线程问题,即数据不一致。
加上这条语句说明servlet是支持多线程的

//得到当前线程的名字
		System.out.println(Thread.currentThread().getName());

结果是:
http-nio-8080-exec-4
http-nio-8080-exec-2

解决同步问题的方案

Servlet实现javax.servlet.SingleThreadModel(Servlet2.4中已经废弃该接口),此时Servlet容器将保证Servlet实例以单线程方式运行,也就是说,同一时刻,只会有一个线程执行Servlet的service()方法。
• 去除实例变量,使用局部变量,参见HelloServlet
• 使用同步代码块: synchronized{…}

Cookie

• Cookie的英文原意是“点心”,它是用户访问Web服务器时,服务器在用户硬盘上存放的信息,好像是服务器送给客户的“点心”。
• 服务器可以根据Cookie来跟踪用户,这对于需要区别用户的场合(如电子商务)特别有用。
• 一个Cookie包含一对Key/Value。下面的代码生成一个Cookie并将它写到用户的硬盘上:

Cookie theCookie = new Cookie("cookieName","cookieValue");
response.addCookie(the Cookie);
package learn.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class CookieServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
    private int count1;
    private int count2;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Cookie cookie = new Cookie("cookieName" + count1++, "cookieValue" + count2++);
		//最大存活时间的单位是秒
		cookie.setMaxAge(10);
		
		response.addCookie(cookie);
		
		Cookie[] cookies = request.getCookies();
		
		if(null == cookies){
			return;
		}
		
		for(Cookie cookie2 : cookies){
			System.out.println("cookie name: " + cookie2.getName());
			System.out.println("cookie value: " + cookie2.getName());
		}
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

结果是:
cookie name: cookieName0
cookie value: cookieName0
刷新一次
cookie name: cookieName0
cookie value: cookieName0
cookie name: cookieName1
cookie value: cookieName1
刷新2次
cookie name: cookieName0
cookie value: cookieName0
cookie name: cookieName1
cookie value: cookieName1
cookie name: cookieName2
cookie value: cookieName2

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%! int count1 = 0;
    int count2 = 0;
%>

<%  Cookie cookie = new Cookie("cookieName" + count1++,"cookieValue" + count2++);
    
    cookie.setMaxAge(10);
    
    response.addCookie(cookie);
    
    Cookie[] cookies = request.getCookies();
    
    if(null == cookies){
    	return;
    }
    
    for(Cookie c : cookies){
%>
<p>
    <b>cookie name:</b><%=c.getName() %>
    <br>
    <b>cookie value:</b><%=c.getValue() %>

<%  }%>
</body>
</html>

比较Servlet和JSP

• 有许多相似之处,都可以生成动态网页
• JSP的优点是擅长于网页制作,生成动态页面,比较直观。JSP的缺点是不容易跟踪与排错。
• Servlet是纯Java语言,擅长于处理流程和业务逻辑。Servlet的缺点是生成动态网页不直观。

练习

  1. 问题:HttpServletRequest对象是由谁创建的?
    • 选项:
    **(A)**由Servlet容器负责创建,对于每个HTTP请求, Servlet容器都会创建一个HttpServletRequest对象
    (B)由JavaWeb应用的Servlet或JSP组件负责创建,当Servlet或JSP组件响应HTTP请求时,先创建HttpServletRequest对象

  2. 问题:从HTTP请求中,获得请求参数,应该调用哪个方法?
    • 选项:
    (A)调用HttpServletRequest对象的getAttribute()方法
    (B)调用ServletContext对象的getAttribute()方法
    **©**调用HttpServletRequest对象的getParameter()方法

  3. 问题:ServletContext对象是由谁创建的?
    • 选项:
    (A)由Servlet容器负责创建,对于每个HTTP请求, Servlet容器都会创建一个ServletContext对象
    (B)由JavaWeb应用本身负责为自己创建一个ServletContext对象
    **©**由Servlet容器负责创建,对于每个JavaWeb应用,在启动时,Servlet容器都会创建一个ServletContext对象

  4. 分析ServletRequest、ServletResponse、Servlet、ServletContext等对象的生命周期,何时被创建,何时被销毁。
    ServletRequest、ServletResponse请求来了创建,响应过去销毁
    Servlet是自己创建
    ServletContext服务器启动、关闭或重启时被创建

  5. 问题:jspForward1.jsp要把请求转发给jspForward2.jsp,应该在jspForward1.jsp中如何实现?
    • 选项:
    (A) < a href=“jspForward2.jsp”>jspForward2.jsp
    (B) < jsp:forward page=“jspForward2.jsp”>

  6. 问题:jspForward1.jsp要把请求转发给jspForward2.jsp,在转发的时候,希望
    把用户名 “小新”传给jspForward2.jsp,如何实现?
    • 选项:
    (A) request.setParameter(“小新”);
    (B) request.setAttribute(“username”, “小新”);
    © < a href=“jspForward2.jsp?username=小新”>jspForward2.jsp

  7. 问题:当浏览器第二次访问该JSP网页时的输出结果是什么?

<!% int a=0; %>
<% int b=0;
 a++;
 b++;
%>
a:<%= a %> <br>
b:<%= b %>

a:2
b:1

  1. 问题:request.getAttribute()和request.getParameter()方法有什么异同?
    • 选项:
    **(A)**前者返回Object类型的对象,后者返回String类型的对象
    (B) request.getAttribute()和request.setAttribute()对应
    © request.getParameter()和request.setParameter()对应。
    **(D)**当两个Web组件之间为链接关系时,被链接的组件通过getParameter()方法来获得请求参数
    **(E)**当两个Web组件之间为转发关系时,转发目标组件通过getAttribute()方法来和转发源组件共享request范围内的数据。
    (F) request.getParameter()方法传递的数据,会从Web客户端传到Web服务器端,代表HTTP请求数据。
    (G) request.setAttribute()和getAttribute()方法传递的数据只会存在于Web容器内部,在具有转发关系的Web组件之间共享。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值