SpringMVC 使用技巧与最佳实践
一、SpringMVC 架构概述
SpringMVC是Spring框架中的Web层模块,采用MVC(Model-View-Controller)架构模式。其核心组件包括:
- DispatcherServlet:前端控制器,请求的入口点
- HandlerMapping:将请求映射到相应的Controller方法
- Controller:处理请求的控制器
- ModelAndView:封装模型数据和视图信息
- ViewResolver:解析视图名称到具体视图实现
- HandlerInterceptor:拦截器,处理请求前后的横切关注点
二、控制器(Controller)开发技巧
1. 注解驱动的控制器
java
@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable("id") Long id, Model model) {
// 业务逻辑
model.addAttribute("user", userService.findById(id));
return "user/detail"; // 返回视图名
}
@PostMapping
public String createUser(@ModelAttribute User user, RedirectAttributes redirectAttributes) {
userService.save(user);
redirectAttributes.addFlashAttribute("message", "用户创建成功");
return "redirect:/users"; // 重定向
}
}
2. RESTful风格控制器
java
@RestController // 等同于@Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserRestController {
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@RequestBody User user) {
return userService.save(user);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
// 更新逻辑
return ResponseEntity.ok(updatedUser);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
}
三、参数绑定高级技巧
1. 基本参数绑定
java
// URL参数绑定
@GetMapping("/search")
public String search(@RequestParam("keyword") String keyword,
@RequestParam(value = "page", defaultValue = "1") int page) {
// 处理搜索请求
return "search/results";}
// 表单数据绑定到对象
@PostMapping("/register")
public String register(@ModelAttribute User user) {
// 注册用户
return "redirect:/login";}
// JSON/XML绑定到对象
@PostMapping("/api/users")
@ResponseStatus(HttpStatus.CREATED)
public User createUser(@RequestBody User user) {
return userService.save(user);
}
2. 复杂参数绑定
数组绑定
用于批量操作,如批量删除:
java
@PostMapping("/deleteBatch")
public String deleteBatch(@RequestParam("ids") Long[] ids) {
userService.deleteByIds(Arrays.asList(ids));
return "redirect:/users";
}
// 对应的表单
<form action="/deleteBatch" method="post">
<input type="checkbox" name="ids" value="1"/>
<input type="checkbox" name="ids" value="2"/>
<input type="checkbox" name="ids" value="3"/>
<button type="submit">批量删除</button>
</form>
List绑定
用于批量修改数据:
java
// 1. 创建包装类
public class UserListForm {
private List<User> users;
// getter和setter
}
// 2. Controller方法
@PostMapping("/updateBatch")
public String updateBatch(@ModelAttribute UserListForm form) {
userService.updateBatch(form.getUsers());
return "redirect:/users";
}
// 3. 表单示例(JSP)
<c:forEach items="${users}" var="user" varStatus="status">
<tr>
<td><input name="users[${status.index}].id" value="${user.id}" type="hidden"/></td>
<td><input name="users[${status.index}].name" value="${user.name}"/></td>
<td><input name="users[${status.index}].email" value="${user.email}"/></td>
</tr>
</c:forEach>
3. 自定义参数绑定
实现Converter
或Formatter
接口进行自定义类型转换:
java
// 日期字符串转换为LocalDate
@Component
public class StringToLocalDateConverter implements Converter<String, LocalDate> {
@Override
public LocalDate convert(String source) {
return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
// 在Controller中使用
@GetMapping("/between")
public List<User> findUsersBetween(@RequestParam("startDate") LocalDate startDate,
@RequestParam("endDate") LocalDate endDate) {
return userService.findBetweenDates(startDate, endDate);
}
四、拦截器(Interceptor)高级用法
1. 实现自定义拦截器
java
@Component
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
logger.info("请求路径: {}", request.getRequestURI());
logger.info("请求方法: {}", request.getMethod());
// 返回true继续处理请求,返回false中断请求
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
// 请求处理后,视图渲染前调用
if (modelAndView != null) {
logger.info("视图名称: {}", modelAndView.getViewName());
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// 整个请求完成后调用,可用于资源清理
if (ex != null) {
logger.error("请求处理异常", ex);
}
logger.info("请求处理完成");
}
}
2. 拦截器配置
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 日志拦截器 - 拦截所有请求
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/**");
// 登录拦截器 - 拦截需要登录的路径
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/admin/**", "/user/profile")
.excludePathPatterns("/admin/login", "/admin/logout", "/static/**");
}
}
3. 拦截器链执行顺序
拦截器的执行顺序取决于注册顺序:
preHandle
:按注册顺序执行postHandle
:按注册的反序执行afterCompletion
:按注册的反序执行
4. 拦截器应用场景
- 登录验证:检查用户是否已登录
- 权限检查:验证用户是否有权限访问资源
- 日志记录:记录请求信息和响应时间
- 参数修改:在请求到达控制器前修改请求参数
- 跨域处理:处理跨域请求的预检和响应头设置
五、异常处理机制
1. 控制器级别异常处理
使用@ExceptionHandler
注解处理特定控制器的异常:
java
@Controller
public class UserController {
@GetMapping("/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
if (user == null) {
throw new ResourceNotFoundException("用户不存在: " + id);
}
model.addAttribute("user", user);
return "user/detail";
}
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handleResourceNotFound(ResourceNotFoundException ex, Model model) {
model.addAttribute("error", ex.getMessage());
return "error/404";
}
}
2. 全局异常处理
使用@ControllerAdvice
实现全局异常处理:
java
@ControllerAdvice
public class GlobalExceptionHandler {
// 处理资源未找到异常
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ModelAndView handleResourceNotFound(ResourceNotFoundException ex) {
ModelAndView modelAndView = new ModelAndView("error/404");
modelAndView.addObject("error", ex.getMessage());
return modelAndView;
}
// 处理参数验证异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorResponse handleValidationException(MethodArgumentNotValidException ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "参数验证失败");
List<String> errors = ex.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
error.setDetails(errors);
return error;
}
// 处理所有未捕获的异常
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleGenericException(Exception ex) {
ModelAndView modelAndView = new ModelAndView("error/500");
modelAndView.addObject("error", "服务器内部错误");
// 在开发环境记录详细错误信息
if (environment.acceptsProfiles(Profiles.of("dev"))) {
modelAndView.addObject("details", ExceptionUtils.getStackTrace(ex));
}
return modelAndView;
}
}
六、数据校验技巧
1. 使用JSR-380(Bean Validation)
java
// 1. 在实体类中添加校验注解
public class User {
@NotNull(message = "ID不能为空")
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄必须大于等于18")
private Integer age;
// getter和setter
}
// 2. 在Controller中启用校验
@PostMapping("/register")
public String register(@Valid @ModelAttribute User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 处理校验错误
return "user/registerForm"; // 返回表单页面显示错误
}
userService.save(user);
return "redirect:/login";
}
// 对于REST API
@PostMapping("/api/users")
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<?> createUser(@Valid @RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity.created(URI.create("/api/users/" + savedUser.getId()))
.body(savedUser);
}
七、视图解析与模板引擎
1. 配置Thymeleaf模板引擎
java
@Configuration
public class ThymeleafConfig {
@Bean
public ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCacheable(false); // 开发环境设为false
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver);
// 添加自定义工具类
engine.addDialect(new LayoutDialect());
return engine;
}
}
2. 使用视图控制器
对于简单的页面跳转,可以使用视图控制器而不需要编写Controller方法:
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/about").setViewName("about");
}
}
八、文件上传与下载
1. 配置文件上传
java
@Configuration
public class MultipartConfig {
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(52428800); // 50MB
resolver.setMaxUploadSizePerFile(10485760); // 10MB
resolver.setDefaultEncoding("UTF-8");
return resolver;
}
}
2. 文件上传处理
java
@Controller
@RequestMapping("/files")
public class FileController {
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "请选择要上传的文件");
return "redirect:/uploadForm";
}
try {
// 保存文件
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
Path targetLocation = Paths.get("uploads").resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
redirectAttributes.addFlashAttribute("message", "文件上传成功: " + fileName);
} catch (IOException ex) {
redirectAttributes.addFlashAttribute("message", "上传失败: " + ex.getMessage());
}
return "redirect:/uploadForm";
}
@GetMapping("/download/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
try {
Path filePath = Paths.get("uploads").resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
throw new ResourceNotFoundException("文件不存在: " + fileName);
}
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + resource.getFilename())
.body(resource);
} catch (MalformedURLException ex) {
throw new ResourceNotFoundException("文件不存在: " + fileName);
}
}
}
九、SpringMVC性能优化技巧
1. 静态资源优化
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 配置静态资源缓存
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(31536000); // 1年缓存
// 配置WebJars
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.resourceChain(true)
.addResolver(new WebJarsResourceResolver())
.addResolver(new PathResourceResolver());
}
}
2. 启用异步处理
对于耗时操作,使用异步处理提高吞吐量:
java
@Controller
public class AsyncController {
@GetMapping("/async")
public Callable<String> handleAsyncRequest() {
return () -> {
// 模拟耗时操作
Thread.sleep(2000);
return "asyncResult";
};
}
@GetMapping("/deferred")
public DeferredResult<String> handleDeferredRequest() {
DeferredResult<String> deferredResult = new DeferredResult<>();
// 在另一个线程中处理
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
deferredResult.setResult("deferredResult");
} catch (Exception e) {
deferredResult.setErrorResult(e.getMessage());
}
});
return deferredResult;
}
}
十、安全最佳实践
1. 防止XSS攻击
java
// 在Controller中进行HTML转义
@GetMapping("/search")
public String search(@RequestParam String keyword, Model model) {
// 使用HtmlUtils进行转义
String safeKeyword = HtmlUtils.htmlEscape(keyword);
model.addAttribute("results", searchService.search(safeKeyword));
return "searchResults";
}
// 在Thymeleaf模板中自动转义
<p th:text="${user.name}"></p> <!-- 自动转义 -->
<p th:utext="${user.description}"></p> <!-- 不转义,需谨慎使用 -->
2. 防止CSRF攻击
xml
<!-- 添加Spring Security依赖 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
java
// 在表单中添加CSRF令牌
<form method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<!-- 表单字段 -->
</form>
// 在Ajax请求中添加CSRF令牌
var token = $(\'meta[name="_csrf"]\').attr(\'content\');
var header = $(\'meta[name="_csrf_header"]\').attr(\'content\');
$.ajax({
url: "/api/resource",
type: "POST",
beforeSend: function(xhr) {
xhr.setRequestHeader(header, token);
},
data: data
});
通过掌握以上SpringMVC使用技巧,您可以构建更加高效、安全、可维护的Web应用程序。SpringMVC提供了丰富的功能和灵活的配置选项,能够满足各种复杂业务场景的需求。