一、Spring MVC 配置的替代方案
在之前我们通过扩展AbstractAnnotationConfigDispatcherServletInitializer
快速搭建了Spring MVC
环境,这个便利的基础类会帮我们创建需要的DispatcherServlet和ContextLoaderListener
对象。但是在实际应用中,除了DispatcherServlet
之外,可能还需要额外的Servlet
和Filter
;或者还需要对DispatcherServlet
本身做一些额外的配置;又或者需要将应用部署到Servlet3.0
之前的容器中,此时则需要将DispatcherServlet
配置到传统的web.xml
中。
1.1 自定义 DispatcherServlet配置
在之前使用Java
配置方式中,我们编写SpittrWebApplicationInitializer
配置类继承AbstractAnnotationConfigDispatcherServletInitializer
,对其中的三个方法进行了重载,但是这三个方法只是必须要重载的抽象方法,实际上还有更多方法可以重载。比如customizeRegistration()
方法,在AbstractAnnotationConfigDispatcherServletInitializer
将DispatcherServlet
注册到Servlet
容器之后,就会调用此方法,并将Servlet
注册后得到的Registration.Dynamic
对象传递进来,我们可以通过此对象对DispatcherServlet
进行一些额外的配置。
1.2 添加其他的 Servlet和 Filter
如果应用中除了DispatcherServlet
这个Servlet
需要注册外,还有其他的Servlet
和Filter
的话,怎样将其注册到Servlet
容器中?
基于Java
的初始化器的一个好处就在于我们可以定义任意数量的初始化器类。因此,如果想往Web
容器中注册其他组件的话,只需创建一个新的初始化器就可以了。最简单的方式就是实现WebApplicationInitializer
接口(其中AbstractAnnotationConfigDispatcherServletInitializer
就继承了此类。)
public class MyServletInitializer implements WebApplicationInitializer{
public void onStartup(ServletContext servletContext) throws ServletException{
javax.servlet.ServletRegistration.Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
myServlet.addMapping("/custom/**");
}
}
说明:这个类中MyServlet
类就是我们自定义的Servlet
类,同时配置了其映射路径,当然我们也可以这样手动配置DispatcherServlet
类,但是没有必要。同理,还可以使用这种方式添加其他的Filter
和Listener
类:
public void onStartup(ServletContext servletContext) throws ServletException{
javax.servlet.FilterRegistration.Dynamic myFilter = servletContext.addFilter("myFilter", MyFilter.class);
myFilter.addMappingForUrlPatterns(null, false, "/custom/**");
}
说明:这里的Dynamic
类和之前的不一样。如果要将应用部署到支持Servlet3.0
的容器中,那么这是一种通用的方式。而如果只是注册Filter
,并且该Filter
只会映射到DispatcherServlet
上的话,那么AbstractAnnotationConfigDispatcherServletInitializer
中还有一种快捷方式。只需要重载其getServletFilters()
(其实是其父类中的方法)方法。
@Override
protected Filter[] getServletFilters(){
return new Filter[] { new MyFilter() };
}
说明:这个方法返回一个javax.servlet.Filter
的数组,在这里没有必要声明它的映射路径,此方法返回的所有Filter
都会映射到DispatcherServlet
上。
以上介绍的方式都是将应用部署到Servlet3.0
容器中的情况,这种方式下不用创建web.xml
文件。但是如果要部署到Servlet3.0
之前的容器中,则需要配置web.xml
文件了。
1.3 在web.xml 中声明 DispatcherServlet
这种方式配置下,我们需要自己来注册DispatcherServlet
和ContextLoaderListener
。如下:
contextConfigLocation
/WEB-INF/spring/root-context.xml
org.springframework.web.context.ContextLoaderListener
appServlet
org.springframework.web.servlet.DispatcherServlet
1
appServlet
/
说明:上下文contextConfigLoaction
中指定了初始化文件地址,这个文件定义了根应用上下文,会被ContextLoaderListener
加载。而DispatcherServlet
会根据Servlet
的名字找到一个文件,并基于该文件加载应用上下文。这里Servlet
名字是appServlet
,因此对应的文件为“/WEB-INF/appServlet-context.xml”
,当然我们也可以显示指定其路径:
appServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/appServlet-context.xml
1
appServlet
/
说明:以上的配置中DispatcherServlet
和ContextLoaderListener
要加载的配置文件都是XML
文件,我们也可以使用Java
配置。
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
spittr.config.RootConfig
org.springframework.web.context.ContextLoaderListener
appServlet
org.springframework.web.servlet.DispatcherServlet
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
spittr.config.WebConfig
1
appServlet
/
说明:这里的两个配置类在之前的文章中已经给出,这里只需要配置使用Java
配置,在初始化的时候就会从带有@Configuration
注解的类上加载配置,所以RootConfig.java
和WebConfig.java
中都需要使用这个注解。
二、处理 multipart 形式的数据
一般表单提交所形成的请求结果是很简单的,如GET
方式中,就是以多个“&”
符号分割多个name-value
对。但是对于传送二进制文件,如图片上传,就不行了。而multipart
格式的数据会将一个表单拆分为多个部分(part
),每个部分对应一个输入域。在一般地表单输入域中,它所对应的部分中会放置文本型数据,但是如果是上传文件的话,它所对应的可以是二进制,如下:
------WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="firstName"
Charles
------WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="lastName"
Xavier
------WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="email"
[email protected]
------WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="username"
professorx
------WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="password"
letmein01
------WebKitFormBoundaryqgkaBn8IHJCuNmiW
Content-Disposition: form-data; name="profilePicture" filename="me.jpg"
Content-Type: image/jpeg
[[ Binary image data goes here]]
------WebKitFormBoundaryqgkaBn8IHJCuNmiW------
说明:这个multipart
请求中,可以看到profilePicture
部分与其他部分明显不同,指明自己的Content-Type
头为一个JPEG
的图片。此时请求的就是二进制数据了。虽然看起来很复杂,但是在Spring MVC
中处理却很简单,只需要配置一个multipart
解析器即可,通过它来告诉DispatcherServlet
该如何读取这种请求。
2.1 配置multipart 解析器
DispatcherServlet
并没有实现任何解析multipart
请求数据的功能,而是将其委托给了Spring
中的MultipartResolver
策略接口的实例。从Spring3.1
开始,Spring
内置了两个MultipartResolver
实现类供我们选择:
-
CommonsMultipartResolver
:使用Jakarta Commons FileUpload
(即Apache
的一个项目)解析multipart
请求。 -
StandardServletMultipartResolver
:依赖于Servlet3.0
对multipart
请求的支持(始于Spring3.1
)。
一般来讲,后者可能会是优选方案,但是要注意各自使用的范围。
2.1.1 使用 Servlet3.0 解析 multipart 请求
兼容Servlet3.0
的StandardServletMultipartResolver
没有构造器参数,也没有要设置的属性。这样,在Spring
应用上下文中,将其声明为bean
就简单了。
@Bean
public MultipartResolver multipartResolver() throws IOException{
return new StandardMultipartResolver();
}
说明:将其配置为bean
很简单,那如何限制上传文件的大小,如何指定文件上传时临时写入的目录?其实是有办法配置相关限制条件的,只是不是在Spring
中配置,而是要在Servlet
中指定multipart
的配置。这里必须要指定临时写入的目录,否则无法正常工作。具体来讲,必须要在web.xml
或Servlet
初始化类中,将multipart
的具体限制条件作为DispatcherServlet
配置的一部分。之前讲过,可以对DispatcherServlet
进行一些额外的配置,所以这里可以这样配置:
DispatcherServlet ds = new DispatcherServlet();
javax.servlet.ServletRegistration.Dynamic registration = servletContext.addServlet("appServlet", ds);
registration.addMapping("/");
registration.setMultipartConfigElement(new MultipartConfigElement("/tmp/spittr/uploads"));
说明:这里将临时文件设置为了"/tmp/spittr/uploads"
,这是一种很基本的方式,如果我们配置DispatcherServlet
的自定义Servlet
继承了AbstractAnnotationConfigDispatcherServletInitializer
或其直接父类的话,可以直接重载customizeRegistration()
方法进行配置:
@Override
protected void customizeRegistration(Dynamic registration){
registration.setMultipartConfigElement(new MultipartConfigElement("/tmp/spittr/uploads"));
}
但目前为止,使用的是只有一个参数的MultipartConfigElement
构造器,这个参数指定的是文件系统中的一个绝对目录,上传文件将会写入临时写入该目录中。但是还可以通过其他构造器来限制上传文件的大小。其他参数有:
- 上传文件的最大容量(即每个文件的大小最大值,以字节为单位)。默认是没有限制的。
- 整个
multipart
请求的最大容量(字节),不会关心有多少个part
以及每个part
的大小。默认是没有限制的。 - 在上传的过程中,如果文件大小达到了一个指定最大容量(字节),将会写入到临时文件路径中。默认值为零,也就是所有上传文件都会写入到磁盘。是不是可以这样理解,默认就是直接写入到磁盘,而不是使用内存缓存一定大小之后再写入到磁盘?
例如,我们想限制文件的大小不超过2MB
,整个请求不超过4MB
,而且所有的文件都要写到磁盘中,配置如下:
@Override
protected void customizeRegistration(Dynamic registration){
registration.setMultipartConfigElement(new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0));
}
当然也可以在web.xml
中配置:
appServlet
org.springframework.web.servlet.DispatcherServlet
1
/tmp/spittr/uploads
2097152
4194304
注意:必须要配置路径。
2.1.2 配置Jakarta Commons FileUpload multipart 解析器
如果我们需要将应用部署到Servlet3.0
之前的容器中,则可以使用Jakarta Commons FileUpload multipart
来替代,当然也可以自己编写MultipartResolver
实现,但是没哟必要。Spring
内置了CommonsMultipartResolver
,可以作为StandardServletMultipartResolver
的替代方案。
将CommonsMultipartResolver
声明为Spring bean
的最简单方式如下:
@Bean
public MulitipartResolver multipartResolver(){
return new CommonsMultipartResolver();
}
说明:与StandardServletMultipartResolver
不同,CommonsMultipartResolver
不会强制要求设置临时文件路径。默认情况下,这个路径就是Servlet
容器的临时目录。当然也可以手动指定目录。
@Bean
public MulitipartResolver multipartResolver() throws IOException{
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver .setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));
return multipartResolver ;
}
说明:实际上,还可以设置更多细节,如文件大小之类的。
@Bean
public MulitipartResolver multipartResolver() throws IOException{
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));
multipartResolver.setMaxUploadSize(2097152);
multipartResolver.setMaxInMemorySize(0);
return multipartResolver ;
}
说明:这里将最大的文件大小为2MB
,最大内存大小为0
,分别对应MultipartConfigElement
的第二个和第四个构造器参数。无法设定总共上传文件大小之和。