👋 大家好,我是 阿问学长!专注于分享优质开源项目解析、毕业设计项目指导支持、幼小初高教辅资料推荐等,欢迎关注交流!🚀

Spring MVC架构设计

前言

Spring MVC是Spring框架的Web层实现,采用了经典的MVC(Model-View-Controller)设计模式,为Web应用开发提供了灵活、强大的解决方案。RuoYi-Vue-Plus框架基于Spring MVC构建了完整的Web层架构,通过合理的分层设计和组件配置,实现了高效的前后端交互。本文将详细介绍Spring MVC的架构设计、控制器开发、视图解析器配置以及拦截器与过滤器的使用等内容。

MVC模式与组件架构

Spring MVC核心组件

/**
 * Spring MVC配置类
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.ruoyi.web.controller")
public class WebMvcConfig implements WebMvcConfigurer {
    
    private static final Logger log = LoggerFactory.getLogger(WebMvcConfig.class);
    
    @Autowired
    private RepeatSubmitInterceptor repeatSubmitInterceptor;
    
    @Autowired
    private ResourcesConfig resourcesConfig;
    
    /**
     * 静态资源处理
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 本地文件上传路径
        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
                .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
        
        // swagger配置
        registry.addResourceHandler("/swagger-ui/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
                .resourceChain(false);
    }
    
    /**
     * 跨域配置
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        // 设置访问源地址
        config.addAllowedOriginPattern("*");
        // 设置访问源请求头
        config.addAllowedHeader("*");
        // 设置访问源请求方法
        config.addAllowedMethod("*");
        // 有效期 1800秒
        config.setMaxAge(1800L);
        // 添加映射路径,拦截一切请求
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        // 返回新的CorsFilter
        return new CorsFilter(source);
    }
    
    /**
     * 自定义拦截规则
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
    }
    
    /**
     * 内容协商配置
     */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
            .favorParameter(true)
            .parameterName("mediaType")
            .defaultContentType(MediaType.APPLICATION_JSON)
            .mediaType("json", MediaType.APPLICATION_JSON)
            .mediaType("xml", MediaType.APPLICATION_XML);
    }
    
    /**
     * 消息转换器配置
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // Jackson JSON转换器
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = 
            new MappingJackson2HttpMessageConverter();
        
        ObjectMapper mapper = jackson2HttpMessageConverter.getObjectMapper();
        // 生成JSON时,将所有Long转换成String
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        mapper.registerModule(simpleModule);
        
        // 时间格式化
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setDateFormat(new SimpleDateFormat(DateUtils.YYYY_MM_DD_HH_MM_SS));
        
        jackson2HttpMessageConverter.setObjectMapper(mapper);
        converters.add(jackson2HttpMessageConverter);
        
        // 添加FastJson转换器
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        fastJsonConfig.setDateFormat(DateUtils.YYYY_MM_DD_HH_MM_SS);
        
        // 处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON);
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        
        converters.add(fastConverter);
    }
    
    /**
     * 异常解析器配置
     */
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(new GlobalExceptionHandler());
    }
    
    /**
     * 路径匹配配置
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setUseSuffixPatternMatch(false)
            .setUseTrailingSlashMatch(false)
            .setUrlPathHelper(new UrlPathHelper())
            .setPathMatcher(new AntPathMatcher());
    }
    
    /**
     * 视图控制器配置
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("forward:/index");
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/unauth").setViewName("error/unauth");
    }
    
    /**
     * 默认servlet处理器配置
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

/**
 * Web应用配置
 */
@Configuration
public class WebApplicationConfig {
    
    /**
     * 应用启动配置
     */
    @Bean
    public ServletWebServerFactory webServerFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
        factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
            @Override
            public void customize(Connector connector) {
                Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
                // 设置最大连接数
                protocol.setMaxConnections(2000);
                // 设置最大线程数
                protocol.setMaxThreads(200);
                // 设置最小空闲线程数
                protocol.setMinSpareThreads(10);
                // 设置连接超时时间
                protocol.setConnectionTimeout(5000);
                // 设置请求处理超时时间
                protocol.setAsyncTimeout(30000);
            }
        });
        return factory;
    }
    
    /**
     * 字符编码过滤器
     */
    @Bean
    public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
        FilterRegistrationBean<CharacterEncodingFilter> registration = new FilterRegistrationBean<>();
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        registration.setFilter(characterEncodingFilter);
        registration.addUrlPatterns("/*");
        registration.setName("characterEncodingFilter");
        registration.setOrder(1);
        return registration;
    }
    
    /**
     * 隐藏HTTP方法过滤器
     */
    @Bean
    public FilterRegistrationBean<HiddenHttpMethodFilter> hiddenHttpMethodFilter() {
        FilterRegistrationBean<HiddenHttpMethodFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new HiddenHttpMethodFilter());
        registration.addUrlPatterns("/*");
        registration.setName("hiddenHttpMethodFilter");
        registration.setOrder(2);
        return registration;
    }
    
    /**
     * 请求方法过滤器
     */
    @Bean
    public FilterRegistrationBean<HttpPutFormContentFilter> httpPutFormContentFilter() {
        FilterRegistrationBean<HttpPutFormContentFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new HttpPutFormContentFilter());
        registration.addUrlPatterns("/*");
        registration.setName("httpPutFormContentFilter");
        registration.setOrder(3);
        return registration;
    }
}

请求处理流程

/**
 * 请求处理流程演示控制器
 */
@RestController
@RequestMapping("/api/demo")
@Slf4j
public class RequestFlowController {
    
    /**
     * 演示完整的请求处理流程
     */
    @GetMapping("/flow/{id}")
    public R<Map<String, Object>> demonstrateRequestFlow(
            @PathVariable("id") Long id,
            @RequestParam(value = "name", required = false) String name,
            @RequestParam(value = "type", defaultValue = "default") String type,
            HttpServletRequest request,
            HttpServletResponse response) {
        
        log.info("开始处理请求: id={}, name={}, type={}", id, name, type);
        
        // 1. 请求参数处理
        Map<String, Object> requestInfo = new HashMap<>();
        requestInfo.put("pathVariable", id);
        requestInfo.put("requestParam", name);
        requestInfo.put("defaultParam", type);
        requestInfo.put("requestURI", request.getRequestURI());
        requestInfo.put("method", request.getMethod());
        requestInfo.put("contentType", request.getContentType());
        
        // 2. 请求头处理
        Map<String, String> headers = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }
        requestInfo.put("headers", headers);
        
        // 3. 业务逻辑处理
        Map<String, Object> businessData = processBusinessLogic(id, name, type);
        
        // 4. 响应处理
        response.setHeader("X-Custom-Header", "Custom-Value");
        response.setHeader("X-Process-Time", String.valueOf(System.currentTimeMillis()));
        
        // 5. 构建响应数据
        Map<String, Object> responseData = new HashMap<>();
        responseData.put("requestInfo", requestInfo);
        responseData.put("businessData", businessData);
        responseData.put("timestamp", new Date());
        
        log.info("请求处理完成: id={}", id);
        
        return R.ok(responseData);
    }
    
    /**
     * 演示请求体处理
     */
    @PostMapping("/body")
    public R<Object> handleRequestBody(@RequestBody Map<String, Object> requestBody,
                                      @RequestHeader("Content-Type") String contentType) {
        
        log.info("处理请求体: contentType={}, body={}", contentType, requestBody);
        
        // 处理请求体数据
        Map<String, Object> processedData = new HashMap<>();
        processedData.put("originalData", requestBody);
        processedData.put("processedAt", new Date());
        processedData.put("contentType", contentType);
        
        // 数据验证
        if (requestBody.containsKey("name") && requestBody.containsKey("value")) {
            processedData.put("status", "valid");
            processedData.put("message", "数据验证通过");
        } else {
            processedData.put("status", "invalid");
            processedData.put("message", "缺少必要字段");
        }
        
        return R.ok(processedData);
    }
    
    /**
     * 演示文件上传处理
     */
    @PostMapping("/upload")
    public R<Object> handleFileUpload(@RequestParam("file") MultipartFile file,
                                     @RequestParam(value = "description", required = false) String description) {
        
        log.info("处理文件上传: fileName={}, size={}, description={}", 
                file.getOriginalFilename(), file.getSize(), description);
        
        try {
            // 文件信息
            Map<String, Object> fileInfo = new HashMap<>();
            fileInfo.put("originalName", file.getOriginalFilename());
            fileInfo.put("size", file.getSize());
            fileInfo.put("contentType", file.getContentType());
            fileInfo.put("description", description);
            
            // 文件处理逻辑
            String savedPath = saveFile(file);
            fileInfo.put("savedPath", savedPath);
            fileInfo.put("uploadTime", new Date());
            
            return R.ok(fileInfo);
            
        } catch (Exception e) {
            log.error("文件上传失败", e);
            return R.fail("文件上传失败: " + e.getMessage());
        }
    }
    
    /**
     * 演示响应类型处理
     */
    @GetMapping(value = "/response", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
    public ResponseEntity<Object> handleDifferentResponseTypes(
            @RequestParam(value = "format", defaultValue = "json") String format) {
        
        Map<String, Object> data = new HashMap<>();
        data.put("message", "Hello World");
        data.put("timestamp", new Date());
        data.put("format", format);
        
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Response-Format", format);
        
        if ("xml".equalsIgnoreCase(format)) {
            headers.setContentType(MediaType.APPLICATION_XML);
        } else {
            headers.setContentType(MediaType.APPLICATION_JSON);
        }
        
        return new ResponseEntity<>(data, headers, HttpStatus.OK);
    }
    
    /**
     * 业务逻辑处理
     */
    private Map<String, Object> processBusinessLogic(Long id, String name, String type) {
        Map<String, Object> result = new HashMap<>();
        result.put("processedId", id * 2);
        result.put("processedName", name != null ? name.toUpperCase() : "DEFAULT");
        result.put("processedType", type + "_PROCESSED");
        result.put("processTime", System.currentTimeMillis());
        return result;
    }
    
    /**
     * 保存文件
     */
    private String saveFile(MultipartFile file) throws IOException {
        String fileName = file.getOriginalFilename();
        String savedPath = "/uploads/" + System.currentTimeMillis() + "_" + fileName;
        
        // 这里应该实现实际的文件保存逻辑
        log.info("文件保存到: {}", savedPath);
        
        return savedPath;
    }
}

控制器开发

RESTful API设计

/**
 * 用户管理控制器 - RESTful API设计示例
 */
@RestController
@RequestMapping("/api/users")
@Api(tags = "用户管理")
@Validated
@Slf4j
public class UserController extends BaseController {
    
    @Autowired
    private ISysUserService userService;
    
    /**
     * 获取用户列表
     */
    @GetMapping
    @ApiOperation("获取用户列表")
    @PreAuthorize("@ss.hasPermi('system:user:list')")
    public TableDataInfo<SysUser> list(SysUser user) {
        startPage();
        List<SysUser> list = userService.selectUserList(user);
        return getDataTable(list);
    }
    
    /**
     * 根据用户ID获取详细信息
     */
    @GetMapping("/{userId}")
    @ApiOperation("获取用户详情")
    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "Long", paramType = "path")
    @PreAuthorize("@ss.hasPermi('system:user:query')")
    public R<SysUser> getInfo(@PathVariable("userId") Long userId) {
        userService.checkUserDataScope(userId);
        SysUser user = userService.selectUserById(userId);
        return R.ok(user);
    }
    
    /**
     * 新增用户
     */
    @PostMapping
    @ApiOperation("新增用户")
    @PreAuthorize("@ss.hasPermi('system:user:add')")
    @Log(title = "用户管理", businessType = BusinessType.INSERT)
    public R<Void> add(@Validated @RequestBody SysUser user) {
        if (!userService.checkUserNameUnique(user)) {
            return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
        } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
            return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
        } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
            return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
        }
        
        user.setCreateBy(getUsername());
        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
        return toAjax(userService.insertUser(user));
    }
    
    /**
     * 修改用户
     */
    @PutMapping("/{userId}")
    @ApiOperation("修改用户")
    @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "Long", paramType = "path")
    @PreAuthorize("@ss.hasPermi('system:user:edit')")
    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
    public R<Void> edit(@PathVariable("userId") Long userId, @Validated @RequestBody SysUser user) {
        userService.checkUserAllowed(user);
        userService.checkUserDataScope(userId);
        
        if (!userService.checkUserNameUnique(user)) {
            return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
        } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
            return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
        } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
            return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
        }
        
        user.setUserId(userId);
        user.setUpdateBy(getUsername());
        return toAjax(userService.updateUser(user));
    }
    
    /**
     * 删除用户
     */
    @DeleteMapping("/{userIds}")
    @ApiOperation("删除用户")
    @ApiImplicitParam(name = "userIds", value = "用户ID数组", required = true, dataType = "Long[]", paramType = "path")
    @PreAuthorize("@ss.hasPermi('system:user:remove')")
    @Log(title = "用户管理", businessType = BusinessType.DELETE)
    public R<Void> remove(@PathVariable Long[] userIds) {
        if (ArrayUtils.contains(userIds, getUserId())) {
            return R.fail("当前用户不能删除");
        }
        return toAjax(userService.deleteUserByIds(userIds));
    }
    
    /**
     * 重置密码
     */
    @PutMapping("/{userId}/password")
    @ApiOperation("重置用户密码")
    @PreAuthorize("@ss.hasPermi('system:user:resetPwd')")
    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
    public R<Void> resetPassword(@PathVariable("userId") Long userId, @RequestBody Map<String, String> params) {
        userService.checkUserAllowed(SysUser.builder().userId(userId).build());
        userService.checkUserDataScope(userId);
        
        String newPassword = params.get("password");
        if (StringUtils.isEmpty(newPassword)) {
            return R.fail("新密码不能为空");
        }
        
        SysUser user = new SysUser();
        user.setUserId(userId);
        user.setPassword(SecurityUtils.encryptPassword(newPassword));
        user.setUpdateBy(getUsername());
        
        return toAjax(userService.resetUserPwd(user));
    }
    
    /**
     * 状态修改
     */
    @PutMapping("/{userId}/status")
    @ApiOperation("修改用户状态")
    @PreAuthorize("@ss.hasPermi('system:user:edit')")
    @Log(title = "用户管理", businessType = BusinessType.UPDATE)
    public R<Void> changeStatus(@PathVariable("userId") Long userId, @RequestBody Map<String, String> params) {
        userService.checkUserAllowed(SysUser.builder().userId(userId).build());
        userService.checkUserDataScope(userId);
        
        String status = params.get("status");
        SysUser user = new SysUser();
        user.setUserId(userId);
        user.setStatus(status);
        user.setUpdateBy(getUsername());
        
        return toAjax(userService.updateUserStatus(user));
    }
    
    /**
     * 根据用户编号获取授权角色
     */
    @GetMapping("/{userId}/authRole")
    @ApiOperation("获取用户授权角色")
    @PreAuthorize("@ss.hasPermi('system:user:query')")
    public R<Map<String, Object>> authRole(@PathVariable("userId") Long userId) {
        SysUser user = userService.selectUserById(userId);
        List<SysRole> roles = roleService.selectRolesByUserId(userId);
        Map<String, Object> data = new HashMap<>();
        data.put("user", user);
        data.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
        return R.ok(data);
    }
    
    /**
     * 用户授权角色
     */
    @PutMapping("/{userId}/authRole")
    @ApiOperation("用户授权角色")
    @PreAuthorize("@ss.hasPermi('system:user:edit')")
    @Log(title = "用户管理", businessType = BusinessType.GRANT)
    public R<Void> insertAuthRole(@PathVariable("userId") Long userId, @RequestParam Long[] roleIds) {
        userService.checkUserDataScope(userId);
        userService.insertUserAuth(userId, roleIds);
        return R.ok();
    }
    
    /**
     * 获取部门树列表
     */
    @GetMapping("/deptTree")
    @ApiOperation("获取部门树")
    @PreAuthorize("@ss.hasPermi('system:user:list')")
    public R<List<Tree<Long>>> deptTree(SysDept dept) {
        return R.ok(deptService.selectDeptTreeList(dept));
    }
}

/**
 * 基础控制器
 */
public class BaseController {
    
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    /**
     * 设置请求分页数据
     */
    protected void startPage() {
        PageUtils.startPage();
    }
    
    /**
     * 设置请求排序数据
     */
    protected void startOrderBy() {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) {
            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
            PageHelper.orderBy(orderBy);
        }
    }
    
    /**
     * 清理分页的线程变量
     */
    protected void clearPage() {
        PageHelper.clearPage();
    }
    
    /**
     * 响应请求分页数据
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    protected TableDataInfo getDataTable(List<?> list) {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(list);
        rspData.setTotal(PageUtils.getTotal(list));
        return rspData;
    }
    
    /**
     * 返回成功
     */
    public R<Void> success() {
        return R.ok();
    }
    
    /**
     * 返回失败消息
     */
    public R<Void> error() {
        return R.fail();
    }
    
    /**
     * 返回成功消息
     */
    public R<Void> success(String message) {
        return R.ok(message);
    }
    
    /**
     * 返回失败消息
     */
    public R<Void> error(String message) {
        return R.fail(message);
    }
    
    /**
     * 返回警告消息
     */
    public R<Void> warn(String message) {
        return R.warn(message);
    }
    
    /**
     * 响应返回结果
     *
     * @param rows 影响行数
     * @return 操作结果
     */
    protected R<Void> toAjax(int rows) {
        return rows > 0 ? R.ok() : R.fail();
    }
    
    /**
     * 响应返回结果
     *
     * @param result 结果
     * @return 操作结果
     */
    protected R<Void> toAjax(boolean result) {
        return result ? success() : error();
    }
    
    /**
     * 页面跳转
     */
    public String redirect(String url) {
        return StringUtils.format("redirect:{}", url);
    }
    
    /**
     * 获取用户缓存信息
     */
    public LoginUser getLoginUser() {
        return LoginHelper.getLoginUser();
    }
    
    /**
     * 获取登录用户id
     */
    public Long getUserId() {
        return LoginHelper.getUserId();
    }
    
    /**
     * 获取登录部门id
     */
    public Long getDeptId() {
        return LoginHelper.getDeptId();
    }
    
    /**
     * 获取登录用户名
     */
    public String getUsername() {
        return LoginHelper.getUsername();
    }
}

参数验证与绑定

/**
 * 参数验证示例控制器
 */
@RestController
@RequestMapping("/api/validation")
@Validated
@Slf4j
public class ValidationController {
    
    /**
     * 基础参数验证
     */
    @PostMapping("/basic")
    public R<Object> basicValidation(@Validated @RequestBody UserCreateRequest request) {
        log.info("基础参数验证: {}", request);
        return R.ok("验证通过", request);
    }
    
    /**
     * 分组验证
     */
    @PostMapping("/group")
    public R<Object> groupValidation(@Validated(UserCreateRequest.Create.class) @RequestBody UserCreateRequest request) {
        log.info("分组验证: {}", request);
        return R.ok("创建分组验证通过", request);
    }
    
    /**
     * 更新分组验证
     */
    @PutMapping("/group")
    public R<Object> updateGroupValidation(@Validated(UserCreateRequest.Update.class) @RequestBody UserCreateRequest request) {
        log.info("更新分组验证: {}", request);
        return R.ok("更新分组验证通过", request);
    }
    
    /**
     * 路径参数验证
     */
    @GetMapping("/path/{id}")
    public R<Object> pathValidation(@PathVariable @Min(value = 1, message = "ID必须大于0") Long id) {
        log.info("路径参数验证: id={}", id);
        return R.ok("路径参数验证通过", id);
    }
    
    /**
     * 查询参数验证
     */
    @GetMapping("/query")
    public R<Object> queryValidation(
            @RequestParam @NotBlank(message = "名称不能为空") String name,
            @RequestParam @Min(value = 1, message = "页码必须大于0") Integer page,
            @RequestParam @Range(min = 10, max = 100, message = "页面大小必须在10-100之间") Integer size) {
        
        log.info("查询参数验证: name={}, page={}, size={}", name, page, size);
        
        Map<String, Object> result = new HashMap<>();
        result.put("name", name);
        result.put("page", page);
        result.put("size", size);
        
        return R.ok("查询参数验证通过", result);
    }
    
    /**
     * 自定义验证
     */
    @PostMapping("/custom")
    public R<Object> customValidation(@Validated @RequestBody CustomValidationRequest request) {
        log.info("自定义验证: {}", request);
        return R.ok("自定义验证通过", request);
    }
    
    /**
     * 嵌套对象验证
     */
    @PostMapping("/nested")
    public R<Object> nestedValidation(@Validated @RequestBody NestedValidationRequest request) {
        log.info("嵌套对象验证: {}", request);
        return R.ok("嵌套对象验证通过", request);
    }
}

/**
 * 用户创建请求对象
 */
@Data
public class UserCreateRequest {
    
    // 验证分组接口
    public interface Create {}
    public interface Update {}
    
    @NotNull(groups = Update.class, message = "更新时ID不能为空")
    private Long id;
    
    @NotBlank(message = "用户名不能为空")
    @Length(min = 2, max = 20, message = "用户名长度必须在2-20之间")
    private String username;
    
    @NotBlank(groups = Create.class, message = "创建时密码不能为空")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$", 
             message = "密码必须包含大小写字母和数字,长度至少8位")
    private String password;
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;
    
    @Min(value = 18, message = "年龄不能小于18岁")
    @Max(value = 100, message = "年龄不能大于100岁")
    private Integer age;
    
    @NotEmpty(message = "角色列表不能为空")
    private List<@NotNull(message = "角色ID不能为空") Long> roleIds;
}

/**
 * 自定义验证请求对象
 */
@Data
public class CustomValidationRequest {
    
    @NotBlank(message = "用户名不能为空")
    @UniqueUsername(message = "用户名已存在")
    private String username;
    
    @NotBlank(message = "密码不能为空")
    @StrongPassword(message = "密码强度不够")
    private String password;
    
    @ValidEnum(enumClass = UserStatus.class, message = "用户状态值无效")
    private String status;
}

/**
 * 嵌套验证请求对象
 */
@Data
public class NestedValidationRequest {
    
    @NotBlank(message = "订单号不能为空")
    private String orderNo;
    
    @NotNull(message = "用户信息不能为空")
    @Valid
    private UserInfo userInfo;
    
    @NotEmpty(message = "订单项不能为空")
    @Valid
    private List<OrderItem> items;
    
    @Data
    public static class UserInfo {
        @NotBlank(message = "用户名不能为空")
        private String name;
        
        @Email(message = "邮箱格式不正确")
        private String email;
    }
    
    @Data
    public static class OrderItem {
        @NotBlank(message = "商品名称不能为空")
        private String productName;
        
        @Min(value = 1, message = "数量必须大于0")
        private Integer quantity;
        
        @DecimalMin(value = "0.01", message = "价格必须大于0")
        private BigDecimal price;
    }
}

/**
 * 自定义用户名唯一性验证注解
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueUsernameValidator.class)
@Documented
public @interface UniqueUsername {
    String message() default "用户名已存在";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

/**
 * 用户名唯一性验证器
 */
@Component
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
    
    @Autowired
    private ISysUserService userService;
    
    @Override
    public boolean isValid(String username, ConstraintValidatorContext context) {
        if (StringUtils.isEmpty(username)) {
            return true; // 空值由@NotBlank处理
        }
        
        return userService.checkUserNameUnique(username);
    }
}

/**
 * 强密码验证注解
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
@Documented
public @interface StrongPassword {
    String message() default "密码强度不够";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

/**
 * 强密码验证器
 */
public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {
    
    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        if (StringUtils.isEmpty(password)) {
            return true; // 空值由@NotBlank处理
        }
        
        // 密码强度检查:至少8位,包含大小写字母、数字和特殊字符
        String pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
        return password.matches(pattern);
    }
}

/**
 * 枚举值验证注解
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidEnumValidator.class)
@Documented
public @interface ValidEnum {
    String message() default "枚举值无效";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    Class<? extends Enum<?>> enumClass();
}

/**
 * 枚举值验证器
 */
public class ValidEnumValidator implements ConstraintValidator<ValidEnum, String> {
    
    private Class<? extends Enum<?>> enumClass;
    
    @Override
    public void initialize(ValidEnum constraintAnnotation) {
        this.enumClass = constraintAnnotation.enumClass();
    }
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        
        Enum<?>[] enumConstants = enumClass.getEnumConstants();
        for (Enum<?> enumConstant : enumConstants) {
            if (enumConstant.name().equals(value)) {
                return true;
            }
        }
        
        return false;
    }
}

视图解析器配置

多视图解析器配置

/**
 * 视图解析器配置
 */
@Configuration
public class ViewResolverConfig {
    
    /**
     * JSP视图解析器
     */
    @Bean
    @Order(1)
    public InternalResourceViewResolver jspViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        resolver.setOrder(1);
        return resolver;
    }
    
    /**
     * Thymeleaf视图解析器
     */
    @Bean
    @Order(2)
    public SpringResourceTemplateResolver thymeleafTemplateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(applicationContext);
        templateResolver.setPrefix("classpath:/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        templateResolver.setCacheable(false);
        templateResolver.setCharacterEncoding("UTF-8");
        return templateResolver;
    }
    
    @Bean
    @Order(2)
    public SpringTemplateEngine thymeleafTemplateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(thymeleafTemplateResolver());
        templateEngine.setEnableSpringELCompiler(true);
        return templateEngine;
    }
    
    @Bean
    @Order(2)
    public ThymeleafViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(thymeleafTemplateEngine());
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setOrder(2);
        return viewResolver;
    }
    
    /**
     * FreeMarker视图解析器
     */
    @Bean
    @Order(3)
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("classpath:/templates/freemarker/");
        configurer.setDefaultEncoding("UTF-8");
        
        Properties settings = new Properties();
        settings.setProperty("template_update_delay", "0");
        settings.setProperty("default_encoding", "UTF-8");
        settings.setProperty("number_format", "0.##########");
        settings.setProperty("datetime_format", "yyyy-MM-dd HH:mm:ss");
        settings.setProperty("classic_compatible", "true");
        settings.setProperty("template_exception_handler", "ignore");
        
        configurer.setFreemarkerSettings(settings);
        return configurer;
    }
    
    @Bean
    @Order(3)
    public FreeMarkerViewResolver freeMarkerViewResolver() {
        FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
        resolver.setCache(true);
        resolver.setPrefix("");
        resolver.setSuffix(".ftl");
        resolver.setContentType("text/html;charset=UTF-8");
        resolver.setRequestContextAttribute("request");
        resolver.setExposeRequestAttributes(true);
        resolver.setExposeSessionAttributes(true);
        resolver.setExposeSpringMacroHelpers(true);
        resolver.setOrder(3);
        return resolver;
    }
    
    /**
     * JSON视图解析器
     */
    @Bean
    @Order(4)
    public MappingJackson2JsonView jsonView() {
        MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
        jsonView.setContentType("application/json;charset=UTF-8");
        jsonView.setPrettyPrint(true);
        return jsonView;
    }
    
    @Bean
    @Order(4)
    public BeanNameViewResolver beanNameViewResolver() {
        BeanNameViewResolver resolver = new BeanNameViewResolver();
        resolver.setOrder(4);
        return resolver;
    }
    
    @Autowired
    private ApplicationContext applicationContext;
}

/**
 * 视图控制器
 */
@Controller
@RequestMapping("/view")
public class ViewController {
    
    /**
     * JSP视图
     */
    @GetMapping("/jsp")
    public String jspView(Model model) {
        model.addAttribute("message", "Hello JSP!");
        model.addAttribute("timestamp", new Date());
        return "hello"; // 解析为 /WEB-INF/views/hello.jsp
    }
    
    /**
     * Thymeleaf视图
     */
    @GetMapping("/thymeleaf")
    public String thymeleafView(Model model) {
        model.addAttribute("message", "Hello Thymeleaf!");
        model.addAttribute("user", createSampleUser());
        model.addAttribute("items", createSampleItems());
        return "hello"; // 解析为 classpath:/templates/hello.html
    }
    
    /**
     * FreeMarker视图
     */
    @GetMapping("/freemarker")
    public String freeMarkerView(Model model) {
        model.addAttribute("message", "Hello FreeMarker!");
        model.addAttribute("user", createSampleUser());
        model.addAttribute("items", createSampleItems());
        return "hello"; // 解析为 classpath:/templates/freemarker/hello.ftl
    }
    
    /**
     * JSON视图
     */
    @GetMapping("/json")
    public String jsonView(Model model) {
        model.addAttribute("message", "Hello JSON!");
        model.addAttribute("user", createSampleUser());
        model.addAttribute("timestamp", new Date());
        return "jsonView"; // 使用Bean名称解析器,返回JSON格式
    }
    
    /**
     * 重定向
     */
    @GetMapping("/redirect")
    public String redirect() {
        return "redirect:/view/thymeleaf";
    }
    
    /**
     * 转发
     */
    @GetMapping("/forward")
    public String forward() {
        return "forward:/view/thymeleaf";
    }
    
    /**
     * 创建示例用户
     */
    private Map<String, Object> createSampleUser() {
        Map<String, Object> user = new HashMap<>();
        user.put("id", 1L);
        user.put("name", "张三");
        user.put("email", "zhangsan@example.com");
        user.put("age", 25);
        return user;
    }
    
    /**
     * 创建示例列表
     */
    private List<Map<String, Object>> createSampleItems() {
        List<Map<String, Object>> items = new ArrayList<>();
        
        for (int i = 1; i <= 5; i++) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", i);
            item.put("name", "Item " + i);
            item.put("value", i * 10);
            items.add(item);
        }
        
        return items;
    }
}

总结

本文详细介绍了Spring MVC架构设计,包括:

  1. MVC模式与组件架构:Spring MVC核心组件配置、请求处理流程演示
  2. 控制器开发:RESTful API设计、参数验证与绑定、基础控制器封装
  3. 视图解析器配置:多视图解析器配置、不同视图技术集成

Spring MVC为Web应用开发提供了完整的解决方案,通过合理的架构设计和组件配置,可以构建高效、灵活的Web应用。

在下一篇文章中,我们将探讨RESTful API设计与实现。

参考资料

更多推荐