Spring MVC

Spring MVC 核心功能实践指南:从数据传递到文件上传

本文基于个人学习笔记整理,涵盖了Spring MVC框架中数据传递、中文乱码处理、日期转换、拦截器配置、文件上传等核心功能的配置与使用方法。适合初学者快速查阅和实践。


一、数据传递(Controller → JSP)

Spring MVC提供了多种方式将数据从控制器传递到视图层:

1. 使用 ModelAndView

复制代码
@RequestMapping("/example1")
public ModelAndView example1() {
    ModelAndView mav = new ModelAndView();
    mav.addObject("message", "Hello from ModelAndView!");
    mav.setViewName("result");
    return mav;
}

2. 使用 Model/ModelMap

复制代码
@RequestMapping("/example2")
public String example2(Model model) {
    model.addAttribute("message", "Hello from Model!");
    return "result";
}

3. 返回JSON数据(@ResponseBody)

复制代码
@ResponseBody
@RequestMapping("/api/data")
public Result findAccount() {
    Result result = new Result();
    result.setCode(200);
    result.setData(accountService.findAll());
    result.setMessage("查询成功");
    return result;  // 自动转换为JSON
}

注意 :使用@ResponseBody需要添加Jackson依赖:

复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>

二、中文乱码解决方案

1. POST请求乱码处理

web.xml中配置字符编码过滤器:

复制代码
<!-- 字符编码过滤器 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2. GET请求乱码处理

方案一:修改Tomcat配置(推荐)

server.xml的Connector标签中添加:

复制代码
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           URIEncoding="UTF-8" />

方案二:代码中手动转换(临时方案)

复制代码
// 不推荐,仅作了解
String param = request.getParameter("name");
String correctParam = new String(param.getBytes("ISO-8859-1"), "UTF-8");

三、自定义日期转换器

1. 创建日期转换器

复制代码
@Component
public class DateConverter implements Converter<String, Date> {
    
    private static final String[] patterns = {
        "yyyy-MM-dd",
        "yyyy-MM-dd HH:mm:ss",
        "yyyy/MM/dd",
        "yyyy/MM/dd HH:mm:ss"
    };
    
    @Override
    public Date convert(String source) {
        if (source == null || source.trim().isEmpty()) {
            return null;
        }
        
        for (String pattern : patterns) {
            try {
                SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                sdf.setLenient(false); // 严格模式
                return sdf.parse(source);
            } catch (ParseException e) {
                // 尝试下一个模式
            }
        }
        throw new IllegalArgumentException("无法识别的日期格式: " + source);
    }
}

2. 在Spring配置中注册

复制代码
<!-- 方法一:传统XML配置 -->
<bean id="conversionService" 
      class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.example.converter.DateConverter"/>
        </set>
    </property>
</bean>

<mvc:annotation-driven conversion-service="conversionService"/>

<!-- 方法二:注解配置(推荐) -->
<!-- 在配置类中添加: -->
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new DateConverter());
    }
}

四、拦截器的使用与配置

1. 拦截器 vs 过滤器

特性 过滤器(Filter) 拦截器(Interceptor)
原理 基于Servlet回调函数 基于Spring AOP
依赖 Servlet容器 Spring框架
作用范围 所有Web资源 Spring MVC请求
典型应用 编码过滤、安全过滤 登录验证、权限控制、日志记录

2. 创建自定义拦截器

复制代码
public class LoginInterceptor implements HandlerInterceptor {
    
    /**
     * 前置处理:在控制器执行前调用
     */
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        HttpSession session = request.getSession();
        Object user = session.getAttribute("user");
        
        if (user == null) {
            // 未登录,重定向到登录页
            response.sendRedirect(request.getContextPath() + "/login");
            return false; // 中断请求
        }
        return true; // 放行
    }
    
    /**
     * 后置处理:在控制器执行后,视图渲染前调用
     */
    @Override
    public void postHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler,
                         ModelAndView modelAndView) throws Exception {
        // 可修改ModelAndView
    }
    
    /**
     * 完成处理:在视图渲染完成后调用
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
                              HttpServletResponse response,
                              Object handler,
                              Exception ex) throws Exception {
        // 资源清理
    }
}

3. XML配置拦截器

复制代码
<mvc:interceptors>
    <!-- 全局拦截器(对所有请求生效) -->
    <bean class="com.example.interceptor.GlobalInterceptor"/>
    
    <!-- 路径匹配拦截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/admin/**"/>
        <mvc:mapping path="/user/**"/>
        <mvc:exclude-mapping path="/user/login"/>
        <mvc:exclude-mapping path="/user/register"/>
        <bean class="com.example.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
    
    <!-- 权限拦截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/admin/**"/>
        <bean class="com.example.interceptor.AuthInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

4. Java Config配置拦截器

复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/register", "/css/**", "/js/**");
                
        registry.addInterceptor(new LogInterceptor())
                .addPathPatterns("/api/**");
    }
}

五、文件上传完整实现

1. 添加依赖

复制代码
<!-- commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

<!-- commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

2. Spring MVC配置

复制代码
<!-- 配置文件上传解析器(id必须为multipartResolver) -->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 最大上传大小 5MB -->
    <property name="maxUploadSize" value="5242880" />
    <!-- 内存中最大文件大小 1MB -->
    <property name="maxInMemorySize" value="1048576" />
    <!-- 默认编码 -->
    <property name="defaultEncoding" value="UTF-8" />
    <!-- 上传文件临时目录 -->
    <property name="uploadTempDir" value="/tmp" />
</bean>

<!-- 静态资源映射 -->
<mvc:resources mapping="/upload/**" location="file:${upload.base.dir}/" />

3. 前端表单

复制代码
<form action="/file/upload" method="post" enctype="multipart/form-data">
    <div>
        <label>选择文件:</label>
        <input type="file" name="file" multiple>
    </div>
    <div>
        <label>描述:</label>
        <input type="text" name="description">
    </div>
    <button type="submit">上传</button>
</form>

4. 控制器处理

复制代码
@Controller
@RequestMapping("/file")
public class FileUploadController {
    
    // 从配置文件中读取上传路径
    @Value("${upload.path}")
    private String uploadPath;
    
    /**
     * 单文件上传
     */
    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file,
                           @RequestParam("description") String description,
                           Model model) {
        
        if (file.isEmpty()) {
            model.addAttribute("message", "请选择要上传的文件");
            return "error";
        }
        
        try {
            // 1. 生成唯一文件名
            String originalFilename = file.getOriginalFilename();
            String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
            String newFilename = UUID.randomUUID().toString() + fileExtension;
            
            // 2. 创建目标文件
            File dest = new File(uploadPath + File.separator + newFilename);
            
            // 3. 确保目录存在
            if (!dest.getParentFile().exists()) {
                dest.getParentFile().mkdirs();
            }
            
            // 4. 保存文件
            file.transferTo(dest);
            
            // 5. 返回结果
            model.addAttribute("filename", newFilename);
            model.addAttribute("originalFilename", originalFilename);
            model.addAttribute("size", file.getSize());
            model.addAttribute("contentType", file.getContentType());
            
            return "upload-success";
            
        } catch (IOException e) {
            e.printStackTrace();
            model.addAttribute("message", "文件上传失败: " + e.getMessage());
            return "error";
        }
    }
    
    /**
     * 多文件上传
     */
    @PostMapping("/upload-multiple")
    public String uploadMultipleFiles(@RequestParam("files") MultipartFile[] files,
                                    Model model) {
        List<String> uploadedFiles = new ArrayList<>();
        
        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                try {
                    String filename = saveFile(file);
                    uploadedFiles.add(filename);
                } catch (IOException e) {
                    model.addAttribute("message", "文件保存失败: " + e.getMessage());
                    return "error";
                }
            }
        }
        
        model.addAttribute("uploadedFiles", uploadedFiles);
        return "upload-multiple-success";
    }
    
    /**
     * 文件下载
     */
    @GetMapping("/download/{filename}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
        try {
            File file = new File(uploadPath + File.separator + filename);
            Resource resource = new FileSystemResource(file);
            
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, 
                            "attachment; filename=\"" + file.getName() + "\"")
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .body(resource);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }
    }
    
    private String saveFile(MultipartFile file) throws IOException {
        String originalFilename = file.getOriginalFilename();
        String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        String newFilename = System.currentTimeMillis() + extension;
        
        File dest = new File(uploadPath + File.separator + newFilename);
        file.transferTo(dest);
        
        return newFilename;
    }
}

5. 配置文件上传路径

application.properties中配置:

roperties

properties

复制代码
# 文件上传路径
upload.path=D:/uploads

# 或者使用相对路径
# upload.path=./uploads

# 最大文件大小
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=10MB

六、常见问题与解决方案

问题1:文件大小限制异常

错误信息org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException

解决方案

  1. multipartResolver中配置更大的maxUploadSize

  2. 或使用Spring Boot的配置项:

    spring.servlet.multipart.max-file-size=10MB
    spring.servlet.multipart.max-request-size=20MB

问题2:临时目录权限不足

解决方案:明确指定临时目录

复制代码
@Bean
public MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    factory.setLocation("/tmp/upload");
    return factory.createMultipartConfig();
}

问题3:中文文件名乱码

解决方案:在表单中设置编码

复制代码
<form ... accept-charset="UTF-8">

或在控制器中处理:

复制代码
// 对文件名进行URL编码
String encodedFilename = URLEncoder.encode(originalFilename, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFilename);

总结

本文通过实际代码示例,详细介绍了Spring MVC中几个核心功能的实现方式:

  1. 数据传递:掌握三种常用方式,灵活选择

  2. 中文处理:区分GET/POST,一劳永逸解决乱码

  3. 日期转换:自定义转换器,简化参数绑定

  4. 拦截器:理解与过滤器的区别,实现AOP功能

  5. 文件上传:完整流程,包含异常处理和最佳实践

相关推荐
StockTV几秒前
印度股票实时数据 NSE和BSE的实时行情、K 线及指数数据
java·开发语言·spring boot·python
User_芊芊君子3 分钟前
【OpenAI 把 AI 玩明白了】:自主推理 + 动态知识图谱,这 4 个技术突破要颠覆行业
java·人工智能·知识图谱
c++之路36 分钟前
C++20概述
java·开发语言·c++20
Championship.23.2440 分钟前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
橘子海全栈攻城狮1 小时前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
逻辑驱动的ken1 小时前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法
冷雨夜中漫步2 小时前
Claude Code源码分析——Claude Code Agent Loop 详细设计文档
java·开发语言·人工智能·ai
直奔標竿2 小时前
Java开发者AI转型第二十六课!Spring AI 个人知识库实战(五)——联网搜索增强实战
java·开发语言·人工智能·spring boot·后端·spring
one_love_zfl2 小时前
java面试-微服务组件篇
java·微服务·面试
一只大袋鼠2 小时前
Java进阶:CGLIB动态代理解析
java·开发语言