理解classpath

一、什么是classpath


  classpath,翻译过来就是类路径的意思,它是包含class文件的路径集合,用于指示虚拟机jvm在这些路径下搜索class文件。   类路径可以同时定义多个,多个类路径之间需要使用分隔符进行分隔,windows环境下使用“;”,linux环境下则使用“:”。下面我们对类路径进行一个简单的分类,如下所示:
类路径的类型 描述
"." 表示当前目录,即执行java命令启动应用的目录。如果我们不显式的设置任何类路径,则默认它包含一个多当前目录;但如果显式设置了类路径,并且设置的路径不包含当前目录的话,则类路径就不包含当前目录了
扩展目录 (1)扩展目录可以是包含class文件的基目录,比如“/home/user/classdir”,该目录作为类路径,其下的内容就开始是包路径目录。
理解classpath (2)扩展目录也可以是一些其他的文件目录,不过jvm并不能从中找到class文件,该目录用于存放一些文件以供查找使用。
jar文件 比如“/home/user/lib/Foo.jar”,该jar文件的完整路径也能作为类路径。当然,从javaSE6开始,可以通过通配符“*”一次指定多个jar,例如“/home/user/lib/*”
运行时库文件,rt.jar,jre/lib,jre/lib/ext 这些不用手动指定,它们默认包含在类路径中
 

二、如何设置classpath


  启动应用时,设置classpath非常简单:
1 #方式1
2 java -classpath 具体的类路径
3 #-classpath也可以简写成-cp
4 java -cp 具体的类路径
5 #示例
6 java -classpath .:/home/user/classdir:/home/user/lib/*
7 
8 #方式2(不推荐使用),使用CLASSPATH环境变量,JVM启动时会去查找CLASSPATH这个环境变量
9 export CLASSPATH=".:/home/user/classdir:/home/user/lib/*"

  需要注意的是,某些情况下不需要我们设置classpath,比如在tomcat或者jetty中启动应用,亦或者是通过springboot可执行jar启动应用时,我们都未设置classpath,但这并不代表不用设置,而是框架或者容器替我们做好了这个事。

 

三、"classpath:"与"classpath*:"


  这是一个扩展话题,我们在spring下常常能看到使用"classpath:"或者"classpath*:"去指定一个资源文件的路径,这两个路径前缀是啥意思呢?   其实"classpath:"或者"classpath*:"都不是java语言自带的东西,它只是spring自定义的一种路径格式前缀而已,意思是以classpath作为根目录的指定位置。举个例子,若应用启动时指定了classpath为"/home/user/classdir",那么对于以"classpath:"开头的路径"classpath:resources/myfile.xml",spring就会在"/home/user/classdir/"下去查找相对路径为myfile.xml的文件,也就是查找绝对路径为"/home/user/classdir/myfile.xml"的文件。   需要说明的是,"classpath:/"与"classpath:"对于spring来说是没有区别的,spring框架在处理这个路径的时候,会将开头的"/"符号去掉,这点对于"classpath*:"也是一样,具体可以查看PathMatchingResourcePatternResolver的getResources方法源码。   最后,我们需要搞清楚"classpath:"与"classpath*:"的区别,两者之间相差一个"*"号,但在查找方式上具有很大的差异,这一点网上大部分博客都是你抄我或者我抄你,要么就没说清楚,要么就直接有错误。

3.1.不包含通配符的路径

  所谓不包含通配符的路径,指的是"classpath:"以及"classpath*:"后面资源文件的路径(含文件名)不包含通配符"*、?"等,因为spring在进一步处理路径时,首先会判断路径是否包含通配符,有和没有通配符的处理方式是完全不同的,这也是我们分开讨论的原因。
  查找规则 进一步说明
classpath: (1)既可以查找类路径中扩展目录下的资源,也可以查找类路径中jar包内的资源。
(2)查找顺序是按照类路径定义的顺序逐个查找扩展目录或jar包(并不是网上说的先去查找扩展目录再查找jar包),并返回查找到的第一个资源。
(1)返回的Resource都是ClasspathResource。
(2)如果资源位于扩展目录中,从ClasspathResource中获取到的是BufferedInputStream;如果位于jar包中,获取到的则是JarURLInputStream。
classpath*: (1)既可以查找类路径中扩展目录下的资源,也可以查找类路径中jar包内的资源。
(2)返回查找到的所有的匹配文件资源,因此可以不考虑查找顺序。
(1)返回的Resource都是URLResource。
(2)如果资源位于扩展目录中,从URLResource获取到的是BufferedInputStream;如果位于jar包中,获取到的则是JarURLInputStream。
 

3.2.包含通配符的路径

  包含通配符的路径指的是"classpath:"以及"classpath*:"后面资源文件的路径(含文件名)包含通配符"*、?"等。这种情况比较复杂,对于classpath尤其如此。因为spring在查找包含通配符的路径时,首先会从路径中提取出一个不包含通配符的“根目录”,它是“从根路径开始的、一个没有通配符的最长路径”。例如,"classpath: static/image/**/icon.png"的根目录为"static/image",而"classpath: **/image/first/icon.png"的根目录为""(后面我们称为空目录),另外"classpath: i*on.png"的根目录也是""。   可以看到,包含通配符的路径,其提取出的根目录有两种情况,一种是空目录,一种是包含了有效路径的目录,"classpath:"在这两种不同的根目录下查找行为有所区别,而"classpath*:"保持了一致,下面用一张表格归纳了它们的查找规则:
  根目录类型 查找规则 进一步说明
classpath: 空目录,即"" (1)只在类路径中的扩展目录下查找资源,无法在类路径中的jar内查找。
(2)查找过程比较复杂:假设定义了3个类路径,分别是扩展目录"dir1"和"dir2",以及通配符jar包"dir3/*",它们的顺序可以是任意的,如"dir1:dir2:dir3/*"或者"dir3/*:dir2:dir1"。step1.首先过滤掉类路径中的jar包,剩下的扩展目录保持定义顺序不变;step2.按照类路径定义的顺序逐个查找扩展目录,如果在某个扩展目录下查找到匹配的资源文件,则将查找范围锁定在该扩展目录下,并返回该扩展目录下所有匹配的资源文件。
(1)返回的Resource都是FileSystemResource,从中获取FileInputStream。
(2)实际上,之所以无法去jar包中查找,是因为ClassLoader的getResources方法在传入""时,只能返回扩展目录资源。
包含了有效路径的目录 (1)既可以查找类路径中扩展目录下的资源,也可以查找类路径中jar包内的资源。
(2)查找过程比较复杂:假设定义了3个类路径,分别是扩展目录"dir1"和"dir2",以及通配符jar包"dir3/*",它们的顺序可以是任意的,如"dir1:dir2:dir3/*"或者"dir3/*:dir2:dir1"。step1.直接按照类路径定义的顺序逐个查找扩展目录或jar包,如果查找到包含“根目录”的某个类路径,则将查找范围锁定在此类路径下,并返回此类路径下所有匹配的资源文件,如果没有匹配的就返回空。
(1)如果资源位于扩展目录中,返回的Resource是FileSystemResource,从中获取到FileInputStream。
(2)如果资源位于jar包中,返回的Resource是ClasspathResource,从中获取到JarURLInputStream。
classpath*: 空目录,即"" (1)既可以查找类路径中扩展目录下的资源,也可以查找类路径中jar包内的资源。
(2)返回查找到的所有的匹配文件资源,因此可以不考虑查找顺序。
(1)如果资源位于扩展目录中,返回的Resource是FileSystemResource,从中获取到FileInputStream。
(2)如果资源位于jar包中,返回的Resource是URLResource,从中获取到JarURLInputStream。
包含了有效路径的目录
 

3.3.总结查找规则

  现在对上面分情况讨论的查找规则进行一个总结,方便我们记忆和使用
  • "classpath*:":总是能在类路径的扩展目录和jar包中查找,并且返回所有的匹配资源。
  • "classpath:"(不含通配符):总是能按照类路径定义的顺序逐个查找扩展目录或jar包,并返回第一个匹配的资源。
  • "classpath:"(含通配符):首先判断根目录是否为"",来决定查找的类路径范围是否需要过滤掉jar包。在处理后的类路径中按照定义的顺序逐一查找,直到查找出第一个匹配的资源文件,同时锁定该资源文件所在的类路径。之后查找并返回该锁定的类路径中所有匹配的资源文件。
 

四、框架和容器中的classpath


4.1.web容器的classpath

  传统项目的部署方式是我们将应用打成war包,部署在tomcat或者jetty这样的web容器中,然后启动web容器,其classpath不需要我们手动设置,web容器在启动时会帮我们设置好,并且约定俗称的将“/path/to/WEB-INF/classes”与“/path/to/WEB-INF/lib”这两个目录设置成classpath,这也与war包的目录结构保持一致:
  • WEB-INF/classes:存放src目录java文件编译之后的class文件、xml、properties等资源配置文件。
  • WEB-INF/lib:存放依赖库。
 

4.2.springboot的classpath

  在springboot项目中,我们启动可执行jar时一般不指定classpath,那么springboot的classpath如何指定的呢?我们先来看一个典型的springboot项目打包结构: 理解classpath

   在META-INF中存在清单文件MANIFEST.MF,打开该文件查看里面的内容,我们找到main-class属性指定的并非是我们编写的应用启动类ServiceFeignApplication,而是org.springframework.boot.loader.JarLauncher。

理解classpath   在可执行jar包对应的目录中找到这个类,进行反编译。通过反编译代码我们大概可以推断出JarLauncher这个类添加了两个新的classpath,即“path/to/BOOT-INF/classes”与“/path/to/BOOT-INF/lib”,这两个目录类似于传统war包中的“WEB-INF/classes”与“WEB-INF/lib”。 理解classpath   我们的研究当然不止于此,继续深入的看一下springboot中的另外一个类“WebMVCAutoConfiguration”,其中的方法addResourcesHandlers调用了ResourcesProperties类的getLocations方法,在该方法中,我们发现了springboot定义了4个查找静态资源或者配置文件的默认路径,它们分别是:
  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/
  如果将classpath代入,便可知静态资源实际在jar中的查找位置了,这与springboot将静态资源“resources/static”目录的打包位置一致,下图为jar包中“BOOT-INF/classes”目录下的内容: 理解classpath

   

4.3.自定义打包的classpath

  之前做过一个项目,使用maven-assembly插件对springboot web项目进行定制打zip包,由于客户规定了打包目录结构,因此无法使用springboot默认的打包结构,包结构如下: 理解classpath   为了使得springboot能够正确查找到静态页面文件,选择的解决方式是,在启动脚本文件中,将“/path/to/WEB-INF”添加到classpath中,这样就满足了“classpath:/static/”的查找路径,同时为了保证conf与lib目录也能被查找,也将“/path/to/WEB-INF/conf”与“/path/to/WEB-INF/lib”添加到classpath中。    
上一篇:JAVA配置JDK环境变量


下一篇:Spring Boot静态资源的访问