一、Spring MVC概述
1.1 什么是Spring MVC?
Spring MVC是Spring框架提供的Web层解决方案,基于经典的MVC(Model-View-Controller)设计模式。它通过将业务逻辑、数据和界面显示分离,使Web应用的开发更加清晰和模块化。
1.2 Spring MVC核心组件
┌─────────────────────────────────────────────────────────────────┐
│ Spring MVC 核心架构 │
└─────────────────────────────────────────────────────────────────┘
HTTP请求
│
▼
┌─────────────────────┐
│ DispatcherServlet │ ← 前端控制器(核心)
│ (核心调度器) │
└──────────┬──────────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│HandlerMap │ │ Handler │ │ ViewResolver│
│ ping │ │ Adapter │ │ (视图解析器)│
│(处理器映射)│ │(处理器适配器)│ └───────────┘
└─────┬─────┘ └─────┬─────┘
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│Controller │ │ Model │
│(控制器) │ │ (模型数据) │
└───────────┘ └───────────┘
│
▼
┌───────────┐
│ View │
│ (视图) │
└───────────┘
│
▼
HTTP响应
1.3 核心组件职责
| 组件 | 职责 |
|---|---|
DispatcherServlet | 前端控制器,接收请求并分发到相应的处理器 |
HandlerMapping | 处理器映射器,根据请求URL找到对应的Controller |
HandlerAdapter | 处理器适配器,执行Controller方法 |
Controller | 控制器,处理业务逻辑 |
ModelAndView | 封装模型数据和视图信息 |
ViewResolver | 视图解析器,解析视图名称到具体视图 |
View | 视图,渲染响应结果 |
1.4 请求处理流程
二、自动配置原理
2.1 WebMvcAutoConfiguration
Spring Boot通过WebMvcAutoConfiguration自动配置Spring MVC:
@AutoConfiguration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebMvcAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class,
WebProperties.class, HttpProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
implements WebMvcConfigurer, ResourceLoaderAware {
// 自动配置内容...
}
}
2.2 自动配置内容
| 配置项 | 默认值 |
|---|---|
| 静态资源路径 | /static, /public, /resources, /META-INF/resources |
| 视图解析器 | ContentNegotiatingViewResolver |
| 消息转换器 | Jackson(JSON)、StringHttpMessageConverter等 |
| 格式化器 | NumberFormat、DateFormat |
| 静态首页 | index.html |
| favicon | favicon.ico |
2.3 配置属性类
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
private String staticPathPattern = "/**";
private boolean dispatchOptionsRequest = false;
private boolean dispatchTraceRequest = false;
private boolean logResolvedException = false;
private String viewPrefix;
private String viewSuffix;
private String servletPath = "/";
// ...
}
@ConfigurationProperties(prefix = "spring.web")
public class WebProperties {
private Resources resources = new Resources();
public static class Resources {
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
private boolean addMappings = true;
private boolean cache = true;
private Cache cacheProperties;
// ...
}
}
三、基本配置
3.1 服务器配置
server:
port: 8080 # 端口号
servlet:
context-path: /api # 上下文路径
session:
timeout: 30m # Session超时时间
tomcat:
uri-encoding: UTF-8 # URI编码
max-threads: 200 # 最大线程数
min-spare-threads: 10 # 最小空闲线程数
accept-count: 100 # 等待队列长度
max-connections: 10000 # 最大连接数
ssl:
enabled: true # 启用SSL
key-store: classpath:keystore.p12
key-store-password: your-password
key-store-type: PKCS12
key-alias: tomcat
3.2 Spring MVC配置
spring:
mvc:
servlet:
path: / # DispatcherServlet路径
static-path-pattern: /static/** # 静态资源访问路径
view:
prefix: /WEB-INF/views/ # 视图前缀
suffix: .jsp # 视图后缀
contentnegotiation:
favor-parameter: true # 支持参数决定返回格式
parameter-name: format # 参数名
format:
date: yyyy-MM-dd # 日期格式
date-time: yyyy-MM-dd HH:mm:ss # 日期时间格式
time: HH:mm:ss # 时间格式
web:
resources:
static-locations: classpath:/static/,classpath:/public/
cache:
period: 3600 # 缓存时间(秒)
3.3 编码配置
server:
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
四、视图解析器配置
4.1 Thymeleaf配置
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
配置文件:
spring:
thymeleaf:
prefix: classpath:/templates/ # 模板路径
suffix: .html # 模板后缀
mode: HTML # 模板模式
encoding: UTF-8 # 编码
cache: false # 开发环境关闭缓存
servlet:
content-type: text/html;charset=UTF-8
使用示例:
@Controller
public class UserController {
@GetMapping("/users")
public String userList(Model model) {
model.addAttribute("users", userService.findAll());
return "user/list"; // 对应 templates/user/list.html
}
}
4.2 FreeMarker配置
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
配置文件:
spring:
freemarker:
template-loader-path: classpath:/templates/
suffix: .ftl
content-type: text/html;charset=UTF-8
cache: false
settings:
number_format: 0.##########
datetime_format: yyyy-MM-dd HH:mm:ss
4.3 视图解析器对比
| 特性 | Thymeleaf | FreeMarker | JSP |
|---|---|---|---|
| 模板文件 | .html | .ftl | .jsp |
| 自然模板 | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
| Spring集成 | ✅ 官方推荐 | ✅ 支持 | ✅ 支持 |
| 性能 | 中等 | 高 | 高 |
| 学习曲线 | 平缓 | 中等 | 平缓 |
| 热部署 | ✅ 支持 | ✅ 支持 | ❌ 需重启 |
五、拦截器配置
5.1 自定义拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
log.info("请求路径: {}", request.getRequestURI());
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"未登录\"}");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
log.info("请求处理完成");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
log.info("请求结束");
}
}
5.2 注册拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/login",
"/register",
"/static/**",
"/error",
"/swagger-ui/**",
"/v3/api-docs/**"
)
.order(1);
}
}
5.3 多拦截器执行顺序
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 按order值从小到大执行preHandle,从大到小执行postHandle和afterCompletion
registry.addInterceptor(new FirstInterceptor())
.addPathPatterns("/**")
.order(1);
registry.addInterceptor(new SecondInterceptor())
.addPathPatterns("/**")
.order(2);
}
}
执行流程:
请求 → FirstInterceptor.preHandle()
→ SecondInterceptor.preHandle()
→ Controller
→ SecondInterceptor.postHandle()
→ FirstInterceptor.postHandle()
→ 视图渲染
→ SecondInterceptor.afterCompletion()
→ FirstInterceptor.afterCompletion()
→ 响应
六、消息转换器配置
6.1 自定义Jackson配置
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
builder.featuresToDisable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
SerializationFeature.FAIL_ON_EMPTY_BEANS,
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
);
builder.featuresToEnable(
JsonInclude.Include.NON_NULL,
JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS
);
builder.serializerByType(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.deserializerByType(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
};
}
}
6.2 自定义消息转换器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
objectMapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
converter.setObjectMapper(objectMapper);
converters.add(0, converter);
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new CustomMessageConverter());
}
}
6.3 常用消息转换器
| 转换器 | 用途 |
|---|---|
StringHttpMessageConverter | 处理String类型 |
MappingJackson2HttpMessageConverter | 处理JSON |
ByteArrayHttpMessageConverter | 处理字节数组 |
FormHttpMessageConverter | 处理表单数据 |
ResourceHttpMessageConverter | 处理资源文件 |
AllEncompassingFormHttpMessageConverter | 处理多部分表单 |
七、静态资源处理
7.1 默认静态资源路径
Spring Boot默认从以下路径加载静态资源:
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
7.2 自定义静态资源路径
spring:
web:
resources:
static-locations:
- classpath:/static/
- classpath:/public/
- file:/data/files/
cache:
period: 31536000 # 缓存1年
cachecontrol:
max-age: 31536000
public: true
mvc:
static-path-pattern: /static/**
7.3 代码配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:/data/upload/");
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/");
}
}
7.4 静态资源版本控制
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
VersionResourceResolver versionResolver = new VersionResourceResolver()
.addContentVersionStrategy("/**");
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
.resourceChain(true)
.addResolver(versionResolver);
}
}
使用方式:
<link rel="stylesheet" href="/static/css/style-abc123.css">
八、文件上传下载
8.1 文件上传配置
spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB # 单个文件最大大小
max-request-size: 100MB # 总请求最大大小
file-size-threshold: 2KB # 超过此大小写入临时文件
location: /tmp/upload # 临时文件存储位置
8.2 单文件上传
@RestController
@RequestMapping("/api/files")
public class FileUploadController {
private static final String UPLOAD_DIR = "/data/upload/";
@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFilename = UUID.randomUUID().toString() + extension;
File dest = new File(UPLOAD_DIR + newFilename);
try {
file.transferTo(dest);
return Result.success("/api/files/download/" + newFilename);
} catch (IOException e) {
return Result.error("文件上传失败: " + e.getMessage());
}
}
}
8.3 多文件上传
@PostMapping("/upload/multiple")
public Result<List<String>> uploadMultiple(@RequestParam("files") MultipartFile[] files) {
List<String> urls = new ArrayList<>();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
String url = saveFile(file);
urls.add(url);
}
}
return Result.success(urls);
}
8.4 文件下载
@GetMapping("/download/{filename}")
public void download(@PathVariable String filename, HttpServletResponse response) {
File file = new File(UPLOAD_DIR + filename);
if (!file.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
response.setContentLengthLong(file.length());
try (InputStream is = new FileInputStream(file);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
} catch (IOException e) {
log.error("文件下载失败", e);
}
}
8.5 文件上传进度监听
@Component
public class FileUploadProgressListener {
private final Map<String, Double> progressMap = new ConcurrentHashMap<>();
public void updateProgress(String fileId, double progress) {
progressMap.put(fileId, progress);
}
public double getProgress(String fileId) {
return progressMap.getOrDefault(fileId, 0.0);
}
}
@RestController
public class UploadProgressController {
@PostMapping("/upload/progress")
public Result<String> uploadWithProgress(
@RequestParam("file") MultipartFile file,
@RequestParam("fileId") String fileId) {
ProgressTrackingInputStream progressStream =
new ProgressTrackingInputStream(file.getInputStream(),
progress -> progressListener.updateProgress(fileId, progress));
// 保存文件...
return Result.success("上传成功");
}
@GetMapping("/upload/progress/{fileId}")
public Result<Double> getProgress(@PathVariable String fileId) {
return Result.success(progressListener.getProgress(fileId));
}
}
九、全局CORS配置
9.1 @CrossOrigin注解
@RestController
@RequestMapping("/api/users")
@CrossOrigin(
origins = "http://localhost:3000",
methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT},
allowedHeaders = "*",
allowCredentials = "true",
maxAge = 3600
)
public class UserController {
@GetMapping
public List<User> list() {
return userService.findAll();
}
}
9.2 全局CORS配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("http://localhost:*", "https://*.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
registry.addMapping("/public/**")
.allowedOrigins("*")
.allowedMethods("GET");
}
}
9.3 CorsFilter方式
@Configuration
public class CorsFilterConfig {
@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
9.4 CORS配置对比
| 方式 | 适用场景 | 优先级 |
|---|---|---|
@CrossOrigin | 单个Controller或方法 | 最高 |
WebMvcConfigurer.addCorsMappings | 全局配置,推荐使用 | 中 |
CorsFilter | 需要更细粒度控制时 | 可配置 |
十、最佳实践与总结
10.1 配置最佳实践
| 场景 | 建议 |
|---|---|
| 开发环境 | 关闭模板缓存、开启详细日志 |
| 生产环境 | 开启静态资源缓存、压缩响应 |
| 文件上传 | 限制文件大小、校验文件类型 |
| 拦截器 | 合理设置排除路径、注意执行顺序 |
| CORS | 使用白名单、不要使用* |
10.2 性能优化建议
spring:
mvc:
async:
request-timeout: 30000 # 异步请求超时
web:
resources:
cache:
period: 31536000 # 静态资源缓存
chain:
enabled: true # 启用资源链
compressed: true # 启用压缩
server:
compression:
enabled: true # 启用响应压缩
mime-types: text/html,text/xml,text/plain,application/json
min-response-size: 1024 # 最小压缩大小
10.3 常见问题
问题一:静态资源无法访问
// 检查是否被拦截器拦截
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/static/**"); // 排除静态资源
}
问题二:中文乱码
@Bean
public FilterRegistrationBean<CharacterEncodingFilter> encodingFilter() {
FilterRegistrationBean<CharacterEncodingFilter> registration =
new FilterRegistrationBean<>();
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
registration.setFilter(filter);
registration.addUrlPatterns("/*");
return registration;
}
问题三:跨域请求失败
// 确保allowCredentials为true时,allowedOrigins不能为"*"
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*") // 使用allowedOriginPatterns
.allowCredentials(true);
}
10.4 总结
Spring Boot Web MVC配置要点:
| 配置项 | 关键点 |
|---|---|
| 自动配置 | 理解WebMvcAutoConfiguration的工作原理 |
| 基本配置 | 通过application.yml配置服务器和MVC属性 |
| 视图解析 | Thymeleaf是Spring Boot推荐的模板引擎 |
| 拦截器 | 实现HandlerInterceptor并注册 |
| 消息转换 | 自定义Jackson配置处理日期和JSON |
| 静态资源 | 配置资源路径和缓存策略 |
| 文件上传 | 配置大小限制,使用MultipartFile |
| CORS | 使用全局配置或注解方式 |
掌握这些配置,能够帮助开发者灵活应对各种Web开发场景,构建高效、安全的Web应用。
参考资料:
- Spring Boot官方文档 - Spring MVC
- Spring Framework官方文档 - Web MVC
- Spring Boot源码