一、Servlet是sun公司提供的一门用于开发动态web资源的技术,Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),须要完成如下2个步骤:html
1.编写一个Java类,实现servlet接口.2.把开发好的Java类部署到web服务器中.例:java
package com.itcast.servlet; import java.io.IOException; import javax.servlet.*; public class ServletTest extends GenericServlet{ @Override public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { arg1.getOutputStream().write("ServletTest".getBytes()); } }
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <servlet> <servlet-name>ServletTest</servlet-name> <servlet-class>com.itcast.servlet.ServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletTest</servlet-name> <url-pattern>/ServletTest</url-pattern> </servlet-mapping> </web-app>
浏览器中输入http://localhost:8080/工程名(可选)/ServletTest便可看到显示的内容.程序员
ps:web应用目录也就是web根目录.web
一、从浏览器输入http://localhost:8080/servletTest/ServletDemo,到看到hello word!页面的整个访问流程:数据库
1>浏览器根据localhost:8080基于TCP链接,链接到web服务器apache
2>发送http请求到web服务器编程
3>web服务器从客户端发送的HTTP请求中依次解析出客户端要访问的主机、web应用、web资源浏览器
4>根据web.xml文件找到要访问的servlet资源,无论是否是静态资源都走servlet,静态资源由缺省的servlet管理,这个servlet在tomcat中,利用反射建立servlet实例对象,通过测试发现不管多少个客户端,servlet只在第一次被访问时建立一个缓存
5>servlet实例对象调用init方法完成servlet对象的初始化tomcat
6>建立表明请求的request对象和表明响应的response对象(request和response在每次请求时都会建立),而后调用service方法响应客户端请求
7>service执行会向response对象写入向客户端输出的数据
8>服务器从response中取出数据,构建一个HTTP响应,返回给客户端
9>浏览器解析HTTP响应,提取数据显示
二、servlet生命周期
在客户端第一次访问的时候建立servlet对象,执行init方法完成初始化,以后这个servlet对象常驻内存,而后执行service响应客户端请求,每次请求来了都会执行该方法,在服务器关闭的时候执行destory方法摧毁servlet对象.
一、在eclipse中新建一个web project工程,eclipse会自动建立下图所示目录结构:
二、Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet.
HttpServlet指可以处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大.所以开发人员在编写Servlet时,一般应继承这个类,而避免直接去实现Servlet接口.HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法.所以,开发人员在编写Servlet时,一般只须要覆写doGet或doPost方法,而不要去覆写service方法.
一、同一个Servlet能够被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值能够是同一个Servlet的注册名.
二、在Servlet映射到的URL中也可使用*通配符,可是只能有两种固定的格式:一种格式是“*.扩展名”,另外一种格式是以正斜杠(/)开头并以“/*”结尾.(/adc/*.do这种是错误的)这样有时一个URL能够匹配多个映射,例:
三、Servlet是一个供其余Java程序(Servlet引擎也就tomcat服务器)调用的Java类,它不能独立运行,它的运行彻底由Servlet引擎来控制和调度.针对客户端的屡次Servlet请求,一般状况下,服务器只会建立一个Servlet实例对象,也就是说Servlet实例对象一旦建立,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁.在Servlet的整个生命周期内,Servlet的init方法只被调用一次.而对一个Servlet的每次访问请求都致使Servlet引擎调用一次servlet的service方法.对于每次访问请求,Servlet引擎都会建立一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,而后将这两个对象做为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法.那么有个问题大量并发的请求下会建立不少request和response服务器会不会崩掉呢,这里不会,由于request和response的生命周期很短,一闪而过,这样不会同时建立不少的对象.
四、若是在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并建立Servlet的实例对象、以及调用Servlet实例对象的init()方法,中间数字表明启动级别,越小越先启动.
<servlet> <servlet-name>invoker</servlet-name> <servlet-class> org.apache.catalina.servlets.InvokerServlet </servlet-class> <load-on-startup>2</load-on-startup> </servlet>
五、缺省的servlet
若是某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet. 凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理全部其余Servlet都不处理的访问请求. 在<tomcat的安装目录>\conf\web.xml文件中(这个文件是全部web应用共享的),注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet,而且设置了<load-on-startup>1</load-on-startup>,也就是服务器启动的时候就建立了这个servlet.当访问Tomcat服务器中的某个静态HTML文件和图片时,其实是在访问这个缺省Servlet,也就是全部的请求过来都会先访问servlet,若是servlet能与对于的请求URL匹配,则访问servlet资源,若是servlet资源没有与URL匹配的,那么就会去访问这个缺省的servlet,去静态资源中寻找,若是还找不到就会返回客户端找不到资源页面.因此本身不要映射缺省的servlet,不然全部的静态资源将不能访问.
一、当多个客户端并发访问同一个Servlet时,web服务器会为每个客户端的访问请求建立一个线程,并在这个线程上调用Servlet的service方法,所以service方法内若是访问了同一个资源的话,好比同一个文件,数据,数据库等等,就有可能引起线程安全问题.
例1,这个代码是不会有线程问题,由于没有共享资源,每一个线程来了都会建立一个i:
public class ServletTest extends HttpServlet{ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int i=0; i++; resp.getOutputStream().write(("i="+i).getBytes()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
例2,这个代码有线程问题,i是共享资源,
public class ServletTest extends HttpServlet{ int i=0; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { i++; resp.getOutputStream().write(("i="+i).getBytes()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
例3:这个代码也有线程问题,静态变量是共享的,person类里面有一个静态的age
public class ServletTest extends HttpServlet{ int i=0; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Person p = new Person(); p.age++; resp.getOutputStream().write(("p.age"+p.age).getBytes()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
ps:这里面就有一个可能引起内存泄露的问题,好比Person里面有一个static的list,每一个线程来了add一个元素,过段时间系统就会崩掉,这种静态的集合元素很容易引起内存泄露.
二、解决方法
1>在共享资源上加同步块,可是不使用这种状况,不然客户端可能等很长时间.
2>servlet实现SingleThreadModel接口(这时一个标记接口,里面啥都没有),可是servlet引擎在处理客户端请求的时候会针对每次请求建立一个servlet,天然没有线程问题,可是很明显时间久了会使内存崩溃,并且还有问题以下:
public class ServletTest extends HttpServlet implements SingleThreadModel{ int i=0; @Override//该子类方法不能抛出比父类更多的异常,因此不少异常必须处理 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { i++; resp.getOutputStream().write(("i="+i).getBytes()); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } }
多个客户端访问,看到的i都是1,很明显不是咱们想要的,因此这种方式已经废弃了,到目前为止,其实servlet尚未很好的处理线程问题.
一、ServletConfig
在Servlet的配置文件中,可使用一个或多个<init-param>标签为servlet配置一些初始化参数.当servlet配置了初始化参数后,web容器在建立servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet(实际上是一个servlet一个ServletConfig).进而,程序员经过ServletConfig对象就能够获得当前servlet的初始化参数信息.例:
//ServletConfig用于封装servlet的配置 public class ServletConfigTest extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获得一个参数 String value=this.getServletConfig().getInitParameter("data"); //获得全部的 Enumeration e= this.getInitParameterNames(); while(e.hasMoreElements()){ String a=(String) e.nextElement(); String v=this.getServletConfig().getInitParameter(a); System.out.println(a+"="+v); } } }
<servlet> <servlet-name>ServletDemo5</servlet-name> <servlet-class>cn.itcast.ServletDemo5</servlet-class> <init-param> <param-name>data</param-name> <param-value>xxxx</param-value> </init-param> <init-param> <param-name>data2</param-name> <param-value>yyyy</param-value> </init-param> </servlet>
ps:这个对象的用处是有些数据不要程序中写死,能够经过配置的形式传递个程序,这样之后修改的话不用从新修改源代码编译,直接修改配置文件便可,并且写在配置中方便查找.
二、ServletContext
WEB容器在启动时,它会为每一个WEB应用程序都建立一个对应的ServletContext对象,它表明当前web应用,在服务器关闭时销毁,与具体的请求没法,注意这里与ServletConfig生命周期的区别.ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,能够经过ServletConfig.getServletContext方法得到ServletContext对象.因为一个WEB应用中的全部Servlet共享同一个ServletContext对象,所以Servlet对象之间能够经过ServletContext对象来实现通信.ServletContext对象一般也被称之为context域对象(ServletContext实际上是一个容器,这个容器的做用范围是web应用程序范围).拿到ServletContext的两种方式以下:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获得servletContext ServletContext context=this.getServletConfig().getServletContext(); ServletContext context2=this.getServletContext();//可直接拿到 }
三、servletContext的应用场景
一、多个Servlet经过ServletContext对象实现数据共享,例:
public class ServletDemo8 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String value = (String) this.getServletContext().getAttribute("data"); response.getOutputStream().write(value.getBytes()); } } public class ServletDemo7 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.getServletContext().setAttribute("data",123); } }
ps: servletContext的这个应用场景能够用在聊天室中,具体写法,略.
二、获取WEB应用的初始化参数
web.xml中使用context-param配置,而后使用相似ServletConfig获取配置的getInitParameter,getInitParameter这俩个方法来拿到参数,相似数据库等数据能够这样配置.
<web-app> <context-param> <param-name>data</param-name> <param-value>zzzz</param-value> </context-param> </web-app>
三、实现Servlet的转发(转发客户端只有一次请求,重定向有屡次)
由于servlet中不适合作输出,要使用一行一行write,通常转发给jsp去处理,开发中大量使用,例:
public class ServletDemo10 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data="aaaaaaaaaa";
//目前只学了这种,可是有线程问题
this.getServletContext().setAttribute("data", data); RequestDispatcher rd=this.getServletContext().getRequestDispatcher("/1.jsp"); rd.forward(request, response); } }
<html> <body> <font color="red"> <% String data = (String)application.getAttribute("data");//application就是ServletContext out.write(data); %> </font> </body> </html>
ps:线程问题,当多个客户端并发访问同一个Servlet时,web服务器会为每个客户端的访问请求建立一个线程,若是他们都访问ServletContext时,这个ServletContext就是一个共享资源,会有线程问题,好比多个线程同时set一个key为data的数据,那么后一个线程可能覆盖前一个线程的值,从而出现问题,因此开发中这里通常使用request域.
四、利用ServletContext对象读取资源文件
1>第一种方式
private void test1() throws IOException { //注意目录的正确书写 InputStream in=this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties"); //这时读取properties的固定步骤 Properties props=new Properties(); props.load(in); System.out.println(props.getProperty("url")); }
二、只用传统方式以下是不行的,以下:
private void test4() throws IOException { //这种相对路径是tomcat的bin目录,因此这种方法要在该目录下创建文件夹classes,并把文件放在这里 FileInputStream in2=new FileInputStream("classes/db.properties"); Properties props=new Properties(); props.load(in2); System.out.println(props.getProperty("url")); }
三、先获得绝对路径,在用传统方式是能够的,这种相对于第一种方式来讲也是有需求的,好比下载中要得到文件的名称,第一种方式直接转为了留没法得到,而这种经过截取path便可获得文件名.
private void test5() throws IOException { //获得绝对路径 String path=this.getServletContext().getRealPath("/WEB-INF/classes/db.properties"); FileInputStream in2=new FileInputStream(path); Properties props=new Properties(); props.load(in2); System.out.println(props.getProperty("url")); }
ps:上面是在servlet中经过ServletContext来读取资源文件,可是实际开发中像加载数据库等配置文件是不会在servlet中来读取的,通常在dao中读取,这时按以下方式读取,不能传递ServletContext对象到dao层,破坏了软件编程思想.
//若是读取资源文件的程序不是servlet的话, //就只能经过类转载器去读了,文件不能太大,太大会直接撑破内存,那么大文件怎么读取呢? //用传递参数方法很差,耦合性高 public class UserDao { private static Properties dbconfig=new Properties(); static { //方式一,这种类加载器只会加载一次,若是后期db.properties修改的话程序没法读到 InputStream in=UserDao.class.getClassLoader().getResourceAsStream("db.properties"); try { dbconfig.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(e); } //方式二,后期db.properties修改的话程序能够读到(建议使用) URL url=UserDao.class.getClassLoader().getResource("db.properties"); String str=url.getPath(); //file:/C:/apache-tomcat-7.0.22/webapps/day05/WEB-INF/classes/db.properties try { InputStream in2=new FileInputStream(str); try { dbconfig.load(in2); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } catch (FileNotFoundException e1) { throw new ExceptionInInitializerError(e1); } } public void update() { System.out.println(dbconfig.get("url")); } }
注:对于不常常变化的数据,在servlet中能够为其设置合理的缓存时间值,以免浏览器频繁向服务器发送请求,提高服务器的性能.略