一、DispatcherServlet个性化配置
继承AbstractAnnotationConfigDispatcherServletInitializer时,同时还有许多其他方法可以重写从而可以实现更多的配置。
例如customizeRegistration()方法,在注册了DispatcherServlet之后,就会调用customizeRegistration()方法,并根据servlet的注册返回值传送ServletRegistration.Dynamic,通过对customizeRegistration()的重写,就可以对DispatcherServlet进行额外的配置。其中ServletRegistration.Dynamic作为入参,你可以做很多事情,比如调用setLoadOnStartup()来设置加载时优先级,调用setInitParameter()来设置初始化参数,调用setMultipartConfig()来设置Servlet3.0的多路支持
/**
* Spring MVC如何处理多个请求和文件上传
* 设置了多路支持的上传文件临时存储路径为:/tmp/spittr/uploads。
*/
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(
new MultipartConfigElement("/tmp/spittr/uploads"));
}
二、添加额外的Servlet和Filter
1.注册额外的Servlet、Filter或者Listener
(1)最简单的方法就是实现Spring的WebApplicationInitializer接口。
例如,下面的代码展示了如何通过实现WebApplicationInitializer接口的方式来注册一个Servlet:
public class MyServletInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 定义Servlet
Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
// 映射Servlet
myServlet.addMapping("/custom/**");
}
}
(2)使用AbstractAnnotationConfigDispatcherServletInitializer注册Filter并将其映射到DispatcherServlet
@Override
protected Filter[] getServletFilters() {
return new Filter[] { new MyFilter() };
}
三、使用web.xml声明DispatcherServlet
1.典型的web.xml文件,其中对DispatcherServlet和ContextLoaderListener进行了声明:
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/spring/root-context.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>appServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>appServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
2.通过设置contextConfigLocation初始化参数的方式指定DispatcherServlet配置文件的位置,例如,下面的DispatcherServlet配置就会从/WEB-INF/spring/appServlet/servlet-context.xml文件中加载:
<servlet>
<servlet-name>appServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>
/WEB-INF/spring/appServlet/servlet-context.xml
param-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
3.基于Java的配置方式,通过设置DispatcherServlet的contextClass参数和初始化参数来实现
为了使用基于Java的配置,需要通知DispatcherServlet和ContextLoaderListener去使用AnnotationConfigWebApplicationContext,该类是WebApplicationContext接口的实现类,它可以对Java配置类进行加载。
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextClassparam-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
param-value>
context-param>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>spittr.config.RootConfigparam-value>
context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
listener-class>
listener>
<servlet>
<servlet-name>appServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextClassparam-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
param-value>
init-param>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>
spittr.config.WebConfigConfig
param-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>appServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
四、处理multipart表单数据
1.配置multipart解析器
从Spring3.1开始,Spring提供了两种MultipartResolver实现类供选择:
CommonsMultipartResolver:使用Jakarta Commons FileUpload来解析multipart请求;
StandardServletMultipartResolver:依靠Servlet 3.0支持来解析(Spring 3.1及以上,第一选择);
设置StandardServletMultipartResolver的,但是它的设置不是在Spring配置中进行的,而是在Servlet配置中。起码要配置一下存放临时文件的位置,进一步来讲,还要将multipart配置为DispatcherServlet的一部分。
(1)继承自WebMvcConfigurerAdapter的servlet初始化类中配置的DispatcherServlet,那么就可以在servlet注册时通过调用setMultipartConfig()方法来配置multipart详情。比如:
DispatcherServlet ds = new DispatcherServlet();
Dynamic registration = context.addServlet("appServlet", ds);
registration.addMapping("/");
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
(2)继承自AbstractAnnotationConfigDispatcherServletInitializer或者AbstractDispatcherServletInitializer的servlet初始化类进行的配置,没有创建DispatcherServlet的实例或者使用servlet上下文对其进行注册。因此就没有直接的引用供Dynamicservlet注册来使用。重写customizeRegistration()方法来进行配置:
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
}
(3)MultipartConfigElement的唯一参数设置了上传文件时临时文件的存放位置。也可以进行其他一些设置:
文件上传的最大值(byte),默认没有限制;
所有multipart请求的文件最大值(byte),不管有多少个请求,默认无限制;
直接上传文件(不需存储到临时目录)的最大值(byte),默认是0,也就是所有的文件都要写入硬盘;
例如,你想设置文件大小不超过2MB,所有请求的总和不超过4MB,并且所有文件都要写入硬盘,那么就可以这样设置:
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0));
}
(4)传统的web.xml的方式来设置的DispatcherServlet,那么就需要使用多个元素,其默认值和MultipartConfigElement相同,并且是必填项:
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<multipart-config>
<location>/tmp/spittr/uploads</location>
<max-file-size>2097152</max-file-size>
<max-request-size>4194304</max-request-size>
</multipart-config>
</servlet>
(5)配置Jakarta Commons FileUpload解析器CommonsMultipartResolver
这里设置了文件的最大大小为2MB,最大的内存中大小为0,即每个上传文件都会直接写入磁盘的。但是它是无法设置multipart请求总的文件大小的。
@Bean
public MultipartResolver multipartResolver() throws IOException {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));
multipartResolver.setMaxUploadSize(2097152);
multipartResolver.setMaxInMemorySize(0);
return multipartResolver;
}
2.处理multipart请求
页面
<form method="POST" th:object="${spitter}" *enctype="multipart/form-data"*>
<label>Profile Picturelabel>:
<input type="file" name="profilePicture" accept="image/jpeg,image/png,image/gif" /><br/>
<input type="submit" value="Register" />
form>
(1) @RequestPart注解一个控制器参数获取到上传的文件
/**
* 当注册表单提交时,请求部分的数据就会赋予到profilePicture属性中,
* 如果用户没有选中一个文件,那么该数组就会是一个空值(不是null)
*/
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(@RequestPart("profilePicture") byte[] profilePicture, @Valid Spitter spitter, Errors errors) {
}
(2)使用MultipartFile接口保存上传文件
MultipartFile提供获取上传文件的方法,同时提供了很多其他方法,比如原始文件名称、大小和内容类型等。另外还提供了一个InputStream可以将文件数据作为数据流读取。
另外,MultipartFile还提供了一个方便的transferTo()方法帮助你将上传文件写入到文件系统
public interface MultipartFile {
String getName();
String getOriginalFilename();
String getContentType();
boolean isEmpty();
long getSize();
byte[] getBytes() throws IOException;
InputStream getInputStream() throws IOException;
void transferTo(File dest) throws IOException;
}
(3)将文件保存到Amazon S3管理文件
下面的代码可以将上传的图像保存到Amazon S3:
private void saveImage(MultipartFile image) throws ImageUploadException {
try {
//设置Amazon Web Service (AWS)认证,你需要提供S3的密钥和私钥,这些在注册S3服务时Amazon都会给你的
AWSCredentials awsCredentials = new AWSCredentials(s3AccessKey, s2SecretKey);
// 配置S3服务
S3Service s3 = new RestS3Service(awsCredentials);
// 创建S3 bucket对象
S3Bucket bucket = s3.getBucket("spittrImages");
S3Object imageObject = new S3Object(image.getOriginalFilename());
// 设置图像数据
imageObject.setDataInputStream(image.getInputStream());
imageObject.setContentLength(image.getSize());
imageObject.setContentType(image.getContentType());
AccessControlList acl = new AccessControlList();
// 设置权限
acl.setOwner(bucket.getOwner());
acl.grantPermission(GroupGrantee.ALL_USERS, Permission.PERMISSION_READ);
imageObject.setAcl(acl);
// 保存图片
s3.putObject(bucket, imageObject);
} catch (Exception e) {
throw new ImageUploadException("Unable to save image", e);
}
(4)在Servlet 3.0的容器上,Spring MVC也可以将javax.servlet.http.Part作为控制器的入参
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(@RequestPart("profilePicture") Part profilePicture, @Valid Spitter spitter,Errors errors) {
}
Part接口
public interface Part {
public InputStream getInputStream() throws IOException;
public String getContentType();
public String getName();
public String getSubmittedFileName();
public long getSize();
public void write(String fileName) throws IOException;
public void delete() throws IOException;
public String getHeader(String name);
public Collection getHeaders(String name);
public Collection getHeaderNames();
}
五、异常处理
(1)@ResponseStatus来将其映射到404。
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found")
public class SpittleNotFoundException extends Exception {
}
(2)编写异常处理方法
@ExceptionHandler注解的方法在同一个控制器里是通用的,即无论SpittleController的哪一个方法抛出DuplicateSpittleException异常,handleDuplicateSpittle()方法都可以对其进行处理,而不再需要在每一个出现异常的地方进行捕获
@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle() {
return "error/duplicate";
}
(3)@ControllerAdvice控制器增强类
使用@ControllerAdvice控制器增强类,不论哪一个controller抛出DuplicateSpittleException,都会调用handleDuplicateSpittle()方法来处理。
// 声明控制器增强
@ControllerAdvice
public class AppWideExceptionHandler {
// 定义异常处理方法
@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle() {
return "error/duplicate";
}
@ExceptionHandler(SpittleNotFoundException.class)
public String handleSpittleNotFound() {
return "error/duplicate";
}
}
六、在redirect请求中携带数据进行传递
跳转的路径:
return “redirect:/spitter/” + spitter.getUsername();
两种方法用来从重定向的方法中获取数据:
(1)将数据转换为路径参数或者查询参数
使用URL模版重定向:使用路径参数和查询参数传递数据比较简单,它只适用于传递简单值,比如String和数字,不能传递比较复杂的东西。
由于model中的spitterId属性并没有映射到URL中的占位符,它会自动作为查询参数。
如果username是habuma,spitterId是42,那么返回的重定向路径将是/spitter/habuma?spitterId=42。
@RequestMapping(value="/register", method=POST)
public String processRegistration(Spitter spitter, Model model) {
spitterRepository.save(spitter);
model.addAttribute("username", spitter.getUsername());
model.addAttribute("spitterId", spitter.getId());
return "redirect:/spitter/{username}";
}
(2)在flash属性中发送数据
使用flash属性
在重定向中传送一个Spitter对象。
在重定向之前,所有的flash属性都会拷贝到session中,在重定向之后,存储在session中的flash属性会从session中移出到model中。然后处理重定向请求的方法就可以使用Spitter对象了
@RequestMapping(value="/register", method=POST)
public String processRegistration(Spitter spitter, RedirectAttributes model) {
spitterRepository.save(spitter);
model.addAttribute("username", spitter.getUsername());
model.addFlashAttribute("spitter", spitter);
return "redirect:/spitter/{username}";
}