JavaEE实战——Servlet入门、Servlet生命周期、绝对路径、ServletContext
2016-08-20 17:55
639 查看
前言
接下来的三篇博客我会分别介绍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-patternURL 配置格式 三种:
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-7.0.42\webapps\day05\WEB-INF\classes\a1.txt readFile(filename); // 读取 a2.txt,位于站点根目录 / String filename2 = "/a2.txt";//--> / 代表WebRoot站点根目录 // getRealPath:通过 / 开始的站点根目录的绝对路径 获得绝对磁盘路径 filename2 = getServletContext().getRealPath(filename2); System.out.println(filename2); //E:\apache-tomcat-7.0.42\webapps\day05\a2.txt readFile(filename2); // 因为a1.txt 位于 /WEB-INF/classes --- 类路径 中 --- 通过Class对象读取文件 Class c = ReadFileServlet.class; // 这条语句中 / 代表classpath 根目录 --- /a1.txt 等价于 /WEB-INF/classes/a1.txt // 返回磁盘绝对路径 String filename3 = c.getResource("/a1.txt").getFile(); System.out.println(filename3);//在国际标准中,所有操作系统的根目录 都默认是 / //--/E:/apache-tomcat-7.0.42/webapps/day05/WEB-INF/classes/a1.txt readFile(filename3); } public 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(); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
结论:
在web工程中,必须将 文件路径转换绝对磁盘路径 c:\xxx e:\xxx\xxx ----- getServletContext().getRealPath("/xxx"); / 代表WebRoot 站点根目录
如果读取文件 恰好位于 WEB-INF/classes ----- 通过 类名.class.getResource("/文件名").getFile(); 获得绝对磁盘路径 / 代表 /WEB-INF/classes
缺省Servlet
如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。缺省Servlet 功能:处理其他Servlet都不处理的请求
tomcat/conf/web.xml org.apache.catalina.servlets.DefaultServlet 作为缺省Servlet
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。
我也搞了一个缺省Servlet玩玩:
package ustc.lichunchun.servlet; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 路径 / 这是一个缺省Servlet */ public class DefaultServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("缺省Servlet执行了"); // 将用户访问资源返回 // 1、获得用户访问目标资源路径 --> 下一篇博客会讲到 String path = request.getRequestURI().substring( request.getContextPath().length()); System.out.println(path);//--/hello.html // 2.判断文件是否存在 --- 读取必须绝对磁盘路径 String filename = getServletContext().getRealPath(path); File file = new File(filename);// 用绝对路径 构造文件对象 if(file.exists()){ // 存在 InputStream in = new FileInputStream(file); OutputStream out = response.getOutputStream(); int b; while((b=in.read()) != -1){ out.write(b); } in.close(); out.close(); }else{ // 不存在 response.setStatus(404); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
这时你也就明白了,静态资源不是自己返回的,是通过缺省的Servlet程序返回的。
总结
1、编写Servlet HelloServlet2、修改Servlet模板
3、Servlet生命周期 理论重点掌握
4、Servlet url三种写法 完全、目录、扩展名
5、路径问题:绝对路径 ----- 案例 九九乘法表
将web.xml 配置路径复制到网页 在路径前 /工程名
6、ServletConfig 和ServletContext 读取初始化参数区别 ?
7、ServletContext数据共享案例 ----- 统计访问次数
8、ServletContext转发案例 --- 统计字母出现次数
9、读取web工程中资源文件 ---- 绝对路径
在web工程中,必须将 文件路径转换绝对磁盘路径 c:\xxx e:\xxx\xxx ----- getServletContext().getRealPath("/xxx"); /代表WebRoot
如果读取文件 恰好位于 WEB-INF/classes ----- 通过 类名.class.getResource("/文件名").getFile(); 获得绝对磁盘路径 / 代表 /WEB-INF/classes
10、缺省Servlet 了解功能将静态资源数据内容读取写给客户端
相关文章推荐
- 第10天(就业班) servlet的映射路径、生命周期、自动加载+init方法+线程并发安全、servletconfig对象+servletcontext对象入门、servletcontext对象的使
- JavaWeb之Servlet(一)Servlet入门、API(生命周期)、ServletContext对象
- JavaEE 保存文件获取绝对路径getResource("")和servletContext.getRealPath("/")
- JavaEE 保存文件获取绝对路径getResource("")和servletContext.getRealPath("/")
- JSP、Servlet中的相对路径和绝对路径(包括路径问题)
- JSP、Servlet中的相对路径和绝对路径
- 【转】JSP和Servlet中对路径的获得问题-绝对路径和相对路径
- MyEclipse 6 实战开发讲解视频入门 5 MyEclipse 6 + Tomcat 6 Servlet 入门开发
- JSP、Servlet中的相对路径和绝对路径
- servlet、jsp中获得项目绝对路径
- Servlet中获得当前应用的相对路径和绝对路径-
- JSP和Servlet中的绝对路径和相对路径
- servlet中获得项目绝对路径
- JSP、Servlet中的相对路径和绝对路径
- 在Servlet与JSP中取得当前文件所在的相对路径与绝对路径
- JSP 和 Servlet 中的绝对路径和相对路径问题困扰了我好几天
- servlet/jsp 获取绝对路径和相对路径
- 关于Servlet中的相对路径和绝对路径归纳
- 在Servlet与JSP中取得当前文件所在的相对路径与绝对路径- -
- 关于Servlet中的相对路径和绝对路径归纳