J2EE项目在生产环境中,通常以WAR,EAR包等形式部署在服务器上,在Websphere Application Server(WAS)服务器上部署应用时,有时项目需要获取文件的物理路径,但是WAR包部署时,在tomcat和WAS下部署有很大的差别:tomcat会把WAR包解压到当前文件夹下,然后再用解压的文件夹来发布,这样与本地编写调试的程序环境类似;而WAS或WebLogic两种服务器,WAR包就不会被解压;如果开发过程中不注意,WAR包公布后就有可能出现找不到文件的现象。比如:程序使用下面方法获取路径:

this.getClass().getClassLoader().getResource("/").getPath();

这样得到的是Uri路径,比如::/E:/tomcat8/server/default/deploy/Spitter.war/WEB-INF/classes/,这种带盘符的路径,显然在WAR包中无法依据盘符来找到文件;

另外一种方式:

this.getClass().getResource("/").getPath();

那么这样获取行不行呢?

经试验,这样获取的是当前类的Uri文件夹,比如:/E:/tomcat8/server/default/deploy/Spitter.war/WEB-INF/classes/org/george/helper/也是绝对路径,显然无法适用于war包。

事实上,存在一种绕行方案,读取文件未必要读取路径:文件操作一般都要转换为流的方式,既然要读取文件,不如直接读取输入流,也少了一步封装。请看下面方式:

InputStream is= this.getClass().getResourceAsStream("/config/george/" + templateFileName);

这个操作可以读取classes文件夹中,文件夹config/george/下,文件名称为templateFileName的文件输入流。经试验,在WAR包中能够正常读取到文件流。

该方法存在一个弊端。如上所述,这个操作仅仅能读取classes文件夹下的文件。对于其它文件夹下的文件无能为力,显然并不适用于全部场景。

假设文件在WEB-INF文件夹下。怎样进行读取呢?

答案是使用ServletContext.getResourceAsStream(String)方法。

也就是先得到上下文信息,然后通过以project文件夹为root的绝对路径,找到文件,举例说明:

InputStream is = context.getResourceAsStream(templatePath + "/" + templateFileName);
templatePath = "/WEB-INF/classes/config/george/";
templateFileName = "source.xls";

能够看到templatePath是相对于context root 的路径,而不是相对于classes,这样即使文件在WEB-INF其它文件夹下。也能够顺利找到。经试验,可以对WAR包中使用该方法。

请来看看ServletContext.getResourceAsStream的API文档,
Returns a URL to the resource that is mapped to a specified path. The path must begin with a "/" and is interpreted as relative to the current context root. 
This method allows the servlet container to make a resource available to servlets from any source. Resources can be located on a local or remote file system, in a database, or in a .war file.

相信大家都看得懂。就不用赘述了。仅仅是有个问题,context是个什么东西?答案:ServletContext,即上下文信息。在J2EE类中使用request获得。如:

ServletContext context = request.getSession().getServletContext();

那么在普通类中怎样获取呢?有两个办法:使用application是一种方式,第二种方式就是想办法先去request对象,如:

RequestAttributes ra = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) ra).getRequest();
this.context = request.getSession().getServletContext();

这样也是能够获得Context的;

如果是JSP中,也可以直接如下获取输入流:

application.getResourceAsStream("xxx”);

路径问题不要纠结太久,依据以上办法解决,最好都用ServletContext方法来获取。仅仅需要知道一种情况就能够了。达到目的才是最重要的。

终极方案:当使用spring定时器时,request和ServletContext无法获取,可以用下面的办法直接获取ServletContext:

ContextLoader.getCurrentWebApplicationContext().getServletContext();

在George项目中的使用总结:

读写文件的时候可能需要获得路径,比如上传文件的时候就需要输出流。而通过ServletContext不可以直接获得输出流的,但可以直接获取文件的实际路径。如:

servletContext.getRealPath("/");

这样获得的路径是${context}/的路径,可以依据子路径和文件名称拼接获取输出流