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. 文件上传:完整流程,包含异常处理和最佳实践

相关推荐
.select.1 小时前
C++ 单例模式
java·c++·单例模式
一直都在5722 小时前
JAVA类的加载过程
java·开发语言
014-code2 小时前
Dubbo 之 “最速传说”
java·分布式·dubbo
发际线还在2 小时前
互联网大厂Java面试场景故事与技术解析
java·面试·技术栈·技术解析·互联网大厂·代码案例
iPadiPhone2 小时前
性能之基:Java IO 体系深度解析、面试陷阱与实战指南
java·开发语言·后端·面试
于先生吖2 小时前
前后端分离开发 Java 跑腿系统:用户 + 骑手 + 后台三端实战
java·开发语言
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【2】架构、特性与生产级演示案例
java·人工智能·spring
iPadiPhone2 小时前
Java NIO 核心原理解析、性能调优与大厂面试精要
java·后端·面试·nio
皙然2 小时前
深度解析三色标记算法:JVM 并发 GC 的核心底层逻辑
java·jvm·算法