下面在来看一下ServletContext对象,关于这个对象我们要详细的了解他,我们在之前介绍ServletConfig对象的时候,发现ServletConfig对象有一个方法:getServletContext(),通过这个方法可以获取一个ServletContext对象。当然也可以通过this.getServletContext()方法得到这个对象。ServletContext是很重要的一个对象,他同时也是四大域对象之一的context域对象,从他的名字我们就知道context是上下文的意思,(在Android中也有这样的一个概念,getApplicationContext()可以获取当前的Android应用的上下文对象)。那么这里的context就代表这个整个web应用,所以他的生命周期是:当服务器启动的时候会为每一个web应用创建一个ServletContext对象,当web应用销毁了或者关闭服务器这个对象也就随之销毁。这里我们也可以看到他的生命周期是最长的,贯彻整个web应用。下面就来看一下他的相关api(只说一些重要的,常用的方法)
package com.weijia.httpservlet;
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;
public class Servlet2 extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
ServletContext context = this.getServletContext();
//获取属性值
String data = (String) context.getAttribute("data");
//打印
response.getWriter().write(data);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
doGet(request,response);
}
}
在浏览器中输入地址:http://localhost:8080/ServletDemo/Servlet1
从Servlet1中存入的值通过转发到Servlet2中取出来进行读取显示。
下面在来看一下getInitParameter(String name)和getInitParameterNames(),我们读取全局配置参数,其实这个配置是对每个Servlet都是有效的,所以他是全局的效果,我们之前使用ServletConfig进行对某个Servlet进行初始化参数配置,数据库连接的信息这个东东是最每个Servlet都是有效的而且都是一样的配置,所以我们应该将这些信息配置到全局中ServletContext,上面配置到ServletConfig中只是为了演示ServletConfig的作用。这种全局性的信息肯定是要配置到ServletContext中的。
package com.weijia.httpservlet;
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 Servlet1 extends HttpServlet {
private static final long serialVersionUID = 1L;
@SuppressWarnings("unchecked")
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
ServletContext context = this.getServletContext();
response.setHeader("expires", "-1");
Enumeration
names = context.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
System.out.println(name+" = " + context.getInitParameter(name));
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
doGet(request,response);
}
}
在web.xml中进行配置:
url
http://localhost:8080
username
jiangwei
password
123456
这个是全局进行配置的,所有的Servlet都可以进行读取使用。 结果:
下面在来看一下getResouceAsStream(String path)和getReal(String path)方法
这两个方法主要是用来读取web应用中的资源文件的,这个用处很大的,首先我们来看一下web应用的项目结构:
我们分别src目录中新建一个db.properties属性文件和在com.weijia.serlvetcontext包中新建一个db.properties属性文件:
先来看一下怎么读取包中的属性文件
ServletContext context = this.getServletContext();
InputStream is = context.getResourceAsStream("/WEB-INF/classes/com/weijia/servletcontext/db.properties");
使用getResourceAsStream方法获取一个InputStream流,我们需要填写属性文件的路径path: 重点在于这个path:这个path的书写是有规则的:
/WEB-INF/classes/com/weijia/servletcontext/db.properties
首先第一个"/"代表当前的web应用(这个和JavaSE中读取文件不同,JavaSE中读取文件的话是一定不能以斜杠开头的,不然就报错),然后是WEB-INF/classes,这个路径我们之前说过,一个web应用发布之后是没有src这样的目录的,我们在第一个手写Servlet的例子中我们也看到了,我们并没有新建一个src文件夹,而是新建一个classes文件夹,在这个文件下面新建一个Servlet的,这点一定要注意;然后就是包名了路径了,因为包名会被映射到文件目录。这样我们就可以读取到了属性文件。
那么读取src目录中的属性文件就更简单了:
/WEB-INF/classes/db.properties
这里千万不要写成:
/src/db.properties
一定要记得web应用发布之后是没有src目录的,这里的src目录就相当于是classes目录
上面的这个方法是返回一个InputStream流的,但是如果我们现在想通过FileInputStream文件流来读取文件我们该怎么做呢?
那么我就必须要获取到文件的路径,那么我们能这样写吗:
FileInputStream fis = new FileInputStream("classes/db.properties")
这样写行不行呢?我们运行一下就知道了,我发现运行时报异常的,下面我们就来分析一下这个问题:
首先我们都知道上面的那样写法是相对路径,那么这个相对路径是相对于谁呢?
下面的代码是JavaSE:
System.out.println("UserDir:"+System.getProperty("user.dir"));
File file = new File("src/com/weijia/demo/db.properties");
System.out.println("path:"+file.getPath());
System.out.println("absolutePath:"+file.getAbsolutePath());
运行结果:
我们可以看到JVM是会使用user.dir这样的系统属性来拼接相对路径得到绝对路径的。这个user.dir存储的就是当前的工作目录
所以我们在Servlet中打印一下:
System.getProperty("user.dir"):可以得到当前的工作目录:
打印结果如下:
C:\Program Files\Apache Software Foundation\Tomcat 6.0\bin
可以看到是tomcat的bin目录,那么我们使用相对路径:classes/db.properties这样就相当于绝对路径:
C:\Program Files\Apache Software Foundation\Tomcat 6.0\bin\classes\db.properties
所以我们可以到tomcat目录中的bin文件夹下面新建一个classes文件夹,里面新建一个db.properties文件,这时候在运行程序,就不会在报异常了,这样貌似我们想使用FileInputStream来读取文件的话,很是麻烦呀!
这时候我们就可以使用了getRealPath(String path)方法来获取资源的本地磁盘中的绝对路径:
打印属性地址:
System.out.println("AbsolutePath:"+context.getRealPath("/WEB-INF/classes/db.properties"));
打印结果: AbsolutePath:C:\Program Files\Apache Software Foundation\Tomcat 6.0\webapps\ServletDemo\WEB-INF\classes\db.properties
那么这时候我们就可以通过FileInputStream来读取文件了。
现在还有一个问题就是以后我们在编写Web应用的时候是要遵从多层设计思想的,而且层与层之间不能有侵入行为,层与层之间使用接口进行访问的。比如Service层和Dao层是不能有感染的,就是耦合度要很低的,Servlet就是Service层,那么现在如果想在Dao层中读取文件资源我们该怎么办呢?因为Dao层中没有Servlet,所以没有ServletContext了,那么我们该怎么获取路径呢?有人说可以通过方法的参数形式将ServletContext对象从service层传递到Dao层中,这个方法是可以的,但是这就违背了我们开始说的层与层之间的耦合和互不感染的原则了。那这时候我们该怎么办呀?这时候我们就需要另外一种读取资源的一种方式了,而且这种方式也是可以用于JavaSE中读取文件的。也是一种非常经典的读取资源的方式,这就使用类加载器来读取资源:
Properties dbConfig = new Properties();
dbConfig.load(UserDao.class.getClassLoader().getResourceAsStream("com/weijia/servletcontext/db.properties"));
System.out.println("url:"+dbconfig.getProperty("url"));
System.out.println("username:"+dbconfig.getProperty("username"));
System.out.println("password:"+dbconfig.getProperty("password"));
这样我们就可以不使用ServletContext对象也可以读取到com.weijia.servletcontext包底下的db.properties文件资源了,同样的他也是有一个getRealPath(String path)方法的可以得到本地磁盘的绝对路径,这样我们也可以使用FileInputStream来读取资源了。
其实这种读取资源的方式是:类加载器会把该资源文件和class文件等同一样加载到内存中的,所以问题就出现了:
1.首先这个资源文件肯定不能太大,因为他是和class文件一起加载到内存中,太大的话,内存就爆了
2.只要当类加载的时候这个文件才会被加载到内存中,现在假如我们修改了这个db.properties资源文件,保存,但是我们在读取的时候还是之前的资源文件中的内容,原因很简单,因为我们修改的是db.properties资源文件,而没有修改类文件,所以类加载器并不会再次加载类,那么就不会在加载这个修改过的资源文件了,那么这次修改是无效的,所以说这点我们在使用类加载器读取资源的时候一定要注意了。
下面来看一下Servlet的线程安全问题:
当多个客户端并发访问同一个servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用servlet的service方法,因此service方法如果访问了同一个资源的话,就有可能引发线程安全的问题,如果某个servlet实现了SingleThreadModel(标记接口)接口,那么servlet引擎将以单线程模式来调用其service方法。SingleThreadModel接口中没有定义任何方法,只要在servlet类的定义中增加实现SingleThreadModel接口的声明即可.对于实现了SingleThreadModel接口的servlet,servlet引擎仍然支持对该servlet的多线程并发访问,其采用的方式是产生多个servlet实例对象,并发的每个线程分别条用一个独立的servlet实例对象。实现SingleThreadModel接口并不能真正解决servlet的线程安全问题,因为servlet的引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个servlet实例对象被多个线程同时调用的问题,事实上,在servlet api2.4中,已经将SingleThreadModel标记为Deprecated(过时的).标准的解决方案是同步方式sychronized
如果线程向静态list集合中加入了数据(aaa),数据用完后,一般要移除静态集合中的数据(aaa),否则集合中的数据越来越多,就会导致内存溢出。对象销毁了,静态资源的字节码仍然驻留在内存中.
下面来看一下Servlet的对外访问路径的配置:
我们一个Servlet编写完成之后是需要对其进行配置对外访问路径的,如果我们是新建一个Servlet的话,IDE会自动的帮我们配置好这个对外访问路径,但是有时候我们需要自己进行配置已达到我们需要的效果,这时候我们知道配置应用的web.xml文件中的内容即可,首先要知道一个Servlet是可以配置多个对外访问路径的。下面就直接来一下配置路径的案例吧:
以下是Servlet对外访问路径的配置规则案例:
servlet1 映射到 /abc/*
servlet2 映射到 /*
servlet3 映射到 /abc
servlet4 映射到 *.do
问题:
1.当请求URL为"/abc/a.html","/abc/*"和"/*"都匹配,但是servlet引擎会调用servlet1
2.当请求URL为"/abc"时,"/abc/*"和"/abc"都匹配,但是servlet引擎会调用servlet3
3.当请求URL为"/abc/a.do"时,"/abc/*"和"*.do"都匹配,但是servlet引擎会调用servlet1
4.当请求URL为"/a.do"时,"/*"和"*.do"都匹配,但是servlet引擎会调用servlet2
5.当请求URL为"/xxx/yyy/a.do"时,"/*"和"*.do"都匹配,但是servlet引擎会调用servlet2
总结:谁长的最像,谁先匹配,同时*的优先级最低.
当然我们还可以配置一个默认的访问对外访问路径就是直接一个斜杠:/ 这个默认的对外访问路径的作用就是当一个Servlet找不到其对应的映射路径的时候回去找打这个默认的对外访问路径:
上面的图片就是tomcat中的web.xml文件中配置的默认对外访问路径,当我们访问的路径找不到都会去请求DefaultServlet。
总结:上面我们就介绍了Servlet的相关知识,以及ServletConfig和ServletContext对象,下面总结一下在开发web的时候的问题的解决:
1.首先来看怎么修改Servlet的模板,因为我们在开发一个Servlet的时候,发现在doGet/doPost方法中发现很多没有用的代码,每次都需要删除很是麻烦的,我们进入到MyEclispe的安装目录中全局搜索一个servlet.java文件,打开之后我们将doGet/doPost方法中无效的内容删除了即可(最后在修改之前备份一下这个servlet.java)
2.我们有时候在将一个web应用导入到MyEclipse中的时候项目的名称可能会修改,但是他的Context Path并没有修改,所以在通过浏览器访问的时候还是之前项目的名称路径,这时候我们要修改的话就点击项目的properties如下界面修改:
我们只需要将WebRoot从新映射到一个Context-path就可以了。
3.有时候我们在发布应用的时候会发现以下的问题:
这个问题我们知道是版本的问题,就是使用高版本的JDK去编译web应用,然后在用低版本的JVM(tomcat)去运行,这个问题发生在我们之前手动编写一个web应用FirstServlet,那时候我们是用javac命令进行编译的,这个java y"http://www.2cto.com/kf/yidong/wp/" target="_blank" class="keylink">WPD/MHuysfKudPDwctKREs3LjCw5rG+tcQoztLX1Ly6sLLXsMHLSkRLNy4wKSy1sc7Sw8e08r+qTXlFY2xpcHNltcTKsbryo6zO0tTaTXlFY2xpcHNl1tDF5NbDtcTKx1RvbWNhdDYuMLXEo6x0b21jYXQ2LjDKx9TL0NDU2kpWTTYuMLXEo6zL+dLUu+GxqLTtwcujrMTHw7TO0sPHv8nS1NDeuMRNeUVjbGlwc2XW0LXEdG9tY2F01MvQ0Mv50sDAtbXESlZNo7o8L3A+CjxwPjxpbWcgc3JjPQ=="https://www.cppentry.com/upload_files/article/76/1_dp5sf__.jpg" alt="">
点击add可以添加我们JDK7.0版本的JVM,保存在运行就没有问题了。