1、什么是Servlet?
Servlet是javax.servlet包下的一个接口
而Servlet程序是我们自定义的java类,它必须实现Servlet接口,或者继承GenericServlet类,或者继承HttpServlet类
Servlet和GenericServlet都位于javax.servlet包下
HttpServlet位于javax.servlet.http包下
Servlet是JavaWeb三大组件之一
JavaWeb三大组件:Servlet、Filter、Listener
问题:我们在浏览器中可以访问html,但是怎么访问一个java类呢?
html是静态资源(它里面是不可以定义变量的),它可以直接被浏览器解析,但是jsp不行,因为它里面有变量,必须被服务器变成html才能被解析。
Servlet是动态资源。
浏览器发送的是一个请求路径,而服务器端的Servlet是一个java类,那么如何让请求路径和java类联系在一起呢?
Servlet必须在web.xml中进行配置,才能被访问(就是把一个Servlet与一个或多个路径绑定在一起)。
如果要访问的路径在服务器中没有对应的servlet,就会出现404(找不到资源)。
-----------------------------------------------------------------------------------------------------------
2、实现Servlet
实现servlet有3种方式:
1)实现Servlet接口
2)继承GenericServlet类
3)继承HttpServlet类(最佳选择)
补充:我们说过,创建Servlet就必须实现Servlet接口,那么为什么继承GenericServlet和HttpServlet类也可以创建Servlet?因为这两个类都实现了Servlet接口。
GenericServlet和HttpServlet都是抽象类。
Servlet、GenericServlet以及HttpServlet三者之间的关系:
GenericServlet类实现了Servlet接口,
HttpServlet继承了GenericServlet类。
Servlet接口里有5个方法:
1)void init(ServletConfig config)
2)void service(ServletRequest req,ServletResponse res)
3)void destroy()
4)ServletConfig getServletConfig()
5)String getServletInfo()
其中service方法会在用户请求时自动被调用。
我们只关心其中的3个生命周期方法。
HelloWorld程序:
使用记事本编写一个Servlet的Hello world程序
1)写一个Servlet类
继承Servlet接口,重写接口中的5个方法,在service方法中打印“Hello Servlet!”
2)web.xml配置
核心配置如下:
//配置访问路径所对应的Servlet类
<servlet>
<servlet-name>abc</servlet-name>
<servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>
</servlet>
//配置要访问的路径
<servlet-mapping>
<servlet-name>abc</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
大家要永远记住:Myeclipse只是编译环境,而Tomcat才是运行环境。
--------------------------------------------------------------------------------------------------------
3、Servlet生命周期
Servlet实例由Tomcat创建,并且Servlet接口中的所有方法(包括生命周期方法)都由服务器来调用。
Servlet接口中一共有5个方法,其中有3个生命周期方法。
1)void init(ServletConfig config):这个方法会在Servlet实例被创建后,马上被调用。而且只被调用一次,可以对Servlet进行一些初始化工作,如果不需要初始化的话,这个方法空着就可以了。
假如我们的应用有N个Servlet程序,分别为:
Servlet_1
Servlet_2
Servlet_3
...
...
...
Servlet_N
当我们访问Servlet_1时,如果它是第一次被访问,就会创建一个Servlet_1的实例,并立即调用init方法对其进行初始化。如果不是第一次访问,就不会再创建Servlet_1的实例,也不会去调用init方法了。除非在服务器重启后,才会循环这个过程。
同理,当我们访问Servlet_2时,服务器首先判断它是不是第一次被访问,如果是,就创建一个Servlet_2的实例,并立即调用init方法对其进行初始化。如果不是,就不再创建Servlet_2的实例,直接使用第一次创建的Servlet_2实例。
以此类推,当请求Servlet_N时,过程同上。
2)void destroy():在Servlet被销毁之前被调用,而且只被调用一次。如果有一些需要释放的资源可以在这里做。
Servlet被创建的时间点有2个:Servlet默认情况下是在第一次被请求时会被创建,也可以在Tomcat启动时被创建,前提是需要在web.xml中进行配置。
3)void service(ServletRequest req,ServletResponse resp):在每次被请求时被调用,会被调用0—N次。
Servlet被销毁的时间:Tomcat关闭时。
Servlet另外两个方法:
String getServletInfo():获取关于servlet的说明性信息。
ServletConfig getServletConfig():它和init方法的参数类型相同,可以获取web.xml中Servlet的如下配置信息。
<servlet>
<servlet-name>abc</servlet-name>
<servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>
</servlet>
注意:这里我们发现有很多都是接口,比如Servlet、ServletConfig、ServletRequest、ServletResponse,那么这些接口是谁提供的呢?这些接口是由JavaEE(JavaWeb)提供的规范,而这些规范大部分是由Tomcat实现的。Tomcat不支持JavaEE规范,但是它支持JavaEE中的JavaWeb这部分规范。后期我们要学习的Weblogic和JBoss商用服务器都支持JavaEE规范(这些服务器可以写EJB程序)。
JavaEE规范是非常大的东西,它包含了很多规范,比如Jaxp、JNDI、JDBC、EJB、Servlet等。其中EJB指的是“企业级javaBean”,现在过时了,已经被SSH框架取代了。
扩展阅读:
最早,在Servlet诞生之前,我们会通过编写CGI程序来实现数据在Web间的传输,但是,对于客户端做出的每一个请求,都必须创建CGI程序的一个新实例,这将占用大量内存,因此,为了解决这个问题,引入了Servlet技术。
对于所有的客户端请求,只需要创建Servlet的实例一次,因此节省了大量的内存。Servlet在初始化后就会驻留在内存中。因此每次做出请求时无需加载。
一旦请求了一个Servlet,就没有办法阻止容器执行一个完整的生命周期。容器在Servlet首次被调用时创建它的一个实例,并保存该实例在内存中,让它对所有的请求进行处理。
Servlet在执行时,不是一直停留在内存中,服务器会自动将停留时间过长一直没有执行的Servlet从内存中移除,至于停留时间的长短,通常和选用的服务器有关。
-----------------------------------------------------------------------------------------------
4、Servlet相关类介绍
ServletRequest 和 ServletResponse是Servlet接口中service方法的两个参数,该方法由Tomcat调用,所有这两个参数都是由Tomcat提供。
ServletRequest request 请求对象
Tomcat会创建request对象,并将客户端的请求数据封装到request对象中,我们如果需要请求数据就找这个对象即可
ServletResponse response 响应对象
Tomcat会创建response对象,并将数据响应给客户端(为什么不直接说响应给浏览器?因为这个客户端可能是浏览器也可能是一个java程序,比如Socket程序,还有可能是一个Android程序)
ServletConfig config 这是Servlet接口中init方法的参数,用来记录web.xml中通过<init-param>标签配置的初始化参数信息。比如:
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>
//配置初始化参数信息,参数可以配置多个
<init-param>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>abc</param-name>
<param-value>ABC</param-value>
</init-param>
</servlet>
ServletConfig接口有4个方法:
(1)String getInitParameter(String name)
通过此方法获取web.xml中的参数配置信息:
1)在类中首先定义属性
ServletConfig config;
2)在init方法中将参数中的config对象保存起来
this.config = config;
3)在service方法中获取配置信息
String charset = config.getInitParameter("charset");
System.out.println(charset);
(2)Enumeration getInitParameterNames()
通过该方法可以获取所有的初始化参数名称。
//获取所有的参数名
Enumeration e = config.getInitParameterNames();
//遍历每一个参数名
while(e.hasMoreElements()){
String name = (String)e.nextElement();
//根据参数名获取值
String value = config.getInitParameter(name);
System.out.println(name + "=" + value);
}
(3)String getServletName()
它返回的是<servlet-name>元素的值
(4)ServletContext getServletContext()
它可以获取Servlet上下文对象。
ServletContext 这是Servlet上下文,可以在多个Servlet之间共享数据。这种可以在多个Servlet之间传递数据的类我们通常称之为“域对象”。Servlet中有三大域对象。
5、GenericServlet内部结构
GenericServlet是一个抽象类,它实现了Servlet接口和ServletConfig接口
1)它代理了ServletConfig的所有功能,所有使用ServletConfig才能调用的方法,都可以使用GenericServlet的同名方法来完成。
2)不能覆盖父类的init(ServletConfig config)方法,因为在父类中该方法内完成了this.config = config,其他的所有ServletConfig的代理方法都使用this.config来完成。一旦覆盖,那么this.config就是null。
3)如果我们需要做初始化工作,那么可以去覆盖GenericServlet提供的init()方法。
注意:init(ServletConfig config)有参方法是ServletConfig的方法,而init()无参方法才是GenericServlet的方法。
-----------------------------------------------------------------------------------------------
6、HttpServlet内部结构
它提供了一些与http协议相关的功能。它有7个处理请求的doXxx()方法(分别对应着7种请求方式)。还有2个同名service方法。我们不必覆盖service方法,只要覆盖doGet或doPost方法即可,如果没有覆盖这两个方法中的任意一个,默认都会就会响应405。
补充知识:我们知道,http请求有两种方式,Get和Post。在浏览器地址栏中输入地址的方式是Get方式,所有的链接都是Get方式,表单 默认也是Get方式,唯有在表单中指定method=“post”才是Post方式。
关于状态码,2开头表示成功,3开头表示中转,4开头表示客户端错误,5开头表示服务器错误。
(1)void service(ServletRequest, ServletResponse){
1.把参数强转
2.调用下面的service方法
}
(2)void service(HttpServletRequest, HttpServletResponse){
判断当前请求方式
如果是Get请求,调用doGet()
如果是Post请求,调用doPost()
}
(3)void doGet(HttpServletRequest, HttpServletResponse){
如果不覆盖此方法,则返回405
}
(4)void doPost(HttpServletRequest, HttpServletResponse){
如果不覆盖此方法,则返回405
}
请求的流程:Tomcat会首先调用(1)中的service方法,该service方法会调用(2)中的service方法,而(2)中的service方法会根据请求的方式来调用(3)中的doGet方法或(4)中的doPost方法。
应用:通过302转到百度首页。
创建一个类,继承HttpServlet,只需重写它的一个doGet方法。方法如下:
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOEception {
System.out.println("doGet()方法......");
resp.setStatus(302); //设置响应吗
resp.setHeader("Location","http://www.baidu.com"); //设置响应头
}
}
--------------------------------------------------------------------------------------------------------
7、Servlet是单例的
对于每一个Servlet,都是一些.class文件,Tomcat会对这些class文件作缓存。就是说它会先保存起来,我们大致可以将缓存看做是一个Map<String,Servlet>结构。当接受到请求,首先会查看当前Servlet是否存在,如果存在,直接拿来调用service方法,如果不存在,找到其类创建对象,保存到缓存中,再调用service方法。
单例就存在一个问题,线程问题。也就是多个线程会不会同时操作一个对象。这个对象本身是一个实体类型,既能存数据又能取数据的对象我们称之为存储类型的对象。比如:
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
像这种可读可写的类,比如name属性,在多个线程同时操作name属性时,就会出现安全问题。比如一开始往里面存了个值叫”张三“,可是读的时候却是”李四“,这就是不安全。因为在你刚存完之后,在你读之前,有人将这个name的值替换了。
同样,集合也有存储的特性。
我们的对象大致可以分成两种,一种是有存储能力的,就是存储类型的对象,另一种全是功能,没有任何数据可存,叫功能型对象。对于功能型的对象,不用担心线程安全问题。
比如下面这个类就是功能型的类:
class Utils{
public void fun(){
System.out.println("hello world!!");
}
}
Servlet是单例的,所以一个Servlet对象可能同时处理多个请求。
Servlet不是线程安全的,尽可能不要创建成员变量,因为成员变量会被多个线程共享。
如果非要创建,那么创建功能性的,只读!(只能获取,不能修改)
那么,为什么不给service方法用同步块括起来呢?因为括起来会出现站排(排队)的现象。一旦同步,就会觉得卡。当访问量很大的时候,一个服务器忙不过来,就会采用集群。一旦集群还有个问题,这些请求都是瞎子,闭着眼睛排队,它并不知道它要请求的是哪一个服务器。这样就会出现,有的服务器很忙,而有的很闲的现象。就好比在超市结账,有好几个窗口,而大家都在一个窗口排队,分开排队多好。这就需要一个负载均衡问题。
后面我们会学到三大框架的struts2,它里面有个和Servlet很相似的东西,就是action。但action是多例的,就不会出现线程安全问题。就好比说你有一个servlet,我有一个servlet,你用你的,我用我的,互不干扰。
servlet的创建时间
它可以在第一次请求时被创建,还可以在容器启动时被创建。默认是第一次请求时创建。那么启动时创建有什么好处呢?JavaWeb的发展有一个漫长的历史,最早的时候,JavaWeb并没有三大组件,它只有Servlet一个组件,它在服务器启动时创建是为了做Filter或Listener的工作。但是现在,这种启动时创建就没有意义了,但我们得学它。
等我们学了Filter和Listener你就会明白,早期那些本来可以用现在的Filter来完成的工作,可当时为什么偏偏要用Servlet来完成呢?因为当初没有Filter,这是一个发展的过程。你没有经历过那个时代,所以你不知道。
好比说框架当中有一个struts1的东西。它有一个核心的控制器是一个Servlet。而struts2的时候它改成了Filter。所以你可能会认为struts1中的控制器应该用Filter来完成,可它为什么要使用Servlet呢?在一些已经成型的东西当中,你会发现一些不合理的东西就是这个原因。
在服务器启动时创建Servlet的设置:
在web.xml中,在<servlet>元素中添加一个<load-on-startup>大于等于0的整数</load-on-startup>这样的元素。设置好之后,我们可以在一个Servlet中通过init方法来验证该Servlet是否在服务器启动时被创建。
如果有多个Servlet在容器启动时创建,那么<load-on-startup>中的值越小,优先级就越高,就会先被创建。当我们有多个Servlet时,就可以使用它的值对多个Servlet进行排序。
那么谁先启动,对我们有什么意义吗?早期可能会比较依赖这种东西,那时会拿Servlet当Filter来用,谁先执行可能会比较重要,但今天就没有这个必要了。
<url-pattern>通配符
<url-pattern>是<servlet-mapping>的子元素,用来指定Servlet的访问路径,即URL。它必须是以”/“开头!
1)可以在<servlet-mapping>中给出多个<url-pattern>,例如:
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/AServlet</url-pattern>
<url-pattern>/BServlet</url-pattern>
</servlet-mapping>
这样,一个Servlet绑定了两个URL,无论访问/AServlet还是/BServlet,访问的都是AServlet。那么这有什么意义吗?没有任何意义。只有在早期版本使用Servlet充当Filter的时候才会有用。后面我们学习Filter的时候你就会发现,我们经常会使用一个Filter来处理一大堆的路径。
当<url-pattern>有很多时,怎么办呢?
2)还可以在<url-pattern>中使用通配符”*“星号。”*“星号可以匹配任何URL前缀和后缀,使用通配符可以命名一个Servlet绑定一组URL,例如:
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/servlet/AServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
如果我们的项目名是day09_2,那么请问,如果我们在浏览器中访问http://localhost:8080/day09_2/servlet/AServlet 访问的是哪一个Servlet呢?如果按优先级排的话,那么谁会先执行呢?
会执行AServlet而不会执行BServlet,因为AServlet更明确。
如果访问http://localhost:8080/day09_2/servlet/hello 的话,访问的就是BServlet。
<url-pattern>中可以使用”*“表示所有字符,但它不匹配”/“。(包含”/“的字符它是不能匹配的)
通配符的使用要求:
1)它要么在开头,要么在结尾,不能再中间。
2)如果不使用通配符,那么必须使用”/“开头。
举例:
/* 正确
*.action 正确
/*.jsp 错误
/abc/def/* 正确
/*/*/ 错误
如果一个访问路径,匹配了多个<url-pattern>,那么谁更加明确就匹配谁。
比如:
/*
/abc/*
/abc/AServlet
----------------------------------------------------------------------------------------
8、web.xml的继承
每一个项目都有一个web.xml,但是Tomcat安装目录下也有一个web.xml
在${CATALINA_HOME}/conf下有4大配置文件:
context.xml
server.xml
tomcat-users.xml
web.xml
说明:CATALINA_HOME是Tomcat的安装目录
${CATALINA_HOME}/conf/web.xml是所有项目下web.xml文件的父文件,父文件中的内容等同于写在子文件中。就好比父类的方法等同于写在子类中一样。
下面我们就列举一些${CATALINA_HOME}/conf/web.xml中的配置。
在该web.xml中有如下的配置:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
当我们在浏览器中访问WebRoot下的index.jsp时,会首先经过上面配置中<url-pattern>*.jsp</url-pattern>进行匹配。然后就会找到<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>,并执行JspServlet。JspServlet会把你请求路径中的”/index.jsp“通过匹配就找到了WebRoot下的index.jsp。最后会对这个jsp页面进行加工处理转成html文件,再通过response对象发送到浏览器。
所有对jsp的请求,都会执行JspServlet,它会对jsp文件进行处理,最终发送给客户端的是html代码。
在该web.xml中还有如下的配置:
<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>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
上面配置中的<url-pattern>/</url-pattern>表示所有,它的匹配级别较低一些,当所有的都匹配不上时才会执行这个DefaultServlet。它对所有静态资源进行处理,比如:html、htm、CSS、js等。
所有的请求都不会直接访问到指定的资源,所有的请求都会首先被Tomcat服务器接收,然后再通过Servlet找到指定的资源。
在该web.xml中还有如下的配置:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
这是session的最大不活动时间,单位是分钟。比如,你登陆某网站,如果30分钟没有任何操作就会超时,会提示你重新登陆。
此外,在该web.xml中还有很多很多类似下面的配置:
<mime-mapping>
<extension>jpg</extension>
<mime-type>image/jpeg</mime-type>
</mime-mapping>
每一个<mime-mapping>都包含一个扩展名和一个类型名,这是一个扩展名和一个类型之间的映射。比如在上面的这一条配置中,jpg是扩展名(文件的后缀),而image/jpeg是类型。
假如我们只知道文件的后缀名而不知道其媒体类型,就可以通过某种方式根据后缀名得到其对应的媒体类型。
Mime类型(或媒体类型)是网络上的一种资源的类型,所有的资源都有其对应的类型。我们在给浏览器发送数据的时候,就需要告诉浏览器我们发送的是什么类型的数据。
最后,还有一组配置:
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
这是指定欢迎页面的配置,即:当index.html不存在是就访问index.htm,当index.htm不存在时就访问index.jsp。
总结:由于Web.xml的继承,上面的这些配置就会写在我们的每一个项目下的web.xml中。
---------------------------------------------------------------------------------------
9、ServletContext的域功能
它就是一个公共资源的存储区域,可以在多个Servlet之间共享数据。一个Servlet往里面存的数据,另一个Servlet可以从里面获取该数据。ServletContext本身虽不是一个map集合,但它内部有一个map集合结构,所以可以存取数据。
Servlet有3大域对象:
1)ServletContext:范围最大,整个web应用范围
2)HttpSession:会话范围
3)HttpServletRequest:请求范围
后面我们学到jsp就会有4大域对象,比Servlet多出来一个。
域对象都有存取的功能:
1)setAttribute(String name,Object obj);添加属性(put)
2)Object getAttribute(String name); 获取属性(get)
3)Enumeration getAttributeNames(); 获取所有属性名称
4)removeAttribute(String name); 删除属性(remove)
ServletContext除了上面的存取功能之外,还有获取应用初始化参数的功能。
这里的应用初始化参数不是前面学的Servlet的初始化参数。一个Servlet配置的初始化参数,另一个Servlet是无法获取的。
这个应用初始化参数是ServletContext自己的初始化参数。
在项目的web.xml中做如下配置:
<context-param>
<param-name>p1</param-name>
<param-value>v1</param-value>
</context-param>
<context-param>
<param-name>p2</param-name>
<param-value>v2</param-value>
</context-param>
获取上面的应用初始化参数的方法有2个:
在ServletContext中有2个方法
(1)String getInitParameter(String name):通过参数名获取参数值
(2)Enumerarion getInitParameterNames():获取所有参数的名称
注意:
this.getInitPrameter("xxx")和this.gerServletContext().getInitParameter("xxx")的区别
前者获取的是Servlet的初始化参数信息,后者获取的是ServlerContext的初始化参数信息
ServletContext还有获取资源的功能。
ServletContext的功能:
1)存取域属性的功能
2)获取应用初始化参数的功能
3)获取资源的功能
ServletContext的生命周期:
ServletContext在容器(Tomcat)启动时被创建,在容器关闭时被销毁。而且,一个项目只有一个ServletContext对象。
ServletContext的生命周期最长,与Servlet的生命周期做一下比较。每一个Servlet除了可以通过配置后在容器启动时被创建之外,默认都是在第一次被请求时被创建,不请求永远不会被创建。
获取ServletContext的几种方式:
1)通过ServletConfig的getServletContext()来获取
在Servlet接口中有个init(ServletConfig config)方法,方法的参数是ServletConfig接口,这个ServletConfig接口中有一个getServletContext()方法,可以得到ServletContext。但是,这种方式只能是在init方法里面获取。
2)在GenericServlet的子类中通过this.getServletContext()来获取
GenericServlet代理了ServletConfig的所有方法,而且还提供了getServletConfig(),所以在GenericServlet的子类中可以使用以下方式来获取ServletContext对象
(1)this.getServletContext()
(2)this.getServletConfig().getServletContext()
3)通过HttpSession中的session.getServletContext()获取
4)后期我们学习过滤器和监听器时也有获取ServletContext的方式
通常情况下,我们会这样获取ServletContext:
首先创建一个Servlet类,继承HttpServlet,重写doGet或doPost方法,在doGet或doPost方法内部使用this.getServletContext()来获取ServletContext。
ServletContext的举例
我们创建两个Servlet,分别是AServlet和BServlet。在AServlet中往ServletContext中保存一个字符串”张三“,然后在BServlet中从ServletContext获取该字符串并显示到浏览器。代码如下:
AServlet代码:
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//向ServletContext中保存信息
this.getServletContext().setAttribute("name", "张三");
//设置响应编码
resp.setHeader("Content-type", "text/html;charset=utf-8");
//向浏览器显示信息
resp.getWriter().print("<h1>保存成功!</h1>");
}
}
BServlet代码:
public class BServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//从ServletContext中获取信息
String name = (String) this.getServletContext().getAttribute("name");
//设置响应编码
resp.setHeader("Content-type", "text/html;charset=utf-8");
//向浏览器显示信息
resp.getWriter().print("<h1>"+name+"</h1>");
}
}
-------------------------------------------------------------------------------------------------------
10、ServletContext获取资源的功能
问题:
假如在我们有2个文件:a.txt和b.txt,它们分别位于不同的目录下。其中a.txt在WebRoot目录下,b.txt在WEB-INF目录下。请问该如何获取这两个文件呢?
这里有如下几个方法:
1)获取真实路径 String getRealPath(String path)
参数String path是一个相对路径,必须以”/“开头,”/“表示当前项目所在路径。
那么请问,我们的当前项目所在路径在哪里?是在我们的工作空间workspace中项目目录吗?
不是,大家千万注意,MyEclipse只是编译环境,运行环境是Tomcat。
我们的当前项目所在路径应该到Tomcat下去找。
什么是真实路径?
真实路径是以盘符开头的。
请问 http://localhost:8080/helloservlet/AServlet 这种是真实路径吗?
不是,这不是服务器端的真实路径,这是虚拟的。我们所说的真实路径指的是资源位于服务器的哪一个盘符的哪一个文件夹下。它是随着我们的Tomcat的位置而变化的。
注意:WEB-INF是我们唯一的一道安全防线,它里面的的东西通过浏览器是不能直接访问的。它里面存放的都是极其重要的文件,比如字节码文件,jar包,配置文件等。在实际开发中,为了安全,我们常常把jsp文件也放进WEB-INF下。
现在我们通过一个Servlet程序来获取a.txt和b.txt文件的真实路径:
public class CServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//获取a.txt文件的真实路径
String apath = this.getServletContext().getRealPath("/a.txt");
System.out.println(apath);
//获取b.txt文件的真实路径
String bpath = this.getServletContext().getRealPath("/WEB-INF/b.txt");
System.out.println(bpath);
}
}
相对路径的最大的好处在于,当我们的项目从一位置迁移到了另一个位置,访问资源时不会出错。
2)获取指定文件夹下所有资源的路径 Set getResourcePaths(String path)
public class DServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Set<String> paths = this.getServletContext().getResourcePaths("/WEB-INF");
for (String string : paths) {
System.out.println(string);
}
}
}
上面的程序输出结果为:
/WEB-INF/lib/
/WEB-INF/classes/
/WEB-INF/b.txt
/WEB-INF/web.xml
3)获取资源流 InputStream getResourceAsStream(String path)
如果我把一个资源文件对应的流给你了,是不是相当于把这个文件给你一样?你是不是就可以通过流把数据读到内存当中?
这就要用到IO流来读取文件。如何使用IO流来读取文件,那是基础班的东西,在就业班我们一律使用工具。
我们为什么要使用工具?
你要是自己写个循环往外读,说不好听的,你要是哪里写错一点点,够你调几个小时的。我们有现成的工具。
这里我们要使用到一个Apache的commons组件:commons-io-1.4.jar
Apache的commons组件被称为Java的第二API。
我们将commons-io-1.4.jar拷贝到WebRoot下的lib目录中,程序代码如下:
public class EServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
InputStream in1 = this.getServletContext().getResourceAsStream("/a.txt");
InputStream in2 = this.getServletContext().getResourceAsStream("/WEB-INF/b.txt");
//读出a.txt文件中文本
String s1 = IOUtils.toString(in1);
//读出b.txt文件中的文本
String s2 = IOUtils.toString(in2);
//输出文本内容
System.out.println(s1);
System.out.println(s2);
}
}
--------------------------------------------------------------------------------
11、通过Class和ClassLoader来获取类路径下的资源
1)使用ClassLoader获取类路径下的资源
假如我们想要创建一个User对象,那么必须先有Class<User>,那么就必须先有User.class,那么就必须先有User.java。
那么User.class是如何变成Class<User>的呢?这就需要ClassLoader。它的作用就是把硬盘上的User.class加载到内存中,变成一个Class对象。这就是类加载器。
public class EServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//相对于classes
InputStream in1 = this.getClass().getClassLoader().getResourceAsStream("c.txt");
System.out.println(IOUtils.toString(in1));
InputStream in2 = this.getClass().getClassLoader().getResourceAsStream("cn/itcast/servlet/d.txt");
System.out.println(IOUtils.toString(in2));
}
}
注意:上面程序中的c.txt是相对于classes路径(类路径),c.txt前面可以加”/“,也可以不加”/“,结果都一样。
即:
c.txt 等同于 /classes/c.txt
cn/itcast/servlet/d.txt 等同于 /classes/cn/itcast/servlet/d.txt
2)使用Class获取类路径下的资源
那么以上两种方式有什么区别呢?
最大的区别是:
ClassLoader方式的路径是相对于classes目录;
而Class方式的路径如果不加”/“是相对于.class字节码文件所在目录,如果前面加”/“则相对于classes目录,这时它与ClassLoader方式是一样的。
假如我们在项目cn.itcast.servlet包下创建aaa.txt文件,在src下创建bbb.txt文件
public class GServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//不加”/“,相对于字节码文件所在目录
InputStream input1 = this.getClass().getResourceAsStream("aaa.txt");
System.out.println(IOUtils.toString(input1));
//加”/“则相对于classes目录
InputStream input2 = this.getClass().getResourceAsStream("/bbb.txt");
System.out.println(IOUtils.toString(input2));
}
}
---------------------------------------------------------------------------------------
12、统计网站的访问量
”代码是思想的体现“,这句话很重要。没有思路就没有步骤,没有步骤就没有代码。越往后越能体现出来这句话的重要性。
public class Servlet1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//从ServletContext中获取访问次数
Integer cont = (Integer) this.getServletContext().getAttribute("cont");
//如果没有人访问,则次数设置为1
if (cont == null) {
cont = 1;
}else{
//如果访问次数不为空,则次数累加
cont += 1;
}
将累加后的值保存到ServletContext中
this.getServletContext().setAttribute("cont", cont);
//设置响应编码
response.setContentType("text/html;charset=utf-8");
//向浏览器输出信息
response.getWriter().print("这是Servlet1");
response.getWriter().print("当前访问次数为:"+cont);
}
}
后面我们会考大家一道题:统计你是第几个访问本网站的人。这就相对比较有难度了。
Servlet是javax.servlet包下的一个接口
而Servlet程序是我们自定义的java类,它必须实现Servlet接口,或者继承GenericServlet类,或者继承HttpServlet类
Servlet和GenericServlet都位于javax.servlet包下
HttpServlet位于javax.servlet.http包下
Servlet是JavaWeb三大组件之一
JavaWeb三大组件:Servlet、Filter、Listener
问题:我们在浏览器中可以访问html,但是怎么访问一个java类呢?
html是静态资源(它里面是不可以定义变量的),它可以直接被浏览器解析,但是jsp不行,因为它里面有变量,必须被服务器变成html才能被解析。
Servlet是动态资源。
浏览器发送的是一个请求路径,而服务器端的Servlet是一个java类,那么如何让请求路径和java类联系在一起呢?
Servlet必须在web.xml中进行配置,才能被访问(就是把一个Servlet与一个或多个路径绑定在一起)。
如果要访问的路径在服务器中没有对应的servlet,就会出现404(找不到资源)。
-----------------------------------------------------------------------------------------------------------
2、实现Servlet
实现servlet有3种方式:
1)实现Servlet接口
2)继承GenericServlet类
3)继承HttpServlet类(最佳选择)
补充:我们说过,创建Servlet就必须实现Servlet接口,那么为什么继承GenericServlet和HttpServlet类也可以创建Servlet?因为这两个类都实现了Servlet接口。
GenericServlet和HttpServlet都是抽象类。
Servlet、GenericServlet以及HttpServlet三者之间的关系:
GenericServlet类实现了Servlet接口,
HttpServlet继承了GenericServlet类。
Servlet接口里有5个方法:
1)void init(ServletConfig config)
2)void service(ServletRequest req,ServletResponse res)
3)void destroy()
4)ServletConfig getServletConfig()
5)String getServletInfo()
其中service方法会在用户请求时自动被调用。
我们只关心其中的3个生命周期方法。
HelloWorld程序:
使用记事本编写一个Servlet的Hello world程序
1)写一个Servlet类
继承Servlet接口,重写接口中的5个方法,在service方法中打印“Hello Servlet!”
2)web.xml配置
核心配置如下:
//配置访问路径所对应的Servlet类
<servlet>
<servlet-name>abc</servlet-name>
<servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>
</servlet>
//配置要访问的路径
<servlet-mapping>
<servlet-name>abc</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
大家要永远记住:Myeclipse只是编译环境,而Tomcat才是运行环境。
--------------------------------------------------------------------------------------------------------
3、Servlet生命周期
Servlet实例由Tomcat创建,并且Servlet接口中的所有方法(包括生命周期方法)都由服务器来调用。
Servlet接口中一共有5个方法,其中有3个生命周期方法。
1)void init(ServletConfig config):这个方法会在Servlet实例被创建后,马上被调用。而且只被调用一次,可以对Servlet进行一些初始化工作,如果不需要初始化的话,这个方法空着就可以了。
假如我们的应用有N个Servlet程序,分别为:
Servlet_1
Servlet_2
Servlet_3
...
...
...
Servlet_N
当我们访问Servlet_1时,如果它是第一次被访问,就会创建一个Servlet_1的实例,并立即调用init方法对其进行初始化。如果不是第一次访问,就不会再创建Servlet_1的实例,也不会去调用init方法了。除非在服务器重启后,才会循环这个过程。
同理,当我们访问Servlet_2时,服务器首先判断它是不是第一次被访问,如果是,就创建一个Servlet_2的实例,并立即调用init方法对其进行初始化。如果不是,就不再创建Servlet_2的实例,直接使用第一次创建的Servlet_2实例。
以此类推,当请求Servlet_N时,过程同上。
2)void destroy():在Servlet被销毁之前被调用,而且只被调用一次。如果有一些需要释放的资源可以在这里做。
Servlet被创建的时间点有2个:Servlet默认情况下是在第一次被请求时会被创建,也可以在Tomcat启动时被创建,前提是需要在web.xml中进行配置。
3)void service(ServletRequest req,ServletResponse resp):在每次被请求时被调用,会被调用0—N次。
Servlet被销毁的时间:Tomcat关闭时。
Servlet另外两个方法:
String getServletInfo():获取关于servlet的说明性信息。
ServletConfig getServletConfig():它和init方法的参数类型相同,可以获取web.xml中Servlet的如下配置信息。
<servlet>
<servlet-name>abc</servlet-name>
<servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>
</servlet>
注意:这里我们发现有很多都是接口,比如Servlet、ServletConfig、ServletRequest、ServletResponse,那么这些接口是谁提供的呢?这些接口是由JavaEE(JavaWeb)提供的规范,而这些规范大部分是由Tomcat实现的。Tomcat不支持JavaEE规范,但是它支持JavaEE中的JavaWeb这部分规范。后期我们要学习的Weblogic和JBoss商用服务器都支持JavaEE规范(这些服务器可以写EJB程序)。
JavaEE规范是非常大的东西,它包含了很多规范,比如Jaxp、JNDI、JDBC、EJB、Servlet等。其中EJB指的是“企业级javaBean”,现在过时了,已经被SSH框架取代了。
扩展阅读:
最早,在Servlet诞生之前,我们会通过编写CGI程序来实现数据在Web间的传输,但是,对于客户端做出的每一个请求,都必须创建CGI程序的一个新实例,这将占用大量内存,因此,为了解决这个问题,引入了Servlet技术。
对于所有的客户端请求,只需要创建Servlet的实例一次,因此节省了大量的内存。Servlet在初始化后就会驻留在内存中。因此每次做出请求时无需加载。
一旦请求了一个Servlet,就没有办法阻止容器执行一个完整的生命周期。容器在Servlet首次被调用时创建它的一个实例,并保存该实例在内存中,让它对所有的请求进行处理。
Servlet在执行时,不是一直停留在内存中,服务器会自动将停留时间过长一直没有执行的Servlet从内存中移除,至于停留时间的长短,通常和选用的服务器有关。
-----------------------------------------------------------------------------------------------
4、Servlet相关类介绍
ServletRequest 和 ServletResponse是Servlet接口中service方法的两个参数,该方法由Tomcat调用,所有这两个参数都是由Tomcat提供。
ServletRequest request 请求对象
Tomcat会创建request对象,并将客户端的请求数据封装到request对象中,我们如果需要请求数据就找这个对象即可
ServletResponse response 响应对象
Tomcat会创建response对象,并将数据响应给客户端(为什么不直接说响应给浏览器?因为这个客户端可能是浏览器也可能是一个java程序,比如Socket程序,还有可能是一个Android程序)
ServletConfig config 这是Servlet接口中init方法的参数,用来记录web.xml中通过<init-param>标签配置的初始化参数信息。比如:
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>cn.itcast.servlet.HelloServlet</servlet-class>
//配置初始化参数信息,参数可以配置多个
<init-param>
<param-name>charset</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>abc</param-name>
<param-value>ABC</param-value>
</init-param>
</servlet>
ServletConfig接口有4个方法:
(1)String getInitParameter(String name)
通过此方法获取web.xml中的参数配置信息:
1)在类中首先定义属性
ServletConfig config;
2)在init方法中将参数中的config对象保存起来
this.config = config;
3)在service方法中获取配置信息
String charset = config.getInitParameter("charset");
System.out.println(charset);
(2)Enumeration getInitParameterNames()
通过该方法可以获取所有的初始化参数名称。
//获取所有的参数名
Enumeration e = config.getInitParameterNames();
//遍历每一个参数名
while(e.hasMoreElements()){
String name = (String)e.nextElement();
//根据参数名获取值
String value = config.getInitParameter(name);
System.out.println(name + "=" + value);
}
(3)String getServletName()
它返回的是<servlet-name>元素的值
(4)ServletContext getServletContext()
它可以获取Servlet上下文对象。
ServletContext 这是Servlet上下文,可以在多个Servlet之间共享数据。这种可以在多个Servlet之间传递数据的类我们通常称之为“域对象”。Servlet中有三大域对象。
5、GenericServlet内部结构
GenericServlet是一个抽象类,它实现了Servlet接口和ServletConfig接口
1)它代理了ServletConfig的所有功能,所有使用ServletConfig才能调用的方法,都可以使用GenericServlet的同名方法来完成。
2)不能覆盖父类的init(ServletConfig config)方法,因为在父类中该方法内完成了this.config = config,其他的所有ServletConfig的代理方法都使用this.config来完成。一旦覆盖,那么this.config就是null。
3)如果我们需要做初始化工作,那么可以去覆盖GenericServlet提供的init()方法。
注意:init(ServletConfig config)有参方法是ServletConfig的方法,而init()无参方法才是GenericServlet的方法。
-----------------------------------------------------------------------------------------------
6、HttpServlet内部结构
它提供了一些与http协议相关的功能。它有7个处理请求的doXxx()方法(分别对应着7种请求方式)。还有2个同名service方法。我们不必覆盖service方法,只要覆盖doGet或doPost方法即可,如果没有覆盖这两个方法中的任意一个,默认都会就会响应405。
补充知识:我们知道,http请求有两种方式,Get和Post。在浏览器地址栏中输入地址的方式是Get方式,所有的链接都是Get方式,表单 默认也是Get方式,唯有在表单中指定method=“post”才是Post方式。
关于状态码,2开头表示成功,3开头表示中转,4开头表示客户端错误,5开头表示服务器错误。
(1)void service(ServletRequest, ServletResponse){
1.把参数强转
2.调用下面的service方法
}
(2)void service(HttpServletRequest, HttpServletResponse){
判断当前请求方式
如果是Get请求,调用doGet()
如果是Post请求,调用doPost()
}
(3)void doGet(HttpServletRequest, HttpServletResponse){
如果不覆盖此方法,则返回405
}
(4)void doPost(HttpServletRequest, HttpServletResponse){
如果不覆盖此方法,则返回405
}
请求的流程:Tomcat会首先调用(1)中的service方法,该service方法会调用(2)中的service方法,而(2)中的service方法会根据请求的方式来调用(3)中的doGet方法或(4)中的doPost方法。
应用:通过302转到百度首页。
创建一个类,继承HttpServlet,只需重写它的一个doGet方法。方法如下:
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOEception {
System.out.println("doGet()方法......");
resp.setStatus(302); //设置响应吗
resp.setHeader("Location","http://www.baidu.com"); //设置响应头
}
}
--------------------------------------------------------------------------------------------------------
7、Servlet是单例的
对于每一个Servlet,都是一些.class文件,Tomcat会对这些class文件作缓存。就是说它会先保存起来,我们大致可以将缓存看做是一个Map<String,Servlet>结构。当接受到请求,首先会查看当前Servlet是否存在,如果存在,直接拿来调用service方法,如果不存在,找到其类创建对象,保存到缓存中,再调用service方法。
单例就存在一个问题,线程问题。也就是多个线程会不会同时操作一个对象。这个对象本身是一个实体类型,既能存数据又能取数据的对象我们称之为存储类型的对象。比如:
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
像这种可读可写的类,比如name属性,在多个线程同时操作name属性时,就会出现安全问题。比如一开始往里面存了个值叫”张三“,可是读的时候却是”李四“,这就是不安全。因为在你刚存完之后,在你读之前,有人将这个name的值替换了。
同样,集合也有存储的特性。
我们的对象大致可以分成两种,一种是有存储能力的,就是存储类型的对象,另一种全是功能,没有任何数据可存,叫功能型对象。对于功能型的对象,不用担心线程安全问题。
比如下面这个类就是功能型的类:
class Utils{
public void fun(){
System.out.println("hello world!!");
}
}
Servlet是单例的,所以一个Servlet对象可能同时处理多个请求。
Servlet不是线程安全的,尽可能不要创建成员变量,因为成员变量会被多个线程共享。
如果非要创建,那么创建功能性的,只读!(只能获取,不能修改)
那么,为什么不给service方法用同步块括起来呢?因为括起来会出现站排(排队)的现象。一旦同步,就会觉得卡。当访问量很大的时候,一个服务器忙不过来,就会采用集群。一旦集群还有个问题,这些请求都是瞎子,闭着眼睛排队,它并不知道它要请求的是哪一个服务器。这样就会出现,有的服务器很忙,而有的很闲的现象。就好比在超市结账,有好几个窗口,而大家都在一个窗口排队,分开排队多好。这就需要一个负载均衡问题。
后面我们会学到三大框架的struts2,它里面有个和Servlet很相似的东西,就是action。但action是多例的,就不会出现线程安全问题。就好比说你有一个servlet,我有一个servlet,你用你的,我用我的,互不干扰。
servlet的创建时间
它可以在第一次请求时被创建,还可以在容器启动时被创建。默认是第一次请求时创建。那么启动时创建有什么好处呢?JavaWeb的发展有一个漫长的历史,最早的时候,JavaWeb并没有三大组件,它只有Servlet一个组件,它在服务器启动时创建是为了做Filter或Listener的工作。但是现在,这种启动时创建就没有意义了,但我们得学它。
等我们学了Filter和Listener你就会明白,早期那些本来可以用现在的Filter来完成的工作,可当时为什么偏偏要用Servlet来完成呢?因为当初没有Filter,这是一个发展的过程。你没有经历过那个时代,所以你不知道。
好比说框架当中有一个struts1的东西。它有一个核心的控制器是一个Servlet。而struts2的时候它改成了Filter。所以你可能会认为struts1中的控制器应该用Filter来完成,可它为什么要使用Servlet呢?在一些已经成型的东西当中,你会发现一些不合理的东西就是这个原因。
在服务器启动时创建Servlet的设置:
在web.xml中,在<servlet>元素中添加一个<load-on-startup>大于等于0的整数</load-on-startup>这样的元素。设置好之后,我们可以在一个Servlet中通过init方法来验证该Servlet是否在服务器启动时被创建。
如果有多个Servlet在容器启动时创建,那么<load-on-startup>中的值越小,优先级就越高,就会先被创建。当我们有多个Servlet时,就可以使用它的值对多个Servlet进行排序。
那么谁先启动,对我们有什么意义吗?早期可能会比较依赖这种东西,那时会拿Servlet当Filter来用,谁先执行可能会比较重要,但今天就没有这个必要了。
<url-pattern>通配符
<url-pattern>是<servlet-mapping>的子元素,用来指定Servlet的访问路径,即URL。它必须是以”/“开头!
1)可以在<servlet-mapping>中给出多个<url-pattern>,例如:
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/AServlet</url-pattern>
<url-pattern>/BServlet</url-pattern>
</servlet-mapping>
这样,一个Servlet绑定了两个URL,无论访问/AServlet还是/BServlet,访问的都是AServlet。那么这有什么意义吗?没有任何意义。只有在早期版本使用Servlet充当Filter的时候才会有用。后面我们学习Filter的时候你就会发现,我们经常会使用一个Filter来处理一大堆的路径。
当<url-pattern>有很多时,怎么办呢?
2)还可以在<url-pattern>中使用通配符”*“星号。”*“星号可以匹配任何URL前缀和后缀,使用通配符可以命名一个Servlet绑定一组URL,例如:
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/servlet/AServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BServlet</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
如果我们的项目名是day09_2,那么请问,如果我们在浏览器中访问http://localhost:8080/day09_2/servlet/AServlet 访问的是哪一个Servlet呢?如果按优先级排的话,那么谁会先执行呢?
会执行AServlet而不会执行BServlet,因为AServlet更明确。
如果访问http://localhost:8080/day09_2/servlet/hello 的话,访问的就是BServlet。
<url-pattern>中可以使用”*“表示所有字符,但它不匹配”/“。(包含”/“的字符它是不能匹配的)
通配符的使用要求:
1)它要么在开头,要么在结尾,不能再中间。
2)如果不使用通配符,那么必须使用”/“开头。
举例:
/* 正确
*.action 正确
/*.jsp 错误
/abc/def/* 正确
/*/*/ 错误
如果一个访问路径,匹配了多个<url-pattern>,那么谁更加明确就匹配谁。
比如:
/*
/abc/*
/abc/AServlet
----------------------------------------------------------------------------------------
8、web.xml的继承
每一个项目都有一个web.xml,但是Tomcat安装目录下也有一个web.xml
在${CATALINA_HOME}/conf下有4大配置文件:
context.xml
server.xml
tomcat-users.xml
web.xml
说明:CATALINA_HOME是Tomcat的安装目录
${CATALINA_HOME}/conf/web.xml是所有项目下web.xml文件的父文件,父文件中的内容等同于写在子文件中。就好比父类的方法等同于写在子类中一样。
下面我们就列举一些${CATALINA_HOME}/conf/web.xml中的配置。
在该web.xml中有如下的配置:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
当我们在浏览器中访问WebRoot下的index.jsp时,会首先经过上面配置中<url-pattern>*.jsp</url-pattern>进行匹配。然后就会找到<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>,并执行JspServlet。JspServlet会把你请求路径中的”/index.jsp“通过匹配就找到了WebRoot下的index.jsp。最后会对这个jsp页面进行加工处理转成html文件,再通过response对象发送到浏览器。
所有对jsp的请求,都会执行JspServlet,它会对jsp文件进行处理,最终发送给客户端的是html代码。
在该web.xml中还有如下的配置:
<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>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
上面配置中的<url-pattern>/</url-pattern>表示所有,它的匹配级别较低一些,当所有的都匹配不上时才会执行这个DefaultServlet。它对所有静态资源进行处理,比如:html、htm、CSS、js等。
所有的请求都不会直接访问到指定的资源,所有的请求都会首先被Tomcat服务器接收,然后再通过Servlet找到指定的资源。
在该web.xml中还有如下的配置:
<session-config>
<session-timeout>30</session-timeout>
</session-config>
这是session的最大不活动时间,单位是分钟。比如,你登陆某网站,如果30分钟没有任何操作就会超时,会提示你重新登陆。
此外,在该web.xml中还有很多很多类似下面的配置:
<mime-mapping>
<extension>jpg</extension>
<mime-type>image/jpeg</mime-type>
</mime-mapping>
每一个<mime-mapping>都包含一个扩展名和一个类型名,这是一个扩展名和一个类型之间的映射。比如在上面的这一条配置中,jpg是扩展名(文件的后缀),而image/jpeg是类型。
假如我们只知道文件的后缀名而不知道其媒体类型,就可以通过某种方式根据后缀名得到其对应的媒体类型。
Mime类型(或媒体类型)是网络上的一种资源的类型,所有的资源都有其对应的类型。我们在给浏览器发送数据的时候,就需要告诉浏览器我们发送的是什么类型的数据。
最后,还有一组配置:
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
这是指定欢迎页面的配置,即:当index.html不存在是就访问index.htm,当index.htm不存在时就访问index.jsp。
总结:由于Web.xml的继承,上面的这些配置就会写在我们的每一个项目下的web.xml中。
---------------------------------------------------------------------------------------
9、ServletContext的域功能
它就是一个公共资源的存储区域,可以在多个Servlet之间共享数据。一个Servlet往里面存的数据,另一个Servlet可以从里面获取该数据。ServletContext本身虽不是一个map集合,但它内部有一个map集合结构,所以可以存取数据。
Servlet有3大域对象:
1)ServletContext:范围最大,整个web应用范围
2)HttpSession:会话范围
3)HttpServletRequest:请求范围
后面我们学到jsp就会有4大域对象,比Servlet多出来一个。
域对象都有存取的功能:
1)setAttribute(String name,Object obj);添加属性(put)
2)Object getAttribute(String name); 获取属性(get)
3)Enumeration getAttributeNames(); 获取所有属性名称
4)removeAttribute(String name); 删除属性(remove)
ServletContext除了上面的存取功能之外,还有获取应用初始化参数的功能。
这里的应用初始化参数不是前面学的Servlet的初始化参数。一个Servlet配置的初始化参数,另一个Servlet是无法获取的。
这个应用初始化参数是ServletContext自己的初始化参数。
在项目的web.xml中做如下配置:
<context-param>
<param-name>p1</param-name>
<param-value>v1</param-value>
</context-param>
<context-param>
<param-name>p2</param-name>
<param-value>v2</param-value>
</context-param>
获取上面的应用初始化参数的方法有2个:
在ServletContext中有2个方法
(1)String getInitParameter(String name):通过参数名获取参数值
(2)Enumerarion getInitParameterNames():获取所有参数的名称
注意:
this.getInitPrameter("xxx")和this.gerServletContext().getInitParameter("xxx")的区别
前者获取的是Servlet的初始化参数信息,后者获取的是ServlerContext的初始化参数信息
ServletContext还有获取资源的功能。
ServletContext的功能:
1)存取域属性的功能
2)获取应用初始化参数的功能
3)获取资源的功能
ServletContext的生命周期:
ServletContext在容器(Tomcat)启动时被创建,在容器关闭时被销毁。而且,一个项目只有一个ServletContext对象。
ServletContext的生命周期最长,与Servlet的生命周期做一下比较。每一个Servlet除了可以通过配置后在容器启动时被创建之外,默认都是在第一次被请求时被创建,不请求永远不会被创建。
获取ServletContext的几种方式:
1)通过ServletConfig的getServletContext()来获取
在Servlet接口中有个init(ServletConfig config)方法,方法的参数是ServletConfig接口,这个ServletConfig接口中有一个getServletContext()方法,可以得到ServletContext。但是,这种方式只能是在init方法里面获取。
2)在GenericServlet的子类中通过this.getServletContext()来获取
GenericServlet代理了ServletConfig的所有方法,而且还提供了getServletConfig(),所以在GenericServlet的子类中可以使用以下方式来获取ServletContext对象
(1)this.getServletContext()
(2)this.getServletConfig().getServletContext()
3)通过HttpSession中的session.getServletContext()获取
4)后期我们学习过滤器和监听器时也有获取ServletContext的方式
通常情况下,我们会这样获取ServletContext:
首先创建一个Servlet类,继承HttpServlet,重写doGet或doPost方法,在doGet或doPost方法内部使用this.getServletContext()来获取ServletContext。
ServletContext的举例
我们创建两个Servlet,分别是AServlet和BServlet。在AServlet中往ServletContext中保存一个字符串”张三“,然后在BServlet中从ServletContext获取该字符串并显示到浏览器。代码如下:
AServlet代码:
public class AServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//向ServletContext中保存信息
this.getServletContext().setAttribute("name", "张三");
//设置响应编码
resp.setHeader("Content-type", "text/html;charset=utf-8");
//向浏览器显示信息
resp.getWriter().print("<h1>保存成功!</h1>");
}
}
BServlet代码:
public class BServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//从ServletContext中获取信息
String name = (String) this.getServletContext().getAttribute("name");
//设置响应编码
resp.setHeader("Content-type", "text/html;charset=utf-8");
//向浏览器显示信息
resp.getWriter().print("<h1>"+name+"</h1>");
}
}
-------------------------------------------------------------------------------------------------------
10、ServletContext获取资源的功能
问题:
假如在我们有2个文件:a.txt和b.txt,它们分别位于不同的目录下。其中a.txt在WebRoot目录下,b.txt在WEB-INF目录下。请问该如何获取这两个文件呢?
这里有如下几个方法:
1)获取真实路径 String getRealPath(String path)
参数String path是一个相对路径,必须以”/“开头,”/“表示当前项目所在路径。
那么请问,我们的当前项目所在路径在哪里?是在我们的工作空间workspace中项目目录吗?
不是,大家千万注意,MyEclipse只是编译环境,运行环境是Tomcat。
我们的当前项目所在路径应该到Tomcat下去找。
什么是真实路径?
真实路径是以盘符开头的。
请问 http://localhost:8080/helloservlet/AServlet 这种是真实路径吗?
不是,这不是服务器端的真实路径,这是虚拟的。我们所说的真实路径指的是资源位于服务器的哪一个盘符的哪一个文件夹下。它是随着我们的Tomcat的位置而变化的。
注意:WEB-INF是我们唯一的一道安全防线,它里面的的东西通过浏览器是不能直接访问的。它里面存放的都是极其重要的文件,比如字节码文件,jar包,配置文件等。在实际开发中,为了安全,我们常常把jsp文件也放进WEB-INF下。
现在我们通过一个Servlet程序来获取a.txt和b.txt文件的真实路径:
public class CServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//获取a.txt文件的真实路径
String apath = this.getServletContext().getRealPath("/a.txt");
System.out.println(apath);
//获取b.txt文件的真实路径
String bpath = this.getServletContext().getRealPath("/WEB-INF/b.txt");
System.out.println(bpath);
}
}
相对路径的最大的好处在于,当我们的项目从一位置迁移到了另一个位置,访问资源时不会出错。
2)获取指定文件夹下所有资源的路径 Set getResourcePaths(String path)
public class DServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Set<String> paths = this.getServletContext().getResourcePaths("/WEB-INF");
for (String string : paths) {
System.out.println(string);
}
}
}
上面的程序输出结果为:
/WEB-INF/lib/
/WEB-INF/classes/
/WEB-INF/b.txt
/WEB-INF/web.xml
3)获取资源流 InputStream getResourceAsStream(String path)
如果我把一个资源文件对应的流给你了,是不是相当于把这个文件给你一样?你是不是就可以通过流把数据读到内存当中?
这就要用到IO流来读取文件。如何使用IO流来读取文件,那是基础班的东西,在就业班我们一律使用工具。
我们为什么要使用工具?
你要是自己写个循环往外读,说不好听的,你要是哪里写错一点点,够你调几个小时的。我们有现成的工具。
这里我们要使用到一个Apache的commons组件:commons-io-1.4.jar
Apache的commons组件被称为Java的第二API。
我们将commons-io-1.4.jar拷贝到WebRoot下的lib目录中,程序代码如下:
public class EServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
InputStream in1 = this.getServletContext().getResourceAsStream("/a.txt");
InputStream in2 = this.getServletContext().getResourceAsStream("/WEB-INF/b.txt");
//读出a.txt文件中文本
String s1 = IOUtils.toString(in1);
//读出b.txt文件中的文本
String s2 = IOUtils.toString(in2);
//输出文本内容
System.out.println(s1);
System.out.println(s2);
}
}
--------------------------------------------------------------------------------
11、通过Class和ClassLoader来获取类路径下的资源
1)使用ClassLoader获取类路径下的资源
假如我们想要创建一个User对象,那么必须先有Class<User>,那么就必须先有User.class,那么就必须先有User.java。
那么User.class是如何变成Class<User>的呢?这就需要ClassLoader。它的作用就是把硬盘上的User.class加载到内存中,变成一个Class对象。这就是类加载器。
public class EServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//相对于classes
InputStream in1 = this.getClass().getClassLoader().getResourceAsStream("c.txt");
System.out.println(IOUtils.toString(in1));
InputStream in2 = this.getClass().getClassLoader().getResourceAsStream("cn/itcast/servlet/d.txt");
System.out.println(IOUtils.toString(in2));
}
}
注意:上面程序中的c.txt是相对于classes路径(类路径),c.txt前面可以加”/“,也可以不加”/“,结果都一样。
即:
c.txt 等同于 /classes/c.txt
cn/itcast/servlet/d.txt 等同于 /classes/cn/itcast/servlet/d.txt
2)使用Class获取类路径下的资源
那么以上两种方式有什么区别呢?
最大的区别是:
ClassLoader方式的路径是相对于classes目录;
而Class方式的路径如果不加”/“是相对于.class字节码文件所在目录,如果前面加”/“则相对于classes目录,这时它与ClassLoader方式是一样的。
假如我们在项目cn.itcast.servlet包下创建aaa.txt文件,在src下创建bbb.txt文件
public class GServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//不加”/“,相对于字节码文件所在目录
InputStream input1 = this.getClass().getResourceAsStream("aaa.txt");
System.out.println(IOUtils.toString(input1));
//加”/“则相对于classes目录
InputStream input2 = this.getClass().getResourceAsStream("/bbb.txt");
System.out.println(IOUtils.toString(input2));
}
}
---------------------------------------------------------------------------------------
12、统计网站的访问量
”代码是思想的体现“,这句话很重要。没有思路就没有步骤,没有步骤就没有代码。越往后越能体现出来这句话的重要性。
public class Servlet1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//从ServletContext中获取访问次数
Integer cont = (Integer) this.getServletContext().getAttribute("cont");
//如果没有人访问,则次数设置为1
if (cont == null) {
cont = 1;
}else{
//如果访问次数不为空,则次数累加
cont += 1;
}
将累加后的值保存到ServletContext中
this.getServletContext().setAttribute("cont", cont);
//设置响应编码
response.setContentType("text/html;charset=utf-8");
//向浏览器输出信息
response.getWriter().print("这是Servlet1");
response.getWriter().print("当前访问次数为:"+cont);
}
}
后面我们会考大家一道题:统计你是第几个访问本网站的人。这就相对比较有难度了。