Servlet 知识点总结(来自那些年的笔记)

时间:2022-01-16 07:55:26
2018年04月15日 20:16:01 淮左白衣 阅读数:350
 
版权声明:转载请给出原文链接 https://blog.csdn.net/youngyouth/article/details/79939190

(史上最全知识汇总)转载请贴上原文链接!

作者:淮左白衣

写于 2018年4月15日20:14:55
  • 1
  • 2
  • 3

如果,碰巧你打开了本篇博客,相信我,你想要的servlet知识,这里应该都能找到!!

目录


Servlet开发

动态web资源开发,技术有两种:Servlet 和 JSP ;

  • 什么是Servlet开发

    Servlet是Sun公司提供的一门用于开发动态web资源的技术;

  • 如何用Servlet开发一个动态web资源(即如何编写一个servlet类)

    Sun公司在其 Api 中提供了一个Servlet接口,用户若想开发一个动态的Web资源(即开发一个java程序向浏览器输出数据),只需要完成以下两个步骤: 
    1、编写一个java类,实现Servlet接口 ;

    2、把开发好的java类部署到服务器中 ;


IDEA如何配置tomcat和WEB项目

http://blog.csdn.net/yhao2014/article/details/45740111


什么是生命周期方法

一个对象拥有其自身的一个生命周期;在其生命周期的过程中,不同时间段,会执行不同的方法;这些方法,被称为 生命周期方法;

Servlet接口中的方法,就是生命周期方法 ;

servlet接口代码:

    import java.io.IOException;

    public interface Servlet {
void init(ServletConfig var1) throws ServletException; ServletConfig getServletConfig(); void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; String getServletInfo(); void destroy();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

其中我们重点关注的 service() 方法,我们为WEB应用写的逻辑,全放在这里,这是servlet的几个几个生命周期方法中,需要我们关注的一个方法;


向浏览器写数据

我们代表的是Tomcat服务器:我们编写一个servlet类,在service方法中获取响应头的输出流,然后往流中写数据;

    /**
*
* @param servletRequest 获得浏览器请求
* @param servletResponse 获得服务器响应
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
// 获取回应,以便向浏览器写数据
OutputStream out = servletResponse.getOutputStream() ;
// 写数据
out.write("hello Servlet".getBytes());
// 关闭流
out.close();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在Web.xml文件中配置servlet类:

<!—注册servlet-->

 <servlet>
<servlet-name>getnum</servlet-name>
<servlet-class>day06.输出数据</servlet-class>
</servlet> <!—映射关系,下面为地址,即在浏览器中输入的URN--> <servlet-mapping>
<servlet-name>getnum</servlet-name>
<url-pattern>/getnum</url-pattern>
</servlet-mapping>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

<servlet> 用于注册Servlet,其中含有两个子标签:<servlet-name> 和 <servlet-class> ;

<servlet-name> :为Servlet注册一个友好的名字 ; 
<servlet-class> :指明为哪一个Servlet类起个友好的名字,名字要写全限定名 ;

<servlet-mapping> 用于映射一个已注册的Servlet的一个对外访问路径,其中包含两个子标签:<servlet-name>和 <url-pattern>

<servlet-name> :指明为哪一个Servlet类配置对外访问路径 
<url-pattern> :指定对外访问的路径 ;

同一个Servlet可以被映射到多个URL上,即多个 <servlet-mapping> 的<servlet-name> 的值,可以是同一个Servlet ;

伪静态:将一个动态web资源,映射到一个静态web资源上,就在映射地址上,写上后缀,诸如 .html ,浏览器在访问的时候,看到URL中的资源后缀名是 .html 以为访问的是一个静态资源,其实是被我们伪静态的一个动态资源;

    <servlet-mapping>
<servlet-name>myform</servlet-name>
<!--映射的地址,加上了 .html 的后缀-->
<url-pattern>/myform.html</url-pattern>
</servlet-mapping>
  • 1
  • 2
  • 3
  • 4
  • 5

映射地址通配符的问题

Servlet映射到 URL中也可以使用 * 通配符,但是只能有两种固定的格式: 
1、*.扩展名 只要满足这个后缀名,就会访问到这里 ; 
2、以正斜杠(/)开头并用 /* 结尾的 ; /aa/* 只要是aa/,无论后面写什么,都无所谓;都会访问到这里 ;


Servlet映射冲突问题

其中谁长得最像的,就匹配谁; 并且通配符在前面的,优先级最低 ;


Servlet 调用过程

在浏览器输入地址以后,到获取数据的过程中,都经历了什么?
  • 1
  1. 首先,客户机浏览器根据ip地址,找到要访问的服务器;连接上这台服务器;然后客户机的浏览器发送http请求头给服务器;
  2. 服务器收到请求头,以后分析协议请求头,得知客户机要访问的主机、web应用、哪一个资源 ;
  3. 服务器上的动态资源,都对应着一个servlet类, 如果该资源是 第一次 被外部访问,也就意味着servlet类是第一次被访问,那么,服务器会创建一个该Servlet类的一个对象 ,这个对象有生命周期方法,创建完毕以后,自动执行init()方法,进行初始化; (只有这里,才会对 第一次 很在意)
  4. 然后,服务器创建代表请求的ServletRequest对象;代表回应的ServletResponse对象,这时候的ServletResponse是 空的回应头; (这一步是每次访问都会执行)
  5. 接着调用servlet的service()方法,来响应客户机的请求 ;执行我们写的逻辑 ;
  6. Servlet对象将service()方法的处理结果返回;也就是将数据写到了ServletResponse对象中;
  7. 服务器,发现ServletResponse对象中有数据了 ,就会从ServletResponse中取出数据,构建一个http响应头,回送给客户机;
  8. 客户机的浏览器收到回应,解析出数据 ;

备注:服务器在启动的时候,会把所有的web应用加载一次;注意,启动服务器的时候,并不会创建servlet实例对象;并且访问不同的动态资源会创建不同的servlet实例对象 ;一个动态资源只会创建一个servlet对象(第一次被访问的时候,创建) ;


Servlet 的一些细节

  • 什么是 servlet

    Servlet是一个供其他java程序(Servlet引擎)调用的java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度 ;

  • servlet对象,会被创建几个?

    针对客户端的多次对 同一个servlet,发起请求,通常情况下,服务器只会创建一个Servlet实例对象,创建完成以后,这个对象就会驻留在服务器内存中 ;为后续的其他请求服务,直到web服务器关闭或者对应的web应用从服务器中移除,这个Servlet实例对象就会被销毁 ;

  • 每次向服务器发起请求,都经由哪几个方法处理

    在servlet的整个生命周期内,servlet的init()方法,只会被调用一次,就是在第一次访问的时候;而对于servlet的每次访问请求,都会调用一次servlet的service()方法 ;


请求头和响应头对象的创建时间、次数

对于 每次 客户机的访问,服务器的servlet引擎,都会新建一个新的请求头对象和响应头 对象;其中请求头对象保存客户机传来的请求头 ,响应头对象刚创建的时候,里面不保存有任何数据;

servlet引擎 将它们作为参数传给 service 方法;在service方法中,根据处理逻辑,往响应头中写入数据 ;,当 service 方法 执行结束以后,服务器发现响应头不再为空,就会取出数据,构建出一个http响应头回送给客户机 ;

对于上述所说的,每次请求,都会创建一个响应头、请求头对象;服务器受得了吗?

答案:响应头、请求头对象,在内存中驻留的时间的是非常短的;请求结束,就会被销毁了;因此,只要不是高并发的访问,服务器就可以接受 ;


HttpServlet

我们上面说过,要想开发一个servlet程序,只需要写一个实现servlet接口,就好了;

我们在源代码里面可以看出,servlet 接口定义了一个servlet的 所有的生命周期方法 ,但是我们在实际开发中,只关注 service()方法 ;而我们要是直接实现 servlet 接口的话,其他几个方法,也需要我们写一下,这是很麻烦的事,因此Sun公司的 api 文档中,还提供了一个 已经实现好的servlet子类 GenericServlet ;

但是 GenericServlet 是一个普适的实现类,而我们开发中,经常要与HTTP 打交道,这时候,他们又在 GenericServlet 的基础上,实现了一个 HttpServlet 类 ;因此,我们在写servlet程序的时候,一般不让GenericServlet;而是使用HttpServlet;

Httpservlet 是能够处理Http请求的Servlet,他在原有的Servlet接口上添加了一些与HTTP协议相关的处理方法,比Servlet接口功能更为强大;因此,我们在开发时,通常都继承这个类,而非直接去实现Servalet或者使用GenericServlet;

Httpservlet 在实现接口的时候,重写了 service() 方法,该方法体内的代码,会自动判断用户的请求方式;如果为 GET 请求,则调用Httpservlet 的 doGet 方法;如果为 POST 请求,则调用Httpservlet 的 doPost 方法;

注意:doGET() 、doPOST() 方法,是Httpservlet 自己定义的,并不是 GenericServlet 和 Servlet 接口里面的 ;

Httpservlet 重写以后的 service() 方法

  protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
// 如果发现浏览器的请求方式是 GET ,则调用 this.doGet(req, resp)方法
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
} if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
// 判断浏览器的请求方式是否是 HEAD
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
// 判断浏览器的请求方式是否是 POST
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

从上面的源代码中,可以看出,我们以后在继承 Httpservlet 类的时候, 我们不需要,也没理由去重写这个service方法; 我们只需要重写 doGET() 、doPOST() 方法 ;


load-on-startup 标签

如果在 <servlet> 元素中配置了一个 <load-on-startup> 元素,那么WEB应用程序在被加载的的时候,就会装载并创建Servlet对象,以及调用init()方法 ;标签中间写的值,必须是正整数,数字越小,优先级越高 ; 优先级体现在,当有多个

 <servlet>
<servlet-name>buycar</servlet-name>
<servlet-class>day07.BuyCar</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
  • 1
  • 2
  • 3
  • 4
  • 5

用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库和逻辑 ;


配置缺省的servlet

这与上一篇中讲 配置缺省的WEB应用,是两码事 ;缺省应用是相对于服务器来说,而缺省servlet是相对于web应用来说;
  • 1
  • 方法:

    如果某个servlet的映射路径仅仅是一个正斜杠 / ,那么这个Servlet就成为当前web应用程序的缺省Servlet ;

    凡是在web.xml文件中,找不到匹配的 <servlet-mapping> 元素的URL,它们的访问请求都将被交给缺省的servlet处理,也就是说,缺省的servlet用于处理其他servlet都不处理的访问请求 ;

  • 用途:

    首先明白一个道理:我们任何一个浏览器向服务器发送请求,其实都是访问servlet来的;当我们发送的地址,在服务器中没有对应的servlet映射到这个地址上时,就会去找缺省的servlet ;

  • 服务器默认缺省的servlet :

    服务器已经帮我们配置好了一个缺省的servlet,它引用的servlet是阿帕奇的一个什么类;并且这个servlet还配置了<load-on-startup>,数值为1.意味着随着服务器的启动而启动 ;

    我们在浏览器中访问静态web资源,在web.xml文件中,明显是找不到匹配的 <servlet-mapping> ,因为,里面配置都是servlet类; 
    因此,我们访问静态资源的时候,其实都是访问缺省的servlet;都是由这个缺省的servlet完成的,它引用的阿帕奇的一个什么类,会自动去静态web资源的位置,寻找资源;如果没有这个资源,就会返回404页面; 
    假如,我们自己也配置了一个缺省的servlet,那么就会覆盖掉服务器的缺省servlet,这样,服务器的静态资源,就无法访问了 ;


Servlet线程安全问题

当 多个客户端 并发的访问 同一个 servlet时,web服务器会为每一个客户端的请求创建一个线程,并在这个线程上调用servlet的service方法 ;因此,如果service方法内,访问同一个资源的话,就会可能引发线程安全问题 ;(现在是多个线程、一个servlet对象)

当 在服务器中,也就是这里的 service()方法,我们是不能加锁的;加锁的话,一个资源同一时间只有一个人可以访问到,这还是网站吗;

如果,某个servlet实现了 SingleThreadModel 接口,那么servlet引擎将以 单线程模式 来调用其service方法 ;

SingleThreadModel 接口中没有定义任何方法,只要在servlet类定义中增加SingleThreadModel接口的声明即可 ;这种接口,java通常叫做 标记接口 ;

对于实现了SingleThreadModel 接口的servlet,servlet引擎仍然支持对该servlet的多线程并发访问,其采用的方式是产生多个servlet实例对象,并发的每个线程分别调用一个独立的servlet对象 ;(也就是说,当前servlet对象在为其他人服务的时候,有新的请求来访时,服务器会新建一个servlet对象来继续提高服务)(现在是多个线程、多个servlet对象)

实现 SingleThreadModel 接口,并不能 真正的解决 servlet的线程安全问题, 因为servlet引擎会创建多个servlet实例对象; 
而真正意义上的解决多线程安全问题是指 一个servlet实例 被多个线程同时调用 的问题 ;

SingleThreadModel 实质上并未解决这个问题,并且这个方法现在已经过时了;(哈哈哈,学了半天,是个过时的方法,妙啊!,,,)


ServletConfig对象

在Servlet的配置文件中,可以使用一个或者多个标签为servlet配置一些 初始化参数 ;

当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自定将这些初始化参数 封装到servletConfig对象 中,并在调用servlet的init()方法时,将servletConfig对象传递给servlet。进而,程序员可以通过 ServletConfig 对象,得到当前servlet的初始化参数信息。

用于封装,不适合在程序中写死的数据 ;


获取ServletConfig对象

因为servlet类的爷爷,也就是 Genericservlet 类中已经将 init() 方法的servletConfig参数封装进一个对象里面,保存到本地。并且提供了获取这个对象的方法getServletConfig( )方法;但是这里封装具体是个什么对象,我不知道,我翻看源码,也没找到,只知道servletConfig 是个接口,服务器在这里使用了多态 ;为了描述方便,我还称这个对象是servletConfig对象吧;

看源代码:

    public ServletConfig getServletConfig() {
return this.config;
}
// 在初始化一个servlet 之前,服务器会将参数封装进 实现了 ServletConfig接口 的对象里面
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

因此可以在servlet对象中,通过 getServletConfig() 方法直接获取servletConfig对象 ;

获取servletConfig对象中参数,可以根据名字获取,也可以一下子获取到所有的配置参数,返回到一个枚举中;

//  根据名字获得对应的参数
String getInitParameter(String var1);
// 一下子获得所有的参数
Enumeration<String> getInitParameterNames();
  • 1
  • 2
  • 3
  • 4

其中遍历枚举类型的方法:

    while(e.hasMoreElements()){
e.nextElement() ;
}
  • 1
  • 2
  • 3

服务器传递给servlet的对象

客户机访问web资源的时候,首先客户机的浏览器会先访问到服务器,然后,服务器根据请求头,才知道要具体访问哪一个资源;在具体访问某一个资源的时候,又会创建Servlet对象,在创建servlet对象的时候,会传递许多对象给servlet;

传递的对象有:Request,Response,servletConfig,servletContext,session,cookie;


ServletContext对象

WEB容器在启动时,他会为 每个web应用程序 都创建一个对应的 ServletContext 对象,它代表当前的web应用 ; 被当前WEB应用中的 所有servlet 共享 ; (注意是servlet,而不是整个web)

ServletConfig 接口中维护了 ServletContext 对象的引用

public interface ServletConfig {
String getServletName();
// 定义了一个方法,获得 SercletContext 对象
ServletContext getServletContext(); String getInitParameter(String var1); Enumeration<String> getInitParameterNames();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

GenericServlet 类中的具体实现:

 public ServletConfig getServletConfig() {
return this.config;
}
// 获取到servletContext对象
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

开发人员在编写servlet时,可以通过servletConfig.getServletContext() 方法获得ServletContext对象 ;它拥有一些全局性方法 ;当然我们还是愿意直接使用 getServletContext() 方法直接获得;

向其中添加数据:使用servletContext 添加数据,就是添加一个属性,setAttribute(键值对) ;


ServletContext对象生命周期

创建:在服务器启动的时候,就会去加载每一个web应用,在加载web应用的时候,就会为每一个web应用创建一个ServletContext对象 ;

销毁:停掉服务器;或者删除某一个web应用 ;就跟servlet对象一样,销毁的时机 ;


ServletContext类中方法的应用

ServletContext 也是一个接口,我翻看源码,也没找与它相关的实现类。。。为了描述方便,也称之为ServletContext对象 ;
  • 1

由于一个web应用中的所有servlet共享同一个ServletContext对象,所以单个servlet可以通过servletContext对象实现数据共享(网络聊天室); 
因为servletContext对象的共享属性,servletContext对象通常也被称为 context域对象 ;

  • 获取web应用的初始化参数 ;

    前面讲ServletConfig的时候,说可以通过 ServletConfig 对象获得初始化参数 ; 
    我们这里讲的 servletContext 对象,也可以获得初始化参数 ;

    其实是这两个接口,都定义了相同的抽象方法;

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();
  • 1
  • 2
  • 3

转发

说到转发,就要提到重定向了;
  • 1

重定向:就是浏览器向服务器提交一个请求,服务器告诉它没有这个资源,资源在某某某哪里,让浏览器自己去找某某某;这里客户机发 两次 请求 ;

转发:则是服务器虽然没有这个资源,但是服务器帮浏览器去问有这个资源的人要 ; 
这里客户机发一次请求 ;

转发技术应用的特别广;servlet不适合输出数据的;因为我们想输出的数据格式漂亮一点的话,就需要写HTML,css;在servlet里面写这些,简直要命;因此,servlet会做一个转发,将它产生的数据转发到jsp(后面会讲到什么是JSP)那里,jsp再对数据做美化,然后输出 ;


如何在servlet中进行转发

  1. 在 servlet 中将 http请求 转发给 jsp ;
  2. 并且将数据传递到jsp中;

    大家可能还记得,servletContext,我们说过它是一个WEB应用所共享的;但是这里并不能使用Context域。会有 线程安全问题 ; 
    假如在传递数据之前,有其他请求也往servletContext添加了数据,并且键的名字是一样的,那么传递过去的数据,就被改变了;所以这里应该使用request域,一个请求持有一个request ;

  3. 通过 servletContext 对象的 getRequestDispatcher(jsp的路径) ;该方法返回一个转发对象 RequestDispatcher
  4. 调用转发对象的 forward(请求头,应答头); 就会将请求转到jsp那里 ;
  5. 在jsp中,是可以嵌套java的
  6. 在 <% java代码 %> 在这里面写java代码;将数据提取出去,写到应答头中 ;可以在<% %> 
    外部任意嵌套HTML标签,对数据进行修饰 ;

在jsp中 application 代表 servletContext ;取数据,就是获取属性的方法:getAttribute(键) ;取出来的时候,是一个object,需要强转 ;


利用ServletContext对象读取资源文件

  • (资源)配置文件的类型

    首先,明确javaweb中,配置文件一般有两种:xml文件和properties文件; 
    当配置文件的数据没有关系的时候,使用properties文件;假如,配置文件的数据之间有关系,就选用xml文件;

  • 读取资源文件的方法

    利用 servletContext 的 getResourceAsStream(路径) ;返回一个 inputStream ;

       getServletContext().getResourceAsStream() ;
  • 1
  • 2
  • getResourceAsStream(路径)的参数路径(相对路径)

    在Javaweb中 ,路径分隔符是 : /

    当我们的路径是给 浏览器 用的时候,\ 代表的是 网站; 
    当我们的路径是给 服务器 用的时候,\ 代表 web应用;

    例如: 
    •如果配置文件是放在src下面的,而我们 IDEA 中 src 下面的类文件是在 WEB-INF文件夹 下面的 classes文件夹 下面,因此对应的目录是 /WEB-INF/classes/db.properties 
    •如果是放在 WebRoot 目录下,对应的目录是: /db.properties

    简单说,就是对应在服务器中的文件夹目录 ;


Web中的FileInputStream

在javaweb中读取一个文件,就不要再像小时候学java那样了,使用 fileInputSteam ;而是改用servletContext的getResourceAsStream 方法;

在web中因为 fileInputSteam 的路径,除非你写明白绝对路径,否则就是相对路径,相对的是java虚拟机的路径,而java虚拟机在其他地方啊,所以导致写相对路径根本访问不到;除非在虚拟机的目录下添加文件 ;在java中,getResourceAsStream(路径) 的 相对路径是java类的所在文件路径 ;


读取资源文件的三种方式

Web应用中的servlet程序读取资源文件

  1. 通过servletContext对象的getResourceAsStream(配置文件在服务器的路径) 方法获得 input 流
  2. 通过servletContext对象的 getRealPath(配置文件在服务器的路径) 返回配置文件的绝对路径;

    获得文件的 绝对路径 然后就可以使用 FileInputStream 读取了 ;这种方式,一般用在下载的时候,需要获取资源的名称 ;从绝对路径中截取 ;

Web应用中的普通java文件读取资源文件

如果读取资源文件的程序不是servlet的话,那就没有servletContext对象给我们用了;

就只能通过类加载器来读取了 ;但是资源文件不能太大 ;它会把资源文件当做类一样,加载到内存中,文件太大,内存就会溢出 ;


为什么要通过 类加载器读 取读取资源文件?

答案:因为servlet类有一个servletContext对象的,这个对象是整个web中的servlet对象共享的;它能操控整个web应用的资源;因此,就可以读取资源文件;

而在普通的java类中,是无法直接获取到servletContext对象的;除非,传进来一个,这样耦合性就变高了 ;

没有这个servletContext对象,我们要怎么才能在普通java的类中加载资源文件呢?用到类加载器;既然类加载器可以把java类加载到服务器中,那么资源文件是和java类文件在一个文件目录下面的,那么资源文件同样也可以被类加载器加载 ;


如何获得类加载器

类加载器只有一个,只要随便获得一个类的类加载器就可以了;

获取类加载器的方法:类名.class.getClassLoader() ;

类加载器加载资源的方法: getResourceAsStream(资源文件路径) ;返回一个InputStream ;


关于类加载器读取资源的方法的参数问题

getResourceAsStream(资源文件路径);这里的路径到底怎么写
  • 1

这里的资源文件路径,得看使用的 类加载器 怎么得到的。

下面的2个方法,都是直接 class.getResourceAsStream ;没有 getClassLoader 并且路径都是以 / 开头的 ;

  • 任意类名.class.getResourceAsStream (“/文件所在的位置”); (注意这里有个 / ,)

    文件所在的位置从包名开始写

  • 和资源文件文件在同一个目录下的 类名.class.getResourceAsStream (“/文件所在的位置”);

    文件所在的位置从包名开始写, 】注意其实这里是可以不写 / ;为了和下面的区分,我们还是写上;


下面的3个方法,都是通过 getClassLoader ,并且路径都不是以 / 开头的 ;

  • 任意类名.class.getClassLoader().getResourceAsStream(“文件所在的位置”);

    文件所在的位置从包名开始写

  • 任意类名.class.getClassLoader().getResource(“文件所在的位置”).openStream();

    文件所在的位置从包名开始写

  • 任意类名.class.getClassLoader().getResource(“文件所在的位置”)..openConnection().getInputStream();

    文件所在的位置从包名开始写

    我们发现 其实资源路劲,都是从包名开始写,只是开头,是否有 / 的区别 ;

    有getClassLoader 就没有 / ;


类加载器加载文件的一个问题

场景: 类加载器加载类文件到内存中,只会加载一次;只要这个类曾经被加载到内存中,就会创建这个类的字节码对象;只要内存中还有这个字节码对象,就会不会再次加载这个类;

问题:因此,当我们使用类加载器加载资源文件的时候,只要类加载器加载过资源文件一次,后面只要服务器不停,即使更新了资源文件,新的资源文件也不会被加载到内存中,因此,导致了一个资源更新,察觉不到的问题:

解决方法:我们还是要使用传统的方法——读取文件流来读取文件,不能使用类加载器加载资源 ;因此,我们先使用类加载器获得资源的绝对路径

方法类名.class.getClassLoader.getResource(资源的相对路径).getPath() ;通过这样获取资源的绝对路径;得到绝对路径以后,就可以使用传统java的读取方法读取文件了; 
再使用FileInputStream读取文件 ;

说到这,我发现 类加载器 和 servletContext 有诸多相似的地方,不知道他们之间有着什么联系


为整个web应用配置参数

在web.xml中使用 <context-param> 标签 ;这个标签中的参数,在服务器加载这个web应用的时候,会自动把参数添加到 servletContext 对象中 ;(笔者没有笔误,这里确实是 servletContext 对象,而不是上文说的 servletConfig 对象

用途:为整个web应用配置共享配置 ;