Spring MVC进阶知识

1. Spring MVC配置

1.1 自定义DispatcherServlet配置

覆写AbstractAnnotationConfigDispatcherServletInitializer的customizeRegistration()方法

@Override
protected void customizeRegistration(Dynamic registration) {
  registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
}

1.2 添加servlet、filter

实现WebApplicationInitializer接口

注册一个servlet:

package com.myapp.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
import com.myapp.MyServlet;

public class MyServletInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
    myServlet.addMapping("/custom/**"); 
  }
}

注册一个filter:

public class MyServletInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    javax.servlet.FilterRegistration.Dynamic filter =
      servletContext.addFilter("myFilter", MyFilter.class);
    filter.addMappingForUrlPatterns(null, false, "/custom/*");
  }
}

WebApplicationInitializer适用于通用的添加servlet、filter、listener。

覆写AbstractAnnotationConfigDispatcherServletInitializer抽象类的getServletFilters()方法也可以添加filter:

@Override
protected Filter[] getServletFilters() {
    return new Filter[] { new MyFilter() };
}

1.3 在web.xml中声明DispatcherServlet

image.png
定义DispatcherServlet配置文件的位置

在web.xml中使用Java配置类:


image.png

image.png

2. 处理multipart form

在提交表单的时候,有时候会附带上传文件,文件会以二进制的形式提交。
multipart form回将提交的数据分成几个独立的部分,每个部分拥有自己的属性,自己的类型。

multipart request body样例:


multipart request body

2.1 配置multipart解析器

实现MultipartResolver。

Spring提供了两种MultipartResolver实现:

  1. CommonsMultipartResolver 使用Jakarta Commons FileUpload来解析multipart请求
  2. StandardServletMultipartResolver 依赖Servlet 3.0对于multipart请求的支持(since Spring 3.1)
使用Servlet3.0来解析multipart请求
@Bean
public MultipartResolver multipartResolver() throws IOException {
  return new StandardServletMultipartResolver();
}

配置一些对于文件的上传的属性:
(1) 如果是配置DispatcherServlet时实现了WebApplicationInitializer接口

DispatcherServlet ds = new DispatcherServlet();
Dynamic registration = context.addServlet("appServlet", ds);
registration.addMapping("/");
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));

(2) 如果在配置DispatcherServlet时继承了AbstractAnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer,那就覆写customizeRegistration()方法

@Override
protected void customizeRegistration(Dynamic registration) {
  registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
}

MultipartConfigElement的构造方法的参数作用:

. 设置文件的临时位置
. 限定上传文件的最大size(in bytes),默认是没有上限的
. 配置整个multipart请求的最大size(in bytes),默认是没有上限的
. 限定一个上传的文件不写入临时位置,而直接写入到磁盘的最大size(in bytes),这个值默认是0,也就是说默认情况下,所有的文件都会写到磁盘上

/**
* 单个文件不能超过2MB,整个请求不能超过4MB,所有文件都要直接写到磁盘上
*/
@Override
protected void customizeRegistration(Dynamic registration) {
  registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads", 2097152, 4194304, 0));
}

(3)如果在配置DispatcherServlet时使用了web.xml,就使用<multipart-config>元素就好了

<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>
配置JAKARTA COMMONS FILEUPLOAD 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.2 处理multipart请求

最常用的方法是在controller的方法参数加上注解@RequestPart

前端form加上enctype="multipart/form-data"属性:

<form method="POST" th:object="${spitter}" enctype="multipart/form-data">
  <label>Profile Picture</label>:
  <input type="file" name="profilePicture" accept="image/jpeg,image/png,image/gif" />
  <br/>
</form>
@RequestMapping(value="/register", method=POST)
public String processRegistration(@RequestPart("profilePicture") byte[] profilePicture, 
                                  @Valid Spitter spitter, Errors errors) {
  ... 
}
接收MULTIPARTFILE

Spring提供了MultipartFile用来对上传的文件封装,便于操作

@RequestMapping(value="/register", method=POST)
public String processRegistration(@RequestPart("profilePicture") MultipartFile profilePicture, 
                                  @Valid Spitter spitter, Errors errors) {
  ... 
}
package org.springframework.web.multipart;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

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;
}

transferTo方法可以将文件保存至指定位置

如果使用的Servlet3.0, 可以把上传的文件当做一个PART来解析
@RequestMapping(value="/register", method=POST)
public String processRegistration(
    @RequestPart("profilePicture") Part profilePicture,
    @Valid Spitter spitter,
    Errors errors) {
  ...
}
package javax.servlet.http;
import java.io.*;
import java.util.*;
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<String> getHeaders(String name);
  public Collection<String> getHeaderNames();
}

使用write方法将文件保存至指定位置

3. 异常处理

3.1 将异常映射为Http状态码

(1)Spring默认提供了一些状态码映射


image.png

(2) 使用@ResponseStatus注解
首先自定义异常,并使用@ResponseStatus注解

package spittr.web;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException {
}

然后跑出异常即可:


image.png
3.2 异常处理方法

使用@ExceptionHandler注解:

@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle() {
  return "error/duplicate";
}

这个类可能会抛出DuplicateSpittleException异常:

@RequestMapping(method=RequestMethod.POST)
public String saveSpittle(SpittleForm form, Model model) {
  spittleRepository.save(new Spittle(null, form.getMessage(), new Date(),
          form.getLongitude(), form.getLatitude()));
  return "redirect:/spittles";
}

4. 增强Controller

@ControllerAdvice注解
@ExceptionHandler注解
@InitBinder注解
@ModelAttribute注解

统一异常处理
package spitter.web;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class AppWideExceptionHandler {
  @ExceptionHandler(DuplicateSpittleException.class)
  public String duplicateSpittleHandler() {
    return "error/duplicate";
  }
}

5. redirect

controller返回页面时添加前缀redirect:

  return "redirect:/spitter/" + spitter.getUsername()
image.png
5.1 在redirect时,使用url来传递参数
@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}";
}
5.2 在redirect时,使用flash属性

在Spring中,flash属性被定义为:携带数据到下一次请求

Spring使用Model的子类RedirectAttributes的addFlashAttribute()方法来做这个

@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}";
}
image.png
@RequestMapping(value="/{username}", method=GET)
public String showSpitterProfile(@PathVariable String username, Model model) {
  if (!model.containsAttribute("spitter")) {
    model.addAttribute(spitterRepository.findByUsername(username));
  }
  return "profile";
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容