JavaEE实战——Servlet入门Servlet生命周期绝对路径ServletContext

Posted 李春春_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaEE实战——Servlet入门Servlet生命周期绝对路径ServletContext相关的知识,希望对你有一定的参考价值。

前言

接下来的三篇博客我会分别介绍Servlet的以下三个方面:

1、Servlet程序编写 ----- 生命周期
2、ServletAPI Request Response 
3、Cookie 和 Session 

Servlet的作用:Servlet 用来 动态web资源 开发

静态web资源 : 固定数据文件
动态web资源 : 通过程序动态生成数据文件 

Servlet技术基于Request-Response编程模型 ---- HTTP协议也是基于请求响应 模型 

* Servlet技术 用来 开发基于HTTP协议的 web 应用程序 

接触 JavaEE API ------ 程序 接口 和 已经实现接口 类的 使用 

JavaEE ---- Java Platform, Enterprise Edition  缩写 

Servlet快速入门

创建步骤

1、创建web project 

2、编写 class 继承 HttpServlet 
3、在web.xml 配置 Servlet程序 虚拟访问路径 
* 用户在浏览器上通过这个路径 访问编写Servlet程序 

4、覆盖doGet或者doPost方法 进行输出

package ustc.lichunchun.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 HelloServlet extends HttpServlet
	// 覆盖 doGet 和 doPost
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException 
		// 处理 get方式请求
		System.out.println("get 请求 ...");
		
		// 设置响应流 编码问题 (必须和html/meta标签中的charset一致)
		resp.setContentType("text/html;charset=utf-8");
		
		// 生成 hello.html 相同内容
		PrintWriter out = resp.getWriter(); // 获得向浏览器输出流
		// 通过 out 流 生成 html
		out.println("<html>");
		out.println("<head>");
		out.println("<meta http-equiv=\\"Content-Type\\" content=\\"text/html; charset=utf-8\\">");//注意转义字符
		out.println("<head>");
		out.println("<body>");
		out.println("<h1>Hello,这是一个 由Servlet 动态生成网页!</h1>");
		out.println("</body>");
		out.println("</html>");
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException 
		// 处理 post方式请求
		System.out.println("post 请求 ...");
		
	

配置细节

    |--<servlet></servlet>
        |--<servlet-name> 为Servlet程序 命名
        |--<servlet-class> Servlet全路径:包名.类名
    |--<servlet-mapping></servlet-mapping>
        |--<servlet-name> Servlet 名称

        |--<url-pattern> 用户在浏览器通过/hello 访问Servlet

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<!-- 配置全局初始化参数,所有Servlet都可以 访问 -->
	<context-param>
		<param-name>hobby</param-name>
		<param-value>电音</param-value>
	</context-param>
    <!-- 用户 通过 /init 访问Servlet -->
	<servlet>
		<servlet-name>InitServlet</servlet-name>
		<servlet-class>ustc.lichunchun.servlet.InitServlet</servlet-class>
		<!-- 配置 Servlet在服务器启动时 进行加载 -->
		<load-on-startup>1</load-on-startup>
  	</servlet>
  	<servlet-mapping>
		<servlet-name>InitServlet</servlet-name>
		<url-pattern>/init</url-pattern>
		<url-pattern>/init2</url-pattern>
	</servlet-mapping>
  
  <!-- 为 HelloServlet 配置 浏览器可以访问虚拟 路径 -->	
  <servlet>
    <!-- 为 Servlet程序  命名 -->
  	<servlet-name>HelloServlet</servlet-name>
  	<!-- Servlet全路径 : 包名.类名 -->
  	<servlet-class>ustc.lichunchun.servlet.HelloServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>HelloServlet2</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.HelloServlet2</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>ChengfabiaoServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.ChengfabiaoServlet</servlet-class>
  </servlet>
  <servlet>
    <!-- 在Servlet标签内部 配置初始化参数 -->
    <servlet-name>ConfigServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.ConfigServlet</servlet-class>
    <!-- 通过 init-param -->
    <init-param>
    	<param-name>MyName</param-name>
    	<param-value>李春春</param-value>
    </init-param>
    <init-param>
    	<param-name>MySchool</param-name>
    	<param-value>中国科学技术大学</param-value>
    </init-param>
  </servlet>
  <servlet>
    <servlet-name>ContextServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.ContextServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>CountServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.CountServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet>
    <servlet-name>CountShowServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.CountShowServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>AServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.AServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>BServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.BServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>LetterCountServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.LetterCountServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>LetterResultServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.LetterResultServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>ReadFileServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.ReadFileServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>DefaultServlet</servlet-name>
    <servlet-class>ustc.lichunchun.servlet.DefaultServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <!-- 为Servlet程序 指定 浏览器访问 虚拟路径 -->
  	<servlet-name>HelloServlet</servlet-name>
  	<!-- 用户 在 浏览器通过/hello 访问Servlet -->
  	<url-pattern>/hello</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>HelloServlet2</servlet-name>
    <url-pattern>/hello2</url-pattern>
    <url-pattern>/abc/*</url-pattern>
    <!-- 扩展名 匹配 必须不能 用 / 开始 -->
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>ChengfabiaoServlet</servlet-name>
    <!-- 客户端绝对路径 /day05/chengfabiao -->
    <url-pattern>/chengfabiao</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>ConfigServlet</servlet-name>
    <url-pattern>/config</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>ContextServlet</servlet-name>
    <url-pattern>/context</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>CountServlet</servlet-name>
    <url-pattern>/count</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>CountShowServlet</servlet-name>
    <url-pattern>/countshow</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>AServlet</servlet-name>
    <url-pattern>/a</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>BServlet</servlet-name>
    <url-pattern>/b</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>LetterCountServlet</servlet-name>
    <url-pattern>/servlet/lettercount</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>LetterResultServlet</servlet-name>
    <url-pattern>/servlet/result</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>ReadFileServlet</servlet-name>
    <url-pattern>/readfile</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>DefaultServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

Servlet作用:动态生成 网页文件 

Servlet执行过程:

1、用户在客户端发起url请求 : http://localhost/day05/hello ----- web.xml /hello 映射 HelloServlet程序

2、用户提交请求时,get方式提交 执行 HelloServlet的 doGet方法 post方式提交 执行 HelloServlet的doPost 方法 


对Servlet编译运行类库的理解:

Servlet程序在编写和运行时,需要依赖javaee 类库 (API支持)(Jar包)

* 在学习javase  List 需要 import java.util.List  需要 jre/lib/rt.jar 
* MyEclipse 自动导入 javaee5 liberary  存在 javaee.jar  提供 Servlet 需要类 API支持 (开发环境使Servlet程序正常编译
* Serlvet程序运行tomcat环境中 没有javaee.jar , 在 tomcat/lib/servlet-api.jar 提供Servlet程序运行需要 类API 支持 (运行环境需要的)

示例:手动编写Servlet运行

1、在webapps 新建 day05test目录 --- 虚拟应用
2、在day05test 新建 WEB-INF/classes
3、将编写Servlet的java源码文件 放入 classes ,在 WEB-INF 配置web.xml 
4、编译Servlet的 java程序 
javac -classpath E:\\apache-tomcat-6.0.14\\lib\\servlet-api.jar HelloServlet.java  // 通过 -classpath 指定 Servlet需要jar 包
生成Servlet package结构 
javac -d . -classpath E:\\apache-tomcat-6.0.14\\lib\\servlet-api.jar HelloServlet.java

这时,就可以在浏览器中使用 http://192.168.1.104/day05test/hello 来访问这个Servlet程序

Servlet运行原理剖析 

编写Servlet程序没有 main函数 ---- tomcat调用Servlet程序执行


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

MyEclipse中Servlet模板修改

一般开发中,我们是不会手动编写Servlet程序的。

而是通过myeclipse向导 创建Servlet ---- 向导会帮你创建Servlet程序,同时自动生成web.xml 配置 


* 生成Servlet信息非常复杂,想生成Servlet 内容整洁一些,精简一些 ------ 修改Servlet模板

1、myeclipse工具 ---- 安装目录 common / plugins
com.genuitec.eclipse.wizards_8.5.0.me201003052220.jar
2、解压缩 templates/Servlet.java --- 这个就是Servlet模板 
3、com.genuitec.eclipse.wizards_9.0.0.me201108091322.jar 复制此文件jar包名字
   进入你安装的MyEclipse的安装目录下的Common文件夹里 然后再搜索文件的地方粘贴上上面复制的文件名。
   搜索出来后,右键 打开文件夹的位置,剪接出来。如果你想备份就剪接,不备份直接把下载的jar包丢进MyEclipse\\Common\\plugins文件夹里面即可,选择覆盖就OK了,重新在建立一个Servlet 试试效果吧!

   在MyEclipse10中设置的,别的版本没测试过!


理解Servlet继承关系

通过API得到 Servlet 的继承关系



Servlet接口 ---- 实现类 GenericServlet ------ 子类 HttpServlet  ------ 编写Servlet 继承HttpServlet 

* 我们自己自定义编写的 Servlet 就间接实现了 Servlet 接口  (简化开发)


1、Servlet接口 提出,为了解决基于请求-响应模型数据处理 (并没有涉及与HTTP协议相关 API)

2、GenericServlet 实现接口 通用Servlet 也没有提供与 HTTP协议相关 API 

3、HttpServlet 才引入与 HTTP协议相关 API 

所以说,我们现在基于HTTP协议进行web开发,只需要继承HttpServlet即可

Servlet生命周期 

init(ServletConfig config)  初始化 

service(ServletRequest req, ServletResponse res)  提供服务方法,响应客户请求

destroy()  销毁 


1、tomcat服务器启动时,没有创建Servlet对象

2、第一次访问时,tomcat构造Servlet对象,调用 init,执行service
3、从第二次以后的访问 tomcat 不会重新创建Servlet对象,也不会调用init ---- 每一次访问都会调用service 
   (第二次开始后的每次Servlet服务请求,web服务器将启动一个新线程调用service方法响应客户请求,但是Servlet对象是唯一的,不会再创建。详见InitServlet.java成员变量 i)

4、当服务器重启或正常关闭时 调用destroy (正常关闭 shutdown.bat)

package ustc.lichunchun.servlet;

import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

//实现Servlet 接口  --- 感受生命周期
public class InitServlet implements Servlet
	
	int i;
	
	public InitServlet()
		System.out.println("构造了 InitServlet对象 ...");
	
	
	@Override
	public void destroy() 
		System.out.println("销毁...");
	


	@Override
	public void init(ServletConfig arg0) throws ServletException 
		System.out.println("初始化 ...");
	

	@Override
	public void service(ServletRequest request, ServletResponse response)
			throws ServletException, IOException 
		System.out.println("服务...");
		System.out.println("i:" + i + " " + Thread.currentThread().getName());
		i++;
	

	@Override
	public ServletConfig getServletConfig() 
		
		return null;
	
	
	@Override
	public String getServletInfo() 
		
		return null;
	



Servlet对象是tomcat创建的,每次请求都会调用Servlet中service方法,tomcat服务器会在每次调用Servlet的service方法时,为该方法创建Request对象和Response对象。

即:对于一个Servlet在每次请求访问时,Servlet引擎都会创建一个描述请求消息和一个描述响应消息的对象。其中描述请求消息的对象实现了HttpServletRequest接口,而描述响应消息的对象实现了HttpServletResponse接口。

* 但是,在 JavaEE API 中并没有Request和Response实现类 ----- 实现类由Servlet服务器提供的,tomcat提供实现类 weblogic提供实现类 

service方法和HttpServlet doGet/doPost 关系区别

必须阅读HttpServlet源代码 --> HttpServlet.java中两个service()方法

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

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

        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 = req.getDateHeader(HEADER_IFMODSINCE);
                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代码实现中,service方法 会根据请求方式不同 调用相应doXXX方法 get方式请求 --- doGet、post方式 --- doPost 

总结:

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

2、在Servlet的整个生命周期内,Servlet的init方法只被调用一次,而且默认是在第一次访问该Servlet程序时才会调用。

而对一个Servlet的每次访问请求都导致Servlet引擎调用一次Servlet的service方法。
对于每次访问请求,Servlet引擎都会创建一个新的HtpServletRequest请求对象和HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,

service方法再根据请求方式分别调用doXXX方法。

Servlet对象的初始化加载(自动加载)

配置Servlet随tomcat服务器启动时 进行初始化 --- <load-on-startup >

那么web应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init方法。
(注:实际上调用的是该Servlet对象继承的init(config)方法,但是init(config)方法内部会调用子类覆写的init()方法)

  *<load-on-startup > 参数可以是一个数字 0-9 代表服务器加载优先级 0 最高 

例如:

1、在tomcat启动时,想通过Servlet加载一些框架配置文件 配置随服务器启动 

  (例如struts1,会在web.xml中配置ActionServlet入口, 客户端发送请求Http Request,被struts1的核心控件器ActionServlet接收,ActionServlet根据struts-config.xml里的映射关系找到对应的Action,调用Action的excute()方法,执行相应的逻辑操作,比如调用Model层的方法,然后通过ActionForward标签,跳转到对应的输出页面,详见:http://blog.csdn.net/liusong0605/article/details/9935329

2、为web应用写一个InitServlet,这个Servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据

<servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
 </servlet>

结论:

1、编写Servlet 继承HttpServlet

2、编写Servlet 不需要覆盖service方法,只需要覆盖doGet和doPost 方法

为什么继承了HttpServlet的Servlet初始化时只需覆盖init() ,无需覆盖init(config) ?

--> GenericServlet.java 中的 init(config)方法

* 原因:HttpServlet继承自GenericServlet,GenericServlet中的init(Config) 调用了 init() 

* 注意:在启动tomcat时,其调用的是复写的Servlet对象从GenericServlet继承过来的的init(config)方法,并且init(config)方法中会调用HttpServlet子类Servlet对象自己复写的init()方法

public void init(ServletConfig config) throws ServletException 
	this.config = config;
	this.init();
    

3、当doGet和doPost代码逻辑相同时,可以相互调用,简化编程

Servlet路径映射配置(url-pattern)

一个Servlet可以配置多个url-pattern 

URL 配置格式 三种

1、完全路径匹配  (以/开始 ) 例如:/hello /init 
* 当前工程没有被正确发布,访问该工程所有静态资源、动态资源 发生404 ----- 工程启动时出错了 
* 查看错误时 分析错误
1) 单一错误 : 从上到下 查看第一行你自己写代码 (有的错误与代码无关,查看错误信息)
2)复合错误 Caused by ---- 查看最后一个Caused by 
* Invalid <url-pattern> init2 in servlet mapping 

2、目录匹配 (以/开始) 例如:/*  /abc/* 

/ 代表网站根目录 

3、扩展名 (不能以/开始) 例如:*.do *.action 

典型错误 /*.do 

优先级:完全匹配 > 目录匹配 > 扩展名匹配



绝对路径和相对路径

路径问题的原因:


结论:客户端/day05/hello 和 服务器内部day05工程中/hello ---- 等价的

路径问题:编写九九乘法表

1、需要用户在客户端输入一个数字

2、Servlet接收客户输入数字 打印对应乘法表 

<!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 提交数字 -->
<!-- 相对路径 ../chengfabiao -->
<form action="/day05/chengfabiao" method="post">
	请输入一个数字<input type="text" name="number"/>
	<input type="submit" value="打印乘法表"/>
</form>
</body>
</html>

<!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 提交数字 -->
<!-- 相对路径 chengfabiao -->
<form action="/day05/chengfabiao" method="post">
	请输入一个数字<input type="text" name="number"/>
	<input type="submit" value="打印乘法表"/>
</form>
</body>
</html>
package ustc.lichunchun.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 ChengfabiaoServlet extends HttpServlet 

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		// 获得请求提交数据 number
		String numberStr = request.getParameter("number");// 通过 input name 获得数据
		int number = 0;
		try 
			number = Integer.parseInt(numberStr);
		 catch (NumberFormatException e) 
			//e.printStackTrace();
			throw new RuntimeException("输入的不是整数");
		
		// 打印 九九 ---输出到浏览器
		PrintWriter out = response.getWriter();
		for(int i = 1; i <= number; i++)
			for(int j = 1; j <= i; j++)
				out.print(i +"*"+ j +"="+ i*j +" ");
			
			out.println("<br/>");
		
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	

在chengfabiao.html 通过 action 访问 ChengfabiaoServlet  路径可以用绝对路径和相对路径

相对路径

相对当前网页地址 路径  例如: chengfabiao  ./chengfabiao ../chengfabiao

例如: http://localhost/day05/chengfabiao.html  提交 action="chengfabiao"
* 原理:将url最后一个地址换成相对路径 
结果: http://localhost/day05/chengfabiao  ----- 服务器端 /chengfabiao
* /day05/chengfabiao 与服务器 /chengfabiao 匹配
--------
例如: http://localhost/day05/aaa/chengfabiao.html 提交 action="chengfabiao"
结果: http://localhost/day05/aaa/chengfabiao  ----- 服务器 /chengfabiao 
* /aaa/chengfabiao 与服务器 /chengfabiao 不匹配 出现404 
--------
http://localhost/day05/aaa/chengfabiao.html 提供 action="../chengfabiao" 
结果:http://localhost/day05/aaa/../chengfabiao ---- > ..和/aaa抵消 http://localhost/day05/chengfabiao 可以匹配服务器 /chengfabiao

结论:如果用相对路径提交请求,考虑当前路径。 当前访问服务器资源路径不同 ---- 相对路径写法不同

绝对路径 

解决 相对路径,会根据当前地址改变问题。 例如: /day05/chengfabiao 、http://localhost/day05/chengfabiao

绝对路径 以/ 开始 --> / 代表访问tomcat服务器根目录

(注:这里的绝对路径/ 是对html等来说的,注意和Servlet中的站点根目录/ 区分)

例如: 客户端访问服务器,不管当前路径是什么 --- / 代表服务器根目录(http://localhost)
/day05 --- 找到虚拟目录day05工程  /day05/chengfabiao --- 找到 day05工程下配置的虚拟路径/chengfabiao

结论:

1.服务器端和客户端对于/ 的区别

客户端路径  /工程虚拟目录/servlet虚拟路径  例如:/day05/chengfabiao
服务器端 配置web.xml 不需要写工程虚拟目录  只要直接写  /servlet虚拟路径  例如:/chengfabiao
(即 客户端的绝对路径 一定要比 服务器端web.xml中配置的路径 多写一个工程名/day05 )

* WebRoot 站点根目录说明:


2.客户端关于路径问题的编程结论

  *.html *.jsp内都使用绝对路径

  *.css内部使用相对路径----背景图片

  *.js中使用绝对路径

阶段性总结

上面的知识点概括:

    1、掌握Servlet程序编写
    2、通过路径 访问Servlet 程序
    3、Servlet生命周期

接下来围绕着:

    Servlet 生命周期来学习

        init

        service

        destroy 

ServletConfig获得初始化参数

学习init方法 ---- init(ServletConfig) ---- 通过ServletConfig 获得Servlet初始化参数

1、创建一个Servlet
2、在web.xml 中 <servlet> 标签内 通过 <init-param> 标签 为Servlet配置初始化参数
    <init-param>
    <param-name>itcast</param-name>
    <param-value>传智播客</param-value>
    </init-param>
3、在Servlet程序中通过ServletConfig对象 获得itcast对应数据 
getInitParameter ------ 通过name获得value
getInitParameterNames  ----- 获得所有name 

* 当Servlet配置了初始化参数后,web容器在创建Servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用Servlet的init方法时,将ServletConfig对象传递给Servlet。进而,程序员通过ServletConfig对象就可以得到当前Servlet的初始化参数信息。

package ustc.lichunchun.servlet;

import java.io.IOException;
import java.util.Enumeration;

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 ConfigServlet extends HttpServlet 
	/*
	private ServletConfig config;
	
	// 通过init 方法 初始化
	@Override
	public void init(ServletConfig config) throws ServletException 
		// 通过config参数 获得Servlet初始化信息
		// 获得指定一个name的value
		System.out.println(config.getInitParameter("MyName"));
		System.out.println(config.getInitParameter("MySchool"));
		
		// 获得所有name 然后 根据name 获得value
		Enumeration<String> names = config.getInitParameterNames();
		while(names.hasMoreElements())
			String name = names.nextElement();
			System.out.println(name +": "+config.getInitParameter(name));
		
		
		this.config = config;
	
	*/
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		// 获得config 中数据 --- 需要在init方法中 将config对象 保存成员变量
		// GenericServlet 已经将ServletConfig 保存成员变量
		System.out.println(getServletConfig().getInitParameter("MyName"));
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	


* 思考 :如何在doGet 或 doPost 方法中 获得 Servlet初始化参数 ?

将ServletConfig对象保存成 实例成员变量,但其实这件事情,GenericServlet已经帮我们做过了!

GenericServlet 已经将ServletConfig 保存成员变量   ----- 在子类中通过 getServletConfig方法 获得 初始化参数 

GenericServlet.java 

public void init(ServletConfig config) throws ServletException 
	this.config = config;
	this.init();
    

public ServletContext getServletConfig() 
	return config;
    

结论:

子类Servlet不需要覆盖 init(ServletConfig) , 只需要通过GenericServlet中 getServletConfig() 获得ServletConfig对象 

应用:

在init-param 指定配置文件位置和名称,配置Servlet随服务器启动创建 load-on-startup 


struts配置 ---- 服务器启动时,就会加载ActionServlet(load-on-startup),并且初始化过程中,tomcat自动加载该ActionServlet对应的init-param,封装到ServletConfig对象中。并且由于设置了load-on-startup,tomcat启动时还会调用ActionServlet的init(config),将加载了init-param的ServletConfig传给config,我们在通过getServletConfig方法得到ServletConfig,通过Servletconfig就会读取init-param中name、value,从而读取到struts指定的配置文件的位置。

(注:在调用init(config)方法时,内部还会调用ActionServlet复写的init()方法,该方法就是用来读取struts-config.xml文件中的内容,并根据struts-config.xml中的内容来初始化相关的资源)

(即:由于servlet设置了load-on-startup,所以tomcat启动时会加载ActionServlet,也就是会执行ActionServlet中的init()方法,Struts 的初始化实现就是在这里实现的。)


总结:

ServletConfig 配置初始化数据,只能在相对应配置的Servlet中获得,其它Servlet无法获得  ----- 每个Servlet程序都对应一个ServletConfig对象 

Web应用对象:ServletContext

ServletContext 是Servlet上下文对象 

每一个工程 都会创建 单独ServletContext对象,这个对象代表当前web工程 
操作ServletContext 必须通过ServletConfig 获得该对象 
由于一个web应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。

应用:

1、 获得整个web应用初始化参数
2、 实现全局数据共享 (案例--统计站点访问次数)
3、 实现服务器端转发功能 (案例--统计字母出现次数)

4、 读取web工程资源文件

下面我就着重介绍一下这4个方面。

1、ServletContext配置全局参数

ServletContext获取WEB应用的初始化参数 和 ServletConfig 对象的不同:


ServletConfig对象 配置参数,只对当前配置的Servlet有效;

如果配置全局参数,所有Servlet都可以访问,通过ServletContext。

即:ServletConfig每个Servlet对应一个、ServletContext每个工程(web应用)对应一个

package ustc.lichunchun.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 ContextServlet extends HttpServlet 

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		// 获得 hobby 全局参数
		// 通过ServletConfig 获得 ServletContext
		//ServletContext context = getServletConfig().getServletContext();
		
		// 上面写法可以简化一下,因为GenericServlet已经帮我们封装好了
		ServletContext context = getServletContext();
		
		// 读取全局初始化参数
		System.out.println(context.getInitParameter("hobby"));
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	

应用:放一些web工程的版权信息、资源文件、用户密码、管理员邮箱等信息。

<context-param>
	<param-name>hobby</param-name>
	<param-value>唱儿歌</param-value>	
</context-param>

2、ServletContext网站访问次数数据共享

通过ServletContext 在多个Servlet间 共享数据 

示例

在ServletContext中 保存站点访问次数 ,每当一个用户访问站点,将访问次数+1 

在CountServlet 初始化过程中,向ServletContext 保存访问次数 ---- 0  -------------->  ServletContext  setAttribute 


package ustc.lichunchun.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;
/**
 * 用户访问 countservlet 访问次数 +1
 */
public class CountServlet extends HttpServlet 
	//尽量覆盖无参数的init()方法
	@Override
	public void init() throws ServletException 
		// 向ServletContext 保存访问次数 0
		
		// 获得ServletContext对象
		ServletContext context = getServletContext();
		// 保存数据 setAttribute
		context.setAttribute("visittimes", 0);//Attribute类似于一个map表
	
	
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		// 每次访问 执行 doGet --- 将visittimes 次数 +1
		
		// 1、从ServletContext中获得 访问次数
		ServletContext context = getServletContext();
		int times = (Integer)context.getAttribute("visittimes");
		
		// 2、访问次数 +1
		times++;
		
		// 3、将访问次数更新回去 ServletContext
		context.setAttribute("visittimes", times); 	
		
		System.out.println("网站被访问了一次!");
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	

每次访问次数 +1 --- 数据存放ServletContext中 ---- 所有Servlet都可以获得该数据 ServletContext  getAttrbute

* 在ServletContext中保存数据,所有Servlet都可以访问

package ustc.lichunchun.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;
/**
 * 通过Servlet查看 网站访问次数
 */
public class CountShowServlet extends HttpServlet 

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		// 获得 网站访问次数
		ServletContext context = getServletContext();
		int times = (Integer) context.getAttribute("visittimes");
		
		// 输出访问次数 到浏览器
		response.getWriter().println(
				"web site has been visitted:" + times + " times!");
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	

3、ServletContext(转发)统计字母出现次数

通过ServletContext 完成服务器端程序转发 

什么是转发? 转发和重定向区别 ?


转发是java特有的一个功能

getServletContext().getRequestDispatcher(java.lang.String path)  ---- 完成转发 

package ustc.lichunchun.servlet;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 分别从AServlet 转发 和重定向 给 BServlet
 */
public class AServlet extends HttpServlet 

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		System.out.println("AServlet 执行...");
		/*
		// 转发
		ServletContext context = getServletContext();
		
		// 转发给BServlet 
		// 因为转发是在服务器内部进行的,所以 这里 / 代表当前web工程 /day05/ (即站点根目录)
		// 包括后面我们说到的ServletContext读取资源文件,都是在服务器内部进行的
		// 这时,我们Servlet文件中使用的/ 代表站点根目录 ---> 见day05图片 站点根目录说明
		RequestDispatcher dispatcher = context.getRequestDispatcher("/b");//这里写的和web.xml配置的一致即可
		dispatcher.forward(request, response);
		*/
		
		// 重定向
		// 因为重定向是从客户端访问,所以 / 代表当前web服务器
		response.setStatus(302);
		response.setHeader("Location", "/day05/b");// 从客户端访问,必须含有工程路径
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	

package ustc.lichunchun.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 BServlet extends HttpServlet 

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		System.out.println("BServlet 执行...");
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	

转发结果:

重定向结果:


* 参数path 代表要跳转的页面的servlet路径,和web.xml中配置的一致即可。

使用转发还是重定向? ---- 转发性能好于重定向,因为请求次数少。

案例:统计字母次数


<!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>
<!-- 要求用户输入一段内容  -->
<!-- 相对路径 servlet/lettercount -->
<form action="/day05/servlet/lettercount" method="post">
	请输入一段英文内容<textarea rows="5" cols="60" name="content"></textarea>
	<input type="submit" value="统计"/>
</form>
</body>
</html>
package ustc.lichunchun.servlet;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
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 LetterCountServlet extends HttpServlet 

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		// 获得用户输入这段英文内容
		 System.out.println("Letter count ...");
		 
		// 获得form 提交内容 content 就是 textarea name属性
		 String content = request.getParameter("content");
		 content = content.toUpperCase();// 将内容转为大写
		 
		// 分析统计 --- 忽略大小写
		 int times[] = new int[26];
		 
		// 遍历每一个字母
		 for (int i = 0; i < content.length(); i++) 
			char c = content.charAt(i);
			// 判断字母是不是26个字母之一
			if(Character.isLetter(c))// c >= 'A' && c <= 'Z'
				// 如果 c 是 'A' 在数组 0 位置 +1 ,如果 c是 'B' 在数组1位置+1
				times[c-'A']++;
			
		
		// 交给下一个Servlet显示 ,将统计结果保存ServletContext
		 ServletContext context = getServletContext();
		 context.setAttribute("times", times);
		 
		// 转发跳转 另一个Servlet
		 RequestDispatcher dispatcher = context.getRequestDispatcher("/servlet/result");
		 dispatcher.forward(request, response);
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	

request.getParameter("content") 获得form 提交内容 content 就是 textarea name属性。

package ustc.lichunchun.servlet;

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

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 LetterResultServlet extends HttpServlet 

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		// 1 从ServletContext 获得数据
		ServletContext context = getServletContext();
		int[] times = (int[])context.getAttribute("times");
		
		// 2、将结果打印浏览器
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		for (int i = 0; i < times.length; i++) 
			// 字母
			char c = (char) ('A' + i);
			// 次数
			int ltimes = times[i];
			
			out.println("字母:" + c + "出现了 " + ltimes + "次!<br/>");
		
	

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		doGet(request, response);
	

结果如下:

4、ServletContext获得绝对磁盘路径读取web资源文件

利用ServletContext对象读取资源文件

以前我们都是在java工程中读取文件。但是在java工程中读取文件和Servlet中读取文件是有区别的。

现在我分别在day05、day05/src、day05/WebRoot下新建三个txt文件:


复习:

用Java程序如何读取a1.txt、a2.txt、a3.txt --- 默认是读取当前day05工程下文件


package ustc.lichunchun.io;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
 * 复习 同java程序 如何读取 a1.txt a2.txt a3.txt
 */
public class FileReaderTest 

	public static void main(String[] args) throws Exception 
		
		// 默认读取当前工程根目录 /
		// a3.txt直接用"a3.txt",因为就在当前工程根目录下
		
		String filename = "src/a1.txt";
		readFile(filename);
		
		String filename2 = "WebRoot/a2.txt";
		readFile(filename2);
		
		String filename3 = "a3.txt";
		readFile(filename3);
	

	private static void readFile(String filename) throws FileNotFoundException,
			IOException 
		BufferedReader in = new BufferedReader(new FileReader(filename));
		String line;
		while((line = in.readLine()) != null)
			System.out.println(line);
		
		in.close();
	

使用java application 读取文件,可以读取当前工程下所有文件  ----- 使用相对路径读取文件 (绝对路径也可以)

使用Servlet读取文件,只能读取WebRoot下所有文件  ---- 必须使用绝对磁盘路径读取文件

(* Servlet读取不到a3.txt,由于Servlet程序只会运行在tomcat环境中、工程只会向tomcat发布WebRoot,故只能读取WebRoot下所有文件)

(* src目录下的文件,在发布工程时,都会提交到WebRoot下的classes文件夹下,所以servlet读取文件可以找到src中对应的a1.txt文件)

那web应用如何获取到绝对磁盘路径呢?

在web project工程中,必须通过站点根目录的绝对路径/  来获得绝对磁盘路径,才能读取WebRoot下的文件----getServletContext().getRealPath(“/WEB-INF/info.txt”)

* 因为 WEB-INF/classes 非常特殊 (存放.class文件目录),被类加载器加载,所以还可以通过Class类对象读取 该目录下文件 

String filename3 = c.getResource("/a1.txt").getFile(); ----- / 代表 /WEB-INF/classes 

package ustc.lichunchun.servlet;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

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

import sun.reflect.ReflectionFactory.GetReflectionFactoryAction;

/**
 * 通过Servlet 读取 文件 -- 必须通过绝对磁盘路径,才能读取web资源文件
 */
public class ReadFileServlet extends HttpServlet 

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException 
		
		// 读取 a1.txt,位于 /WEB-INF/classes
		String filename = "/WEB-INF/classes/a1.txt";
		filename = getServletContext().getRealPath(filename);
		System.out.println(filename);
		//E:\\apache-tomcat

以上是关于JavaEE实战——Servlet入门Servlet生命周期绝对路径ServletContext的主要内容,如果未能解决你的问题,请参考以下文章

转载 Servlet3.0中使用注解配置Servle

JavaEE实战——会话与状态 Cookie_Session

JavaEE实战——会话与状态 Cookie_Session

JavaEE实战——jsp入门El表达式JSTL标签库

JavaEE实战——jsp入门El表达式JSTL标签库

狂神说JavaJavaWeb入门到实战--MVC三层架构