SpringMVC 使用技巧与最佳实践

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. 自定义参数绑定

实现ConverterFormatter接口进行自定义类型转换:

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提供了丰富的功能和灵活的配置选项,能够满足各种复杂业务场景的需求。

相关推荐
oak隔壁找我3 小时前
Java 使用技巧与最佳实践
java·后端
oak隔壁找我3 小时前
Spring 框架使用技巧与最佳实践
java·后端
白衣鸽子3 小时前
MySql数据库同步技术:构建高可用架构的基石
数据库·后端
xyy1233 小时前
Visual Studio 添加测试项目
后端
前端架构师-老李3 小时前
Java开发—JDK的安装和版本管理(macOS)
java·开发语言·macos
DoveLx3 小时前
Spring Boot 事务管理:从基础到高级
java·后端
oak隔壁找我3 小时前
Spring Boot 使用技巧与最佳实践
java·后端·面试
虎子_layor3 小时前
Java线程池快速入门
java·后端
重生之我在二本学院拿offer当牌打3 小时前
Redis分布式锁深度解析:从SETNX到Redisson,彻底搞懂分布式锁!
后端