文件系统是我们开发过程中常常会接触的问题。那么在Spring Boot框架中,文件的访问又是什么样的呢?今天在此做一个总结。
1,file
和classpath
存放在电脑上实际位置的文件,在Spring Boot中用file:
开头表示。例如:
file:a.txt
当前目录下的a.txt文件。当前路径在开发环境下一般为Maven项目的目录下(与pom.xml同目录下),在打包为jar文件后当前路径即为运行jar文件时的运行路径。file:D:\a.txt
表示绝对路径,在此不多赘述。
而在jar文件内部中,我们一般把文件路径称为classpath
,所以读取内部的文件就是从classpath内读取,classpath指定的文件不能解析成File对象,但是可以解析成InputStream
。例如:
classpath:/a.txt
jar包根目录下的a.txt。classpath
以/
开头表示绝对路径,即为jar包根目录。
2,Spring Boot的静态资源访问
我们都知道Spring Boot工程文件夹中的src/main/resources
是用于存放资源的地方。默认时Spring Boot打包之后静态资源位置如下:
classpath:/static
classpath:/public
classpath:/resources
classpath:/META-INF/resources
在Spring Boot中classpath的根目录就对应工程文件夹下的src/main/resources
。
可以先看这个例子:
在工程文件夹下src/main/resources/static
下放入图片qiqi.png
:
运行,访问127.0.0.1:8080/qiqi.png
,效果如下:
这个例子可见外部访问的资源路径和Spring Boot工程中资源文件路径的一一对应关系。即外部访问时的“根目录”即对应着上述的四个静态资源位置(classpath)。
还可以新建一个Controller类,写如下方法:
"/pic") (publicStringshowPic() { return"/qiqi.png"; }
运行,访问127.0.0.1:8080/pic
,效果同上。
在这个Controller方法中,上面@GetMapping
是路由路径,return
的是对应的资源路径。
其实这个默认的资源路径是可以修改的。
我们需要知道在配置文件application.properties
中可以加入下列两个配置项:
spring.mvc.static-path-pattern spring.web.resources.static-locations
我们来逐一进行讲解。
(1) spring.mvc.static-path-pattern
- 指定资源访问路径
这个spring.mvc.static-path-pattern
代表的是应该以什么样的路径来访问静态资源,也就是只有静态资源满足什么样的匹配条件,Spring Boot才会处理静态资源请求。说白了就是资源的外部访问路径。根据上述例子我们知道了这个配置默认值为/**
。假设在上述工程配置文件中加入:
spring.mvc.static-path-pattern=/resources/**
那么再访问我们那个图片就要访问网址:127.0.0.1:8080/resources/qiqi.png
好了,我们如果现在想使用Controller
类的@GetMapping
进行路由的话,如果还是像上面那么写:
"/pic") (publicStringshowPic() { return"/qiqi.png"; }
访问127.0.0.1:8080/pic
,你会发现:
为什么这时就不行了呢?
这是因为我们改变了spring.mvc.static-path-pattern
配置的值,那么我们对应的Controller类方法中的返回值,也要对应改变。
之前spring.mvc.static-path-pattern
没有配置那默认就是/**
,那访问/qiqi.png
就可以找到图片。现在这个配置改为/resources/**
,那很显然要访问/resources/qiqi.png
了,再访问/qiqi.png
当然访问不到了!因此对应的Controller类方法中也要做出对应修改。
上述配置spring.mvc.static-path-pattern
为/resources/**
,那么我们修改Controller方法如下:
"/pic") (publicStringshowPic() { return"/resources/qiqi.png"; }
可见返回值改成了/resources/qiqi.png
。
可见,Controller类中的方法的返回值,并非是资源文件的实际的相对路径,而是对应的资源的外部访问路径。这一点也是我和我身边许多朋友容易混淆的一点。
(2) spring.web.resources.static-locations
- 指定静态资源查找路径
再者,spring.web.resources.static-locations
用于指定静态资源文件的查找路径,查找文件是会依赖于配置的先后顺序依次进行。根据上述例子可见这个值默认是:
classpath:/static,classpath:/public,classpath:/resources,classpath:/META-INF/resources
其实上面提到了Spring Boot默认资源文件位置,实质上就是这个配置的值。
假设在上述工程中配置文件写入:
spring.web.resources.static-locations=classpath:/myRes
那么我们要将qiqi.png
放在项目文件夹的src\main\resources\myRes
文件夹中,才能访问:
配置此项后,默认值将失效!
还可以使用磁盘路径例如:
spring.web.resources.static-locations=file:res
即指定资源文件在项目文件夹中的res
目录中(即打包后运行jar文件时的运行路径下的res
文件夹中)。也可以使用绝对路径。
通俗地讲,spring.mvc.static-path-pattern
配置指定了我们外部访问的路径,而访问这个外部路径时就会去spring.web.resources.static-locations
配置的路径中找对应的资源。
(3) 集成Spring Security之后导致上述配置失效
今天在维护一个项目的时候发现:即使是正确配置上述的静态资源配置,访问静态资源时一直报404
,我也很纳闷:之前好好的啊!怎么就不行了呢?经查阅各种资料发现:若配置了拦截器,则会导致上述配置失效。这个项目使用了Spring Security,可能是因为其中的拦截器配置导致这个资源配置失效了。
之前一个项目配置了Swagger之后也出现了这个配置失效的问题,我想应该是同一个原因导致。
经参考官方文档之后,发现还有一个方式可以配置静态资源访问路径和对应位置。我们新建一个配置类,重写WebMvcConfigurer
中的addResourceHandlers
方法即可。
因此,在一些外部依赖自带拦截器的情况下,就很有可能覆盖我们上述资源路径配置,导致我们上述资源配置失效。因此这个时候,我们就不能通过写上述配置文件的方式配置静态资源访问了!就要通过写配置类的方式。
我们先来看一个例子,我这里项目目录结构如下:
然后我们新建一个软件包config
,在里面写配置类如下:
packagecom.example.resourcetest.config; importorg.springframework.context.annotation.Configuration; importorg.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer; /*** 自定义MVC配置器*/publicclassMyWebMvcConfigimplementsWebMvcConfigurer { /*** 重写资源路径配置*/publicvoidaddResourceHandlers(ResourceHandlerRegistryregistry) { registry.addResourceHandler("/image/**").addResourceLocations("file:res/image/"); } }
可见我们只需要调用addResourceHandlers
方法参数registry
的方法也可以实现资源路径配置,配置效果和上面是一样的。
其中两个方法的意义如下:
addResourceHandler
等同于上述配置spring.mvc.static-path-pattern
,代表的是应该以什么样的路径来访问静态资源addResourceLocations
等同于上述配置spring.web.resources.static-locations
,用于指定静态资源文件的查找路径
同样地,配置此项后,默认的资源搜索路径将失效!
可见我们要先调用addResourceHandler
再调用addResourceLocations
,两者是一一对应的,上述代码意思就是:外部访问路径/image/xxx
时,就会去当前路径下res/image/
目录下找xxx
。
现在,访问127.0.0.1:8080/image/gz-12.png
,可见访问成功:
可见通过配置类的方式,我们仍然可以实现上述在配置文件中实现的效果。不过这里需要注意的是,和配置文件中不同,指定静态文件查找路径时,若路径是个目录则必须以/
结尾!否则也会出现404
的情况。
当然,在addResourceHandler
和addResourceLocations
方法中都可以添加多个路径,例如:
registry.addResourceHandler("/image/**").addResourceLocations("file:res/image/", "classpath:/static/");
也就是说外部访问/image/xxx
时,会去当前目录下res/image/
和类路径/static/
中去寻找xxx
,上面也讲了类路径classpath
了,这里对应的也是一样的。
还可以这样:
registry.addResourceHandler("/image/**", "/img/**").addResourceLocations("file:res/image/");
也就是写了多个外部访问路径,表示访问/image/xxx
和/img/xxx
时,都会到当前路径下的res/image/
下去查找xxx
。
当然,事实上配置类配置的方式也会更加高级,上述配置文件中我们只能配置一个外部访问路径,对应其它多个实际资源查找路径。而在配置类中,我们可以定义多个外部访问路径,对应不同的资源查找路径,例如我将代码改如下:
packagecom.example.resourcetest.config; importorg.springframework.context.annotation.Configuration; importorg.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer; /*** 自定义MVC配置器*/publicclassMyWebMvcConfigimplementsWebMvcConfigurer { /*** 重写资源路径配置*/publicvoidaddResourceHandlers(ResourceHandlerRegistryregistry) { // 访问/image/xxx对应去res/image查找资源registry.addResourceHandler("/image/**").addResourceLocations("file:res/image/"); // 访问/web/xxx对应去res/web下查找资源registry.addResourceHandler("/web/**").addResourceLocations("file:res/web/"); } }
这时,我访问http://127.0.0.1:8080/image/gz-12.png
可以访问到res/image
中的图片,然后访问http://127.0.0.1:8080/web/test.html
可以访问到res/web
中的网页。
可见,配置类提供了更加灵活的配置方式,还能够解决我们配置被其它依赖覆盖的问题。
需要注意的是,通常一个项目只能有一个类实现WebMvcConfigurer
,否则会造成覆盖产生问题。除了这里配置静态资源之外,之前做用户登录的拦截器也要实现这个接口中的方法,这些方法是可以写在一个配置类中的,毕竟实现的是一个接口。
然后同样地,如果是要自定义Controller
路由资源,也要和上述配置文件中的一样注意return
的访问路径问题。例如我要自定义res/image
中图片访问路径,由于配置了addResourceHandler("/image/**")
,那对应的Controller
方法如下:
"/rabbit-halloween") (publicStringimage() { return"/image/gz-12.png"; }
然后访问http://127.0.0.1:8080/rabbit-halloween
也可以访问到图片。
所以说如果发现配置文件配置资源路径不起作用,我们就可以删掉配置文件中的相关配置,通过编写配置类的方式来实现资源路径自定义,包括更加灵活的情况下例如需要多个访问路径对应各自不同的资源文件查找路径,也需要用到配置类方式。
3,Spring Boot的配置文件位置指定
我们也知道Spring Boot的配置文件默认是位于classpath:/application.properties
,默认会被打包进jar文件。
其实我们也可以修改这个配置文件的位置。
在我们的Spring主类上加入如下注解:
value={"自定义配置文件路径"}) (
value表示配置文件位置,也可以填多个:
value={"配置1路径", "配置2路径"}) (
此处以我的主类全部代码为例:
packagecom.example.demo; importorg.springframework.boot.SpringApplication; importorg.springframework.boot.autoconfigure.SpringBootApplication; importorg.springframework.context.annotation.PropertySource; value={"file:self.properties"}) (publicclassDemoApplication { publicstaticvoidmain(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
即定义配置文件为项目文件夹下的self.properties
文件(打包后运行jar文件时的运行目录下的self.properties
文件)。
4,多环境的配置文件外置方案
我们知道,默认情况下,我们可以在src/main/resources
文件夹下创建多个配置文件以对应多个不同环境下的配置,方便灵活切换,例如开发-生产环境下,我们一共有三个配置文件:
application.properties application-dev.properties application-prod.properties
然后在主配置文件application.properties
里面配置一个配置项,即可一键切换配置环境:
# 指定当前使用开发环境配置文件 spring.profiles.active=dev
我们默认的配置文件名是application
,因此多环境的情况下,配置文件命名如下:
application-环境配置名.properties
主配置文件就是application.properties
,在里面配置:
spring.profiles.active=环境配置名
运行时即可使用指定环境的配置文件。
这个时候想配置文件外置,如果按照上述第3部分的方法来,发现就不行了。那么多环境配置的情况下,配置文件如何外置呢?
我们需要先知道,其实Spring Boot会默认在这四个位置扫描配置文件:
file:./config/ file:./ classpath:/config/ classpath:/
我们可以指定spring.config.location
属性,来实现自定义Spring Boot的配置文件扫描路径。spring.config.location
属性不仅可以设定扫描指定的配置文件,还可以指定扫描指定文件夹。
在我们的main
方法中最开头,使用System.setProperty
方法即可设定,下面给几个例子:
// 扫描项目文件夹(jar运行目录)中Resources/config目录中所有的配置文件System.setProperty("spring.config.location", "file:Resources/config/"); // 扫描项目文件夹(jar运行目录)中Resources/config/app.properties文件System.setProperty("spring.config.location", "file:Resources/config/app.properties"); // 扫描项目文件夹(jar运行目录)中Resources/config目录和Resources/config2目录中所有的配置文件System.setProperty("spring.config.location", "file:Resources/config/, file:Resources/config2/");
可见spring.config.location
属性比较灵活,既可以设定文件还可以指定文件夹,注意指定的如果是文件夹,路径最后一定要以/
结尾。指定多个文件或者文件夹时路径中间以英文逗号分隔。
好了,知道了spring.config.location
属性,我们就知道多环境配置文件外置的方法了。例如我想把所有配置文件application.properties
、application-dev.properties
和application-prod.properties
放到项目文件夹下的Resources/config
目录下,那么我的完整主类代码如下:
packagecom.gitee.swsk33.test; importorg.springframework.boot.SpringApplication; importorg.springframework.boot.autoconfigure.SpringBootApplication; publicclassTestApplication { publicstaticvoidmain(String[] args) { // 设定配置文件扫描目录System.setProperty("spring.config.location", "file:Resources/config/"); SpringApplication.run(TestApplication.class, args); } }
可见很简单,只在主方法main
中最头部写了System.setProperty("spring.config.location", "file:Resources/config/");
这一行代码即可完成配置。
然后我的三个配置文件放在项目文件夹下的Resources/config
目录下:
再在主配置文件里面配置:
spring.profiles.active=dev
运行,即可使用开发环境的配置文件application-dev.properties
了。
5,配置文件改名
我们也知道,Spring Boot配置文件默认名字是application.properties
,Spring Boot默认情况下也是通过搜寻这个名字的文件找到配置文件的。
如果说想改配置文件的名字怎么做呢?其实除了上述直接指定配置文件路径以外,还可以修改属性spring.config.name
来实现,也是使用System.setProperty
方法来修改。例如在main
方法最前面写上:
System.setProperty("spring.config.name", "config");
那么Spring Boot就会去搜寻名为config.properties
的文件作为配置文件。
因此可见spring.config.name
的默认值为application
,这个值不需要写扩展名,扩展名会在Spring Boot中自动适配。
上面修改了spring.config.name
属性为config
,那么如果说是多环境配置,我们的其余环境的配置文件也要跟着改为如下:
config-dev.propertiesconfig-prod.properties
6,总结
Spring Boot的资源文件访问和我们普通Java程序可能有所不同,大家一定要注意资源文件配置,以及配置文件的加载。
本文参考的官方文档: