目录
什么是Servlet
Servlet(Server Applet) 是基于 Java 技术的 web 组件,该组件由容器托管,用于生成动态内容。他是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。
Servlet : 用java语言来编写动态资源的开发技术。
Servlet特点:1)普通的java类,继承HttpServlet类,覆盖doGet方法
2)Servlet类只能交给tomcat服务器运行!!!!(开发者自己不能运行!!!)
Servlet手动编写步骤:
1)编写一个servlet程序,继承HttpServlet
2)找到HelloServlet类的class字节码,然后把拷贝到tomcat的一个web应用中WEB-INF/classes目录下。
3)在当前web应用下的web.xml文件配置Servlet。
4)启动tomcat服务器,运行访问访问servlet: http://localhost:8080/myweb/ hello
什么是 Servlet 容器
Servlet容器是web server或application server的一部分,供基于请求/响应发送模型的网络服务,解码基于 MIME 的请求,并且格式化基于 MIME 的响应。Servlet 容器也包含并管理 Servlet 生命周期。
所有 Servlet 容器必须支持基于 HTTP 协议的请求/响应模型,比如像基于 HTTPS(HTTP over SSL)协议的 请求/应答模型可以选择性的支持。容器必须实现的 HTTP 协议版本包含 HTTP/1.0 和 HTTP/1.1。
Tomcat 与 Servlet 是如何工作的
步骤:
- Web Client 向Servlet容器(Tomcat)发出Http请求
- Servlet容器接收Web Client的请求
- Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中。
- Servlet容器创建一个HttpResponse对象
- Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给HttpServlet 对象。
- HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息。
- HttpServlet调用HttpResponse对象的有关方法,生成响应数据。
- Servlet容器把HttpServlet的响应结果传给Web Client。
Servlet工作原理
首先简单解释一下Servlet接收和响应客户请求的过程,首先客户发送一个请求,Servlet是调用service()方法对请求进行响应的,通过源代码可见,service()方法中对请求的方式进行了匹配,选择调用doGet,doPost等这些方法,然后再进入对应的方法中调用逻辑层的方法,实现对客户的响应。在Servlet接口和GenericServlet中是没有doGet()、doPost()等等这些方法的,HttpServlet中定义了这些方法,但是都是返回error信息,所以,我们每次定义一个Servlet的时候,都必须实现doGet或doPost等这些方法。
每一个自定义的Servlet都必须实现Servlet的接口,Servlet接口中定义了五个方法,其中比较重要的三个方法涉及到Servlet的生命周期,分别是上文提到的init(),service(),destroy()方法。GenericServlet是一个通用的,不特定于任何协议的Servlet,它实现了Servlet接口。而HttpServlet继承于GenericServlet,因此HttpServlet也实现了Servlet接口。所以我们定义Servlet的时候只需要继承HttpServlet即可。
Servlet接口和GenericServlet是不特定于任何协议的,而HttpServlet是特定于HTTP协议的类,所以HttpServlet中实现了service()方法,并将请求ServletRequest、ServletResponse强转为HttpRequest 和 HttpResponse。
Servlet 生命周期
Servlet 通过一个定义良好的生命周期来进行管理,该生命周期规定了 Servlet 如何被加载、实例化、初始化、 处理客户端请求,以及何时结束服务。
该生命周期可以通过 javax.servlet.Servlet 接口中的 init、service 和 destroy 这些 API 来表示,所有 Servlet 必须直接或间接的实现 GenericServlet 或 HttpServlet 抽象类。
servlet的生命周期就是从servlet出现到销毁的全过程。主要分为以下几个阶段:
加载类—>实例化(为对象分配空间)—>初始化(为对象的属性赋值)—>请求处理(服务阶段)—>销毁
主要涉及到的方法有init、service、doGet、doPost、destory等
服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf),该servlet对象去处理所有客户端请求。
service(ServletRequest req,ServletResponse res)方法中执行。
最后服务器关闭时,才会销毁这个servlet对象,执行destroy()方法。其中加载阶段无法观察,但是初始化、服务、销毁阶段是可以观察到的。
public class EmpServlet extends HttpServlet{
//初始化servlet,调用init方法
@Override
public void init() throws ServletException {
System.out.println("初始化时调用");
}
//开启服务
@Override
protected void service(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {
System.out.println("开启服务时调用");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
//销毁时调用destory
@Override
public void destroy() {
System.out.println("销毁时调用");
}
}
加载并实例化
Servlet容器负责加载和实例化Servelt。
当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。
启动后,它必须要知道所需的Servlet类在什么位置,Servlet容器可以从本地文件系统、远程文件系统或者其他的网络服务中通过类加载器加载Servlet类,
当Servlet容器启动后,Servlet通过类加载器来加载Servlet类,加载完成后再new一个Servlet对象来完成实例化。
成功加载后,容器创建Servlet的实例。因为容器是通过Java的反射API来创建Servlet实例,调用的是Servlet的默认构造方法(即不带参数的构造方法),所以我们在编写Servlet类的时候,不应该提供带参数的构造方法。
初始化
服务器启动时(web.xml中配置load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf)
在Servlet实例化之后,容器将调用init()方法。
初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作。如建立数据库的连接,获取配置信息等。
在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。
在初始化期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常来通知容器。ServletException异常用于指明一般的初始化失败,例如没有找到初始化参数;而UnavailableException异常用于通知容器该Servlet实例不可用。例如,数据库服务器没有启动,数据库连接无法建立,Servlet就可以抛出UnavailableException异常向容器指出它暂时或永久不可用。
在init()方法中,Servlet可以部署描述符中读取配置参数,或者执行任何其他一次性活动。在Servlet的整个生命周期类,init()方法只被调用一次。
如何配置Servlet的初始化参数
在web.xml中该Servlet的定义标记中,比如:
<servlet>
<servlet-name>TimeServlet</servlet-name>
<servlet-class>com.allanlxf.servlet.basic.TimeServlet</servlet-class>
<init-param>
<param-name>user</param-name>
<param-value>username</param-value>
</init-param>
<init-param>
<param-name>blog</param-name>
<param-value>http://。。。</param-value>
</init-param>
</servlet>
配置了两个初始化参数user和blog它们的值分别为username和http://。。。, 这样以后要修改用户名和博客的地址不需要修改Servlet代码,只需修改配置文件即可。
如何读取Servlet的初始化参数
ServletConfig中定义了如下的方法用来读取初始化参数的信息:
public String getInitParameter(String name)
参数:初始化参数的名称。
返回:初始化参数的值,如果没有配置,返回null。
init(ServletConfig)方法执行次数
在Servlet的生命周期中,该方法执行一次。
init(ServletConfig)方法与线程
该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题。
init(ServletConfig)方法与异常
该方法在执行过程中可以抛出ServletException来通知Web服务器Servlet实例初始化失败。一旦ServletException抛出,Web服务器不会将客户端请求交给该Servlet实例来处理,而是报告初始化失败异常信息给客户端,该Servlet实例将被从内存中销毁。如果在来新的请求,Web服务器会创建新的Servlet实例,并执行新实例的初始化操作
请求处理
当Servlet初始化后,容器就可以准备处理客户机请求了。当容器收到对这一Servlet的请求,就调用Servlet的service()方法,并把请求和响应对象作为参数传递。要注意的是,在service()方法调用之前,init()方法必须成功执行。
在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
在service()方法执行期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常。如果UnavailableException异常指示了该实例永久不可用,Servlet容器将调用实例的destroy()方法,释放该实例。此后对该实例的任何请求,都将收到容器发送的HTTP 404(请求的资源不可用)响应。如果UnavailableException异常指示了该实例暂时不可用,那么在暂时不可用的时间段内,对该实例的任何请求,都将收到容器发送的HTTP 503(服务器暂时忙,不能处理请求)响应。
当并行的请求到来时,多个service()方法能够同时运行在独立的线程中。service()方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet()、doPost()、doPut(),doDelete() 等方法。
一般情况下,当开发基于 HTTP 协议的 Servlet 时,Servlet 开发人员只关注自己的 doGet 和 doPost 请求处理 方法即可。其他方法被认为是非常熟悉 HTTP 编程的程序员使用的方法。
service()方法的职责
service()方法为Servlet的核心方法,客户端的业务逻辑应该在该方法内执行,典型的服务方法的开发流程为:
解析客户端请求 -〉执行业务逻辑-〉输出响应页面到客户端
service()方法与线程
为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性。
service()方法与异常
service()方法在执行的过程中可以抛出ServletException和IOException。其中ServletException可以在处理客户端请求的过程中抛出,比如请求的资源不可用、数据库不可用等。一旦该异常抛出,容器必须回收请求对象,并报告客户端该异常信息。IOException表示输入输出的错误,编程者不必关心该异常,直接由容器报告给客户端即可。
编程注意事项说明:
1) 当Server Thread线程执行Servlet实例的init()方法时,所有的Client Service Thread线程都不能执行该实例的service()方法,更没有线程能够执行该实例的destroy()方法,因此Servlet的init()方法是工作在单线程的环境下,开发者不必考虑任何线程安全的问题。
2) 当服务器接收到来自客户端的多个请求时,服务器会在单独的Client Service Thread线程中执行Servlet实例的service()方法服务于每个客户端。此时会有多个线程同时执行同一个Servlet实例的service()方法,因此必须考虑线程安全的问题。
3) 请大家注意,虽然service()方法运行在多线程的环境下,并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式。分析如下:
i. 如果service()方法没有访问Servlet的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的,不必进行任何的同步控制。
ii. 如果service()方法访问了Servlet的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制。
iii. 如果service()方法访问了Servlet的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句。
iv. 如果service()方法访问了全局的静态变量,如果同一时刻系统中也可能有其它线程访问该静态变量,如果既有读也有写的操作,通常需要加上同步控制语句。
v. 如果service()方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句。
销毁
当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用实例的destroy()方法,以便让该实例可以释放它所使用的资源,保存数据到持久存储设备中。当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法。
在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。
容器会在所有Servlet的service()线程之后,调用Servlet的destroy()方法。然后,Servlet就可以进行无用存储单元收集清理。这样Servlet对象就被销毁了。
在整个Servlet的生命周期过程中,创建Servlet实例、调用实例的init()和destroy()方法都只进行一次,当初始化完成后,Servlet容器会将该实例保存在内存中,通过调用它的service()方法,为接收到的请求服务。
这四个阶段共同决定了Servlet的生命周期。
Servlet的线程安全问题
为了有效利用JVM允许多个线程访问同一个实例的特性,来提高服务器性能。在非分布式系统中,Servlet容器只会维护一个Servlet的实例。
如果 Web 应用中的 Servlet 被标注为分布式的,容器应该为每一个分布式应用程序的 JVM 维护一个 Servlet 实例池。
Servlet容器通过维护一个线程池来处理多个请求,线程池中维护的是一组工作者线程(Worker Thread)。Servlet容器通过一个调度线程(Dispatcher Thread)来调度线程池中的线程。
当客户端的servlet请求到来时,调度线程会从线程池中选出一个工作者线程并将请求传递给该线程,该线程就会执行对应servlet实例的service方法。
同样,当客户端发起另一个servlet请求时,调度线程会从线程池中选出另一个线程去执行servlet实例的service方法。
Servlet容器并不关心这些线程访问的是同一个servlet还是不同的servlet,当多个线程访问同一个servlet时,该servlet实例的service方法将在多个线性中并发执行。
所以,Servlet对象是单实例多线程,Servlet不是线程安全的
为什么不安全?
先看两个定义:
实例变量:实例变量在类中定义。类的每一个实例都拥有自己的实例变量,如果多个线程同时访问该实例的方法,而该方法又使用到实例变量,那么这些线程同时访问的是同一个实例变量,会共享该实例变量。
局部变量:局部变量在方法中定义。每当一个线程访问局部变量所在的方法时,在线程的堆栈中就会创建这个局部变量,线程执行完这个方法时,该局部变量就被销毁。所有多个线程同时访问该方法时,每个线程都有自己的局部变量,不会共享。
看如下代码:
public class MyServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
private String userName1 = null;//实例变量
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
userName1 = req.getParameter("userName1");
String userName2 = req.getParameter("userName2");//局部变量
//TODO 其他处理
}
}
userName1则是共享变量,多个线程会同时访问该变量,是线程不安全的。
userName2是局部变量,不管多少个线程同时访问,都是线程安全的。
解决Servlet的线程安全问题
如果不涉及到全局共享变量,就直接使用局部变量
如果使用到全局共享的场景,可以使用加锁的方式.对全局变量的读写操作置于synchronized同步块中,这样不同线程排队依次执行该代码块,从而避免线程不安全情况发生。
还可以使用线程安全的数据类型。比如hashtable,blockQueue等
Servlet单例多例
Servlet既可以是单例,又可以是多例。Servlet 如果是同一个 Web App 的话,定义多次就是多例。所以要看容器,不能一概而论。
当Servlet实现类实现了SingleThreadModel接⼝后,他就是属于线程安全的,即多例的(对于一个服务让SingleThreadModel接口实现,Servlet容器可以实例化多个实例来处理沉重的请求负载并序列化请求特定实例)。
那一个实现了SingleThreadModel接口的Servlet,一般会初始化多少个实例呢?
StandardWrapper类中有两个属性,其中maxInstance初始为20。即默认会有20个实例。
这个接口在Servlet 2.4之后已经是@deprecated了。也就是说在Servlet2.4以后是⽆ٞ法保证的。所以还是要确保容
器的情况。不能一概而论。目前来看Tomcat8.5还保留了这个实现,在Servlet 4.0规范中这个也还保留着:
之所以要淘汰掉SingleThreadModel,是因为它相当于把锁加到了service方法上,同步的范围太大了,所以后续规范希望用户自行控制,并且告诉Servlet对象默认不是线程安全的。
Servlet是单例或者多例,是要看Servlet声明的情况,比如:
上图看出:同样的类,申明两次。就是两个实例。那么就是多例。 不过Servlet Web应⽤不允许同名。
这两个Servelt⼜可以映射到不同的url-pattern上去。所以证明了既可以是单例,也可以是多例。这样会不会认为单例才会有线程安全,多例完全是线程不安全的?
但是也不能这么说,线程安全的问题与Servlet的实例是单例还是多例没有关系。主要关键还是体现在实现类的⽅ٛ法。
即通常不实现SingleThreadModel接⼝的servlet,每声明一个servlet,就有一个和它对应的servlet映射。
所有访问上图的echo的url的请求,都访问同一个servlet1,所有访问上图的echo2的url的请求,都访问同一个servlet2
servlet1和servlet2是相当于是2个new echoServlet() 的实例。
ServletContext简介
Servlet三大域对象的应用 request、session、application(ServletContext)
ServletContext是一个全局的储存信息的空间,服务器开始就存在,服务器关闭才释放。
request,一个用户可有多个;session,一个用户一个;而servletContext,所有用户共用一个。所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。
ServletContext的生命周期
诞生:web部署到服务器。启动服务器,此时上下文环境对象创建。只要是tomcat服务器不关闭,上下文环境对象一直存在。
销毁:关闭服务器或者是从web服务器上卸载该程序的时候,该对象销毁。
ServletContext的获取
主要有三种获取方式如下
this.getServletConfig().getServletContext();
this.getServletContext();
request.getServletContext();
代码如下
其中方式1和2本质相同
三种方式获取的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;
/*
* 获得ServletContext对象的方式:
* 三种:
*/
public class ContextServletDemo01 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
// 获得上下文环境对象:
// 方式一: ServletConfig 对象: getServletContext(); ServletContext
ServletContext context = this.getServletConfig().getServletContext();
//方式二:
ServletContext context2 = this.getServletContext();
//方式三: 通过request 对象获得:
ServletContext context3 = request.getServletContext();
System.out.println(context== context2);
// 通过不同种方式 : 获得的ServletContext对象是同一个对象:
System.out.println(context3);// 地址: org.apache.catalina.core.ApplicationContextFacade@36046a62
}
}
ServletContext的作用
作为域对象的使用
域:本质上是一个map结构:存数据,取数据。
域的作用范围:作用范围是:在整个web应用程序当中。
servlet中一共有三大域对象,JSP中一共有四大域对象。
一般情况下不往域对象中存数据,原因在
1生命周期太长
2作用范围太广(一般是tomcat把数据存在这个域对象当中)
存数据的方法:setAttribute(key,value); 向域对象当中存放的键值对作为域的一个属性而存在。
取出来:getAttribute(key); 通过给定的key获得key对应的值。
(1)取出来的值一定是Object类型:需要强转
(2)如果指定的属性的名称不存在,将获得一个null。
代码如下(域对象可以被所有的servlet对象共享,所以骚一把一个小服务程序存用另一个来取):
现在浏览器访问运行这个小服务程序,把数据存进去(最后一个是删除操作)
存的时候可以键重复但是会覆盖,查的时候键不存在的话返回null
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;
/*
* 在 context 域对象当中存放数据:
*/
public class ContextServletDemo03 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//存放数据:
ServletContext application = this.getServletContext();
application.setAttribute("username", "俊哥");
application.setAttribute("username", "俊哥2");
application.setAttribute("username", "俊哥3");
application.setAttribute("password", "000000");
String username= application.getAttribute("username");
application.removeAttribute("password");
}
}
获得应用级别的初始化参数
相对于这种初始化,只能在当前的servlet当中访问
<init-param>
<param-name>username</param-name>
<param-value>王导演</param-value>
</init-param>
应用级别的初始化参数,可以在当前应用所有的servlet中访问
<!-- 配置一个应用级别的初始化参数: -->
<context-param>
<param-name>encoding</param-name>
<param-value>gbk</param-value>
</context-param>
<context-param>
<param-name>encoding1</param-name>
<param-value>utf-8</param-value>
</context-param>
import java.io.IOException;
import java.util.Enumeration;
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 ContextServletDemo05 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获得应用级别的初始化参数:
//获得ServletContext对象:
ServletContext context = request.getServletContext();
String value = context.getInitParameter("encoding");//gbk
System.out.println("context-initParam:"+value);
//获得所有的参数的名称:
Enumeration<String> names = context.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String val = context.getInitParameter(name);
System.out.println(name+" "+val);
}
request.setCharacterEncoding(value);
//此处用value 而不写死,直接从web.xml中获取value值。
response.setContentType("text/html;charset="+value+"");
}
}
能够获得资源
能够获得资源的真实路径
真实的路径,是文件在tomcat上的真实的存放的位置。 在文件上传和文件下载的时候,会使用到
context.getRealPath(“path” );
路径:指定路径的时候可以使用/开头。也可以不使用/开头。
使用/开头:相对于当前的网应用。
不使用/开头:也是相对于当前的网应用。
代码如下:其实就这一个方法啦~~~~
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;
/*
* 获得文件的真实目录:
* getRealPath()
*/
public class ServletContextDemo06 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
ServletContext context = this.getServletContext();
String path = context.getRealPath("a.txt");//相对路径: 相对于当前的web 应用:
System.out.println(path);
// C:\Users\Mrzhang\javaEE\javaEE-07\javaEE-01\apache-tomcat-7.0.42\webapps\javaEE-05\a.txt
String path1 = context.getRealPath("/a.txt");// / 相对路径: 相对于当前的web应用:
System.out.println(path1);
//获得web-inf下的文件的真实路径:
String path2= context.getRealPath("/WEB-INF/b.txt");
System.out.println(path2);
//获得com.yidongxueyuan.web.servlet包下的c.txt文件的真实路径:
String path3= context.getRealPath("/WEB-INF/classes/com/yidongxueyuan/web/servlet/c.txt");
System.out.println(path3);
}
}
能够获得指定目录下的所有的资源
context .getResourcePaths(“path”);
能够获得路径下的所有的文件。返回的是一个set集合。
案例:getResourcePaths(“/ WEB-INF”);
WEB-INF这个目录具有安全性该目录下的内容不会被外界浏览器直接访问的。
代码如下:
package com.yidongxueyuan.web.servlet;
import java.io.IOException;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* 获得web-inf下的所有的资源路径:
*/
public class ServletContextDemo07 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
ServletContext context = this.getServletContext();
Set<String> paths = context.getResourcePaths("/WEB-INF");//能够获得web-inf下的所有的资源: web.xml classes lib a.txt
System.out.println(paths);
}
}
获得资源流
方式一:使用ServletContext的对象:
A.TXT文件当中的数据:
context.getResourceAsStream(“路径“); 不管是/开头不是/开头,相当于当前的web应用。
方式二:使用类的加载器,加载类路径下的资源:
实现步骤:
a:获得类的加载器:
b:使用类的加载器的:getResourceAsStream(“path”); 可以是/可以不是/:相对于:类路径.classes路径。
此方法只能获得类路径下的资源
方式三:使用类的方法获得资源流:
a:获得类:
b:使用类当中的APi方法:getResourceAsStream(“path”);
/开头:相对路类,类路径:
不以/开头:相对于当前文件存在的位置。
此方法只能获得类路径下的资源
综上:如果获得webroot下面的内容,可以使用servletcontext。
如果获得类路径下的内容,请使用类的加载器。
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
/*
* servlet 获得资源流: a.txt 目录当中的数据。
* 自己完成 读取: b.txt
*
* 使用类的加载器: 读取c.txt 文件当中的数据。
*
* jar包:
*
*/
public class ServletContextDemo08 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//获得a.txt文件当中的数据:
ServletContext context = this.getServletContext();
InputStream in = context.getResourceAsStream("/a.txt");
/*int len=0;
while((len=in.read())!=-1){
System.out.print((char)len);
}
in.close();
*/
String val = IOUtils.toString(in);
//这个其实是对上面那一段偏底层的代码的封装
System.out.println(val);
IOUtils.closeQuietly(in);
// /WEB-INF/classes/d.txt; calsses 类路径: 读取类路径当中的资源, 使用类的加载器读取。
// readFile(request,response);
readFileByClass(request,response);
}
//使用类的加载器 读取d.txt 文件: src 下创建: 部署到tomcat 下的时候, 文件在classes 下。
// classes 目录: 类路径: 类路径下的资源通常使用类的在加载器读取。
public void readFile(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获得类的加载器:
Class clazz= ServletContextDemo08.class;
ClassLoader loader = clazz.getClassLoader();
// InputStream in = loader.getResourceAsStream("d.txt"); // 相对于:类路径: classes
InputStream in = loader.getResourceAsStream("/d.txt"); // 相对于:类路径: classes
String string = IOUtils.toString(in);
response.getWriter().println(string);
IOUtils.closeQuietly(in);
}
//使用Class 获得资源流: //获得a.txt a.txt没有在类路径下,所以不能被Class 类的方法获得。
// class 只能获得类路径下的内容: d.txt c.txt
public void readFileByClass(HttpServletRequest request, HttpServletResponse response) throws IOException{
Class clazz= ServletContextDemo08.class;
// InputStream in = clazz.getResourceAsStream("/d.txt"); // / : 相对路径: 相对于类路径: classes
InputStream in = clazz.getResourceAsStream("c.txt");// 相对路径: 相对于当前文件所在的位置:
System.out.println(IOUtils.toString(in));
IOUtils.closeQuietly(in);
}
}