(史上最全知识汇总)转载请贴上原文链接!
作者:淮左白衣
写于 2018年4月15日20:14:55
- 1
- 2
- 3
如果,碰巧你打开了本篇博客,相信我,你想要的servlet知识,这里应该都能找到!!
目录
- (史上最全知识汇总)转载请贴上原文链接!
- Servlet开发
- IDEA如何配置tomcat和WEB项目
- 什么是生命周期方法
- 向浏览器写数据
- 在Web.xml文件中配置servlet类:
- 映射地址通配符的问题
- Servlet映射冲突问题
- Servlet 调用过程
- Servlet 的一些细节
- 请求头和响应头对象的创建时间、次数
- HttpServlet
- load-on-startup 标签
- 配置缺省的servlet
- Servlet线程安全问题
- ServletConfig对象
- 获取ServletConfig对象
- 服务器传递给servlet的对象
- ServletContext对象
- ServletContext对象生命周期
- ServletContext类中方法的应用
- 转发
- 如何在servlet中进行转发
- 利用ServletContext对象读取资源文件
- Web中的FileInputStream
- 读取资源文件的三种方式
- 为什么要通过 类加载器读 取读取资源文件?
- 如何获得类加载器
- 关于类加载器读取资源的方法的参数问题
- 类加载器加载文件的一个问题
- 为整个web应用配置参数
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
- 首先,客户机浏览器根据ip地址,找到要访问的服务器;连接上这台服务器;然后客户机的浏览器发送http请求头给服务器;
- 服务器收到请求头,以后分析协议请求头,得知客户机要访问的主机、web应用、哪一个资源 ;
- 服务器上的动态资源,都对应着一个servlet类, 如果该资源是 第一次 被外部访问,也就意味着servlet类是第一次被访问,那么,服务器会创建一个该Servlet类的一个对象 ,这个对象有生命周期方法,创建完毕以后,自动执行init()方法,进行初始化; (只有这里,才会对 第一次 很在意)
- 然后,服务器创建代表请求的ServletRequest对象;代表回应的ServletResponse对象,这时候的ServletResponse是 空的回应头; (这一步是每次访问都会执行)
- 接着调用servlet的service()方法,来响应客户机的请求 ;执行我们写的逻辑 ;
- Servlet对象将service()方法的处理结果返回;也就是将数据写到了ServletResponse对象中;
- 服务器,发现ServletResponse对象中有数据了 ,就会从ServletResponse中取出数据,构建一个http响应头,回送给客户机;
- 客户机的浏览器收到回应,解析出数据 ;
备注:服务器在启动的时候,会把所有的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中进行转发
- 在
servlet
中将http请求
转发给jsp
; -
并且将数据传递到jsp中;
大家可能还记得,servletContext,我们说过它是一个WEB应用所共享的;但是这里并不能使用Context域。会有
线程安全问题
;
假如在传递数据之前,有其他请求也往servletContext添加了数据,并且键的名字是一样的,那么传递过去的数据,就被改变了;所以这里应该使用request域,一个请求持有一个request ; - 通过
servletContext
对象的getRequestDispatcher(jsp的路径)
;该方法返回一个转发对象RequestDispatcher
; - 调用转发对象的
forward(请求头,应答头)
; 就会将请求转到jsp那里 ; - 在jsp中,是可以嵌套java的
- 在 <% 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程序读取资源文件
- 通过
servletContext
对象的getResourceAsStream(配置文件在服务器的路径)
方法获得 input 流 -
通过
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应用配置共享配置 ;