Servlet简介
1.Servlet是sun公司提供的一门用于开发动态web资源的技术
*静态web资源:固定数据文件
*动态web资源:通过程序动态生成数据文件
2.Servlet技术基于Request-Response编程模型 ---- HTTP协议也是基于请求响应模型
*Servlet技术用来开发基于HTTP web应用程序
3.Servlet快速入门
1)创建web project
2)编写class继承HttpServlet
3)在web.xml配置Servlet程序,虚拟访问路径
*用户在浏览器上通过这个路径访问编写Servlet程序
4)覆盖doGet或者doPost方法进行输出
eg:
package cn.lsl.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{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("get请求"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post请求"); } }
<?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"> <!-- 为HelloServlet配置浏览器可以访问虚拟路径 --> <servlet> <!-- 为Servlet程序命名 --> <servlet-name>HelloServlet</servlet-name> <!-- Servlet全路径:包名.类名 --> <servlet-class>cn.lsl.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <!-- 为Servlet程序制定浏览器访问虚拟路径 --> <servlet-name>HelloServlet</servlet-name> <!-- 用户在浏览器通过/hello访问Servlet --> <url-pattern>/hello</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
4.Servlet动态生成网页文件
eg:
package cn.lsl.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{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("get请求"); //设置响应流编码问题 resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter();//获得向浏览器输出流 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>这是一个由Servlet动态生成页面!</h1>"); out.println("</body>"); out.println("</html>"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("post请求"); } }
Servlet生命周期
1.Servlet接口定义了Servlet生命周期
init()方法:服务器调用该方法初始化Servlet
service()方法:初始化完毕,服务器调用该方法响应客户的请求
destory()方法:服务器调用该方法消灭servlet对象
1)tomcat服务器启动时,没有创建Servlet对象
2)第一次访问时,tomcat构造Servlet对象,调用init,执行service
3)从第二次以后访问tomcat不会从新创建Servlet对象,也不会调用init ---- 每一次访问都会调用service
4)当服务器重启或正常关闭,调用destroy(正常关闭 shutdown.bat)
eg:
package cn.lsl.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; public class InitServlet implements Servlet{ public InitServlet(){ System.out.println("构造了InitServlet对象..."); } @Override public void init(ServletConfig arg0) throws ServletException { // TODO Auto-generated method stub System.out.println("初始化...."); } @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { // TODO Auto-generated method stub System.out.println("服务...."); } @Override public void destroy() { // TODO Auto-generated method stub System.out.println("销毁...."); } @Override public ServletConfig getServletConfig() { // TODO Auto-generated method stub return null; } @Override public String getServletInfo() { // TODO Auto-generated method stub return null; } }
//第一次访问输出的结果:
//构造了InitServlet对象...
//初始化....
//服务....
//第二次访问输出的结果:
//服务....
其中,init()方法只在Servlet第一次被请求加载的时候被调用一次,当有客户再请求Servlet服务时,Web服务器将启动一个新的线程,在该线程中,调用service方法响应客户的请求。
2.Servlet对象是tomcat创建的,每次请求调用Servlet中service方法,tomcat服务器会在每次调用Servlet的service方法时,为该方法创建Request对象和Response对象。
*JavaEE API中没有Request和Response实现类 ----- 实现类由Servlet服务器提供的,tomcat提供实现类(weblogic提供实现类)。
Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
3.针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其他请求服务,直至web容器退出,Servlet实例对象才会销毁。
4.service方法和HttpServlet doGet/doPost关系区别?
在HttpServlet代码实现中,根据请求方式不同 调用相应doXXX方法,get方式请求--- doGet post方式 --- doPost
5.配置Servlet随tomcat服务器启动时,进行初始化 --- <load-on-startup>
*<load-on-startup>参数可以是一个数字 0-9 代表服务器加载优先级,0最高
eg:
<servlet> <servlet-name>InitServlet</servlet-name> <servlet-class>cn.itcast.servlet.InitServlet</servlet-class> <!-- 配置 Servlet在服务器启动时 进行加载 --> <load-on-startup>1</load-on-startup> </servlet>
总结:
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。
对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,
service方法再根据请求方式分别调用doXXX方法。
路径匹配
一个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
优先级:完全匹配>目录匹配>扩展名匹配
路径问题
编写九九乘法表
1、需要用户在客户端输入一个数字
2、Servlet接受客户端输入数字,打印对应乘法表
eg:
package cn.lsl.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"); int number = 0; try { number = Integer.parseInt(numberStr); } catch (NumberFormatException e) { throw new RuntimeException("输入的不是整数"); } //打印九九表 PrintWriter out = response.getWriter(); for(int i=1; i<=number; i++){ for(int j=1; j<=i; j++){ out.print(j + "*" + i + "=" + j * i + " "); } out.print("<br>"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
在根目录下的chengfabiao.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>乘法表</title> </head> <body> <!-- 相对路径 chengfabiao --> <form action="/ServletTest/chengfabiao" method="post"> 请输入一个数字<input type="text" name="number"> <input type="submit" value="打印乘法表"/> </form> </body> </html>
在aaa文件夹下的chengfabiao.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>乘法表</title> </head> <body> <!-- 相对路径 ../chengfabiao --> <form action="/ServletTest/chengfabiao" method="post"> 请输入一个数字<input type="text" name="number"> <input type="submit" value="打印乘法表"/> </form> </body> </html>
分析:
在chengfabiao.html 通过 action 访问 ChengfabiaoServlet 路径可以用绝对路径和相对路径
相对路径:相对当前网页地址 路径 例如 chengfabiao ./chengfabiao ../chengfabiao
例如: http://localhost/ServletTest/chengfabiao.html 提交 action="chengfabiao"
* 将url最后地址换成相对路径
结果: http://localhost/ServletTest/chengfabiao ----- 服务器端 /chengfabiao
例如: http://localhost/ServletTest/aaa/chengfabiao.html 提交 action="chengfabiao"
结果: http://localhost/ServletTest/aaa/chengfabiao ----- 服务器 /chengfabiao
* /aaa/chengfabiao 与服务器 /chengfabiao 不匹配 出现404
http://localhost/ServletTest/aaa/chengfabiao.html 提供 action="../chengfabiao"
结果:http://localhost/ServletTest/aaa/../chengfabiao ---- > ..和/aaa抵消 http://localhost/ServletTest/chengfabiao 可以匹配服务器 /chengfabiao
结论:如果用相对路径提交请求,考虑当前路径, 当前访问服务器资源路径不同 ---- 相对路径写法不同
绝对路径 解决相对路径,会根据当前地址改变问题。 例如: /ServletTest/chengfabiao 、http://localhost/ServletTest/chengfabiao
绝对路径 以/开始 /访问服务器根目录
例如: 客户端访问服务器,不管当前路径是什么 --- / 服务器根目录 http://localhost
/ServletTest --- 找到虚拟目录ServletTest工程 /ServletTest/chengfabiao --- 找到 ServletTest工程下配置 虚拟路径/chengfabiao
结论: 客户端路径 /工程虚拟目录/servlet虚拟路径 例如:/ServletTest/chengfabiao
服务器端 配置web.xml 不需要写工程虚拟目录 只要直接写/servlet虚拟路径 例如:/chengfabiao
ServletConfig
1.init方法 --- init(ServletConfig) --- 通过ServletConfig获得Servlet初始化参数
2.在web.xml中<servlet>标签内通过<init-param>标签为Servlet配置初始化参数
<init-param> <param-name>address</param-name> <param-value>软件园</param-value> </init-param>
3.在Servlet程序中通过ServletConfig对象获得address对应数据
getInitParameter ----- 通过name获得value
getInitParameterNames ---- 获得所有name
*思考:如何在doGet 或 doPost 方法中 获得 Servlet初始化参数
GenericServlet 已经将ServletConfig 保存成员变量 ----- 在子类中通过 getServletConfig方法 获得 初始化参数
结论:子类Servlet不需要覆盖init(ServletConfig),只需要通过GenericServlet中getServletConfig()获得ServletConfig对象
应用:在init-param 指定配置文件位置和名称,配置Servlet随服务器启动创建 load-on-startup
*ServletConfig配置初始化数据,只能在配置Servlet获得,其他Servlet无法获得 --- 每个Servlet程序都对应一个ServletConfig对象
ServletContext
ServletContext是Servlet上下文对象
每一个工程对应创建单独ServletContext对象,这个对象代表当前web工程
操作ServletContext必须通过ServletConfig获得对象
应用:
1.获得整个web应用初始化参数
<!-- 配置全局初始化参数,所有Servlet都可以访问 --> <context-param> <param-name>hobby</param-name> <param-value>唱歌</param-value> </context-param>
package cn.lsl.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(); //上面写法可以简化一下 ServletContext context = getServletContext(); //读取全局初始化参数 System.out.println(context.getInitParameter("hobby")); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
*ServletConfig对象配置参数,只对配置Servlet有效(ServletConfig每个Servlet对应一个)
ServletContext对象配置参数对所有Servlet都可以访问(ServletContext每个工程(web应该)对应一个)
config参数<init-param>
context参数<context-param>
2.实现全局数据共享
package cn.lsl.servlet; import java.io.IOException; import javax.servlet.ServletConfig; 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 CountServlet extends HttpServlet { @Override public void init() throws ServletException { //想ServletContext保存访问次数 ServletContext context = getServletContext(); //保存数据setAttribute context.setAttribute("visittimes", 0); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //从ServletContext中获得访问次数 ServletContext context = getServletContext(); int times = (Integer)context.getAttribute("visittimes"); //访问次数+1 times++; //将访问次数更新回去 ServletContext context.setAttribute("visittimes", times); System.out.println("网站被访问了一次"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
package cn.lsl.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 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); } }
在ServletContext中,保存站点访问次数,每一个用户访问站点,将访问次数+1
在ServletContext初始化过程中,向ServletContext保存访问次数0,-------ServletContext setAttribute
每次访问次数+1 --- 数据存放ServletContext中,所有Servlet都可以获得该数据
*在ServletContext中保存数据,所有Servlet都可以访问
3.实现服务器端转发功能
什么是转发?转发和重定向区别?
转发:如果服务器端处理客户端请求时,如果需要多个服务器程序同时进行处理,需要采用转发操作,转发操作对客户端一次请求,在服务器端通过多个程序连续执行,进行处理
重定向:服务器进行处理后,需要通知客户端访问下一个目标程序继续处理
区别:
1)转发产生一次请求一次响应,重定向产生两次请求两次响应
2)转发对客户端不可见的(未知),重定向客户端可以察觉到(可见)
3)转发是url显示转发钱资源路径,重定向url显示定向后资源路径
使用转发还是重定向? --- 转发性能好于重定向,请求次数少
转发的例子
eg:
package cn.lsl.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 { 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)){ 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); } }
package cn.lsl.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 { //从ServletContext获得数据 ServletContext context = getServletContext(); int[] times = (int[])context.getAttribute("times"); 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); } }
eg:
package cn.lsl.servlet; import java.io.IOException; import java.io.PrintWriter; 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 AServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("AServlet 执行..."); // 转发 ServletContext context = getServletContext(); // 转发给BServlet 这里 / 当前web 工程 /day05 RequestDispatcher dispatcher = context.getRequestDispatcher("/B"); dispatcher.forward(request, response); // // 重定向 / 代表当前web服务器 // response.setStatus(302); // response.setHeader("Location", "/ServletTest/B"); // 从客户端访问,必须含有工程路径 } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } }
package cn.lsl.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 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); } }
4.读取web工程资源文件
package cn.lsl.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; public class ReadFileServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //读取a1.txt出现WEB-INF/classes String filename2 = "/WEB-INF/classes/a1.txt"; filename2 = getServletContext().getRealPath(filename2); readfile(filename2); //读取a2.txt位于网站根目录 String filename = "/a2.txt"; //通过/开始路径获得绝对磁盘路径 filename = getServletContext().getRealPath(filename); System.out.println(filename); readfile(filename); //因为a1.txt位于/WEB-INF/classes --- 类路径中 --- 通过Class对象读取文件 Class c = ReadFileServlet.class; //返回磁盘绝对路径 String filename3 = c.getResource("/a1.txt").getFile(); System.out.println(filename3); readfile(filename3); } public static void readfile(String filename) throws 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); } }
使用Servlet读取文件,只能读取WebRoot下所有文件 --- 必须使用绝对磁盘路径
通过站点根目录绝对路径 获得磁盘绝对路径 ------ getServletContext().getRealPath(“/WEB-INF/info.txt”)
* 因为 WEB-INF/classes 非常特殊 (存放.class文件目录),被类加载器加载,通过Class类对象读取 该目录下文件
String filename3 = c.getResource("/a1.txt").getFile(); ----- / 代表 /WEB-INF/classes
结论:在web工程中,必须将 文件路径转换绝对磁盘路径 c:\xxx e:\xxx\xxx ----- getServletContext().getRealPath("/xxx"); /代表WebRoot
如果读取文件 恰好位于 WEB-INF/classes ----- 通过 类名.class.getResource("/文件名").getFile(); 获得绝对磁盘路径 / 代表 /WEB-INF/classes
缺省Servlet
缺省Servlet功能:处理其他Servlet都不处理请求
tomcat/conf/web.xml org.apache.catalina.servlets.DefaultServlet作为缺省Servlet
package cn.lsl.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; 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); //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); } }