SpringMVC高级

SpringMVC高级

课程计划

  • 文件上传

  • 文件下载

  • 异常处理

  • 拦截器

  • 参数类型转换

学习目标

  • MVC的方式上传文件

  • 流的形式下载文件

  • 自定义异常的处理

  • 拦截器的原理和案例

八、MVC文件上传

1、引入依赖

  • 引入commons-io:是为了简化文件保存、流处理等操作,提示开发效率。

  • 里面提供了大量的工具类(FileUtils,IOUtils),简化了文件的读写、流处理、路径操作等常见任务

    <!--文件工具-->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.15.1</version>
    </dependency>

2、修改配置文件

public class WebInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
​
        //创建AnnotationConfigWebApplicationContext对象。声明启动类
        AnnotationConfigWebApplicationContext context =
                new AnnotationConfigWebApplicationContext();
        context.register(WebMvcConfig.class);
​
        //注册 DispatcherServlet
        DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration =
                servletContext.addServlet("dispatcher",dispatcherServlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
        //注册 字符编码 过滤器
        FilterRegistration.Dynamic encodingFilter =
                servletContext.addFilter("characterEncodingFilter", CharacterEncodingFilter.class);
        encodingFilter.setInitParameter("encoding","UTF-8");
        encodingFilter.setInitParameter("forceEncoding","true");
        encodingFilter.addMappingForUrlPatterns(null,false,"/");
​
        //注册 文件上传参数
        //配置临时文件
        String tempDir = System.getProperty("java.io.tmpdir");//系统临时文件夹
        //配置:允许上传的最大文件大小,临时文件阈值
        MultipartConfigElement multipartConfig = new MultipartConfigElement(
                tempDir,                //临时文件目录
                1024*1024*10,            //单个文件最大大小:10MB
                1024*1024*50,            //整个请求最大大小:50MB
                1024*1024                //超过此大小(1MB)的文件会先存储到临时目录
                );
        //文件的配置,绑定到DispatcherServlet中
        registration.setMultipartConfig(multipartConfig);
    }
}

3、单文件上传

@RestController
@RequestMapping("/file")
public class UploadController {
    @Autowired
    HttpServletRequest request;
​
    //写一个方法,接收页面传入的文件
    @PostMapping("/doUpload1")
    public String doUpload1(MultipartFile abc) throws IOException {
        //获取文件保存的根路径--得到文件之后,保存到哪里
        String basePath = getBasePath();
        //获取文件名/生成文件名
        String fileName = abc.getOriginalFilename();
        //保存文件
        abc.transferTo(new File(basePath + fileName));
        return "basePath + fileName";
    }
​
    private String getBasePath(){
        //获取项目的根路径,把上传来的图片/文件 存储到这里
        String realPath = request.getServletContext().getRealPath("/");
        // 对应的是webapp的根路径
        System.out.println(realPath);
        String savePath = realPath + "imgs/";
        //判断文件夹是否存在
        File baseFile = new File(savePath);
        if (!baseFile.exists()){
            baseFile.mkdirs();
        }
        return savePath;
    }
​
}

4、多文件上传

    @PostMapping("/doUpload2")
    public String doUpload2(@RequestParam("abc") List<MultipartFile> files) throws IOException {
        if (files!=null && !files.isEmpty()){
            for (MultipartFile file : files){
                file.transferTo(new File(getBasePath()+file.getOriginalFilename()));
            }
        }
        return "success";
    }

注意:测试的时候,注意被选中文件的大小,如果超过10MB,上传失败

九、文件下载

1、配置静态资源文件放行

@Configuration
@ComponentScan(basePackages = "com.javasm")
@EnableWebMvc//开启WebMvc
public class WebMvcConfig implements WebMvcConfigurer {
    //在下方,添加各种MVC配置
​
​
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/imgs/**")//浏览器访问的前缀
                .addResourceLocations("/imgs/");//资源在项目中的实际存储路径(相较于根路径)
​
        registry.addResourceHandler("/save/**")
                .addResourceLocations("/save/");
    }
}
http://127.0.0.1:8080/mvc/imgs/pic_2.jpg
访问这个路径,可以直接下载文件
​
如果浏览器支持当前下载的文件,会直接打开,不触发下载。
如果浏览器不支持当前文件,会触发下载。
​
这种下载方式,最简单,最便捷,但是也有缺点。

2、流下载

这个下载方式,必须经过服务器,让服务器写出文件,写出流信息到浏览器中,百分百触发下载

2.1 ResponseEntity
    //下载
    @GetMapping("/t1/{name}")
    public ResponseEntity<byte[]> test1(@PathVariable String name) throws Exception {
        //获取文件根路径
        String realPath = request.getServletContext().getRealPath("/save/");
        //文件全路径
        String allPath = realPath + name;
        //获取下载需要的文件信息
        FileInputStream inputStream = new FileInputStream(allPath);
        //获取 流的数组
        byte[] buffer = new byte[inputStream.available()];
        //读取== 把流 读取到数组里
        inputStream.read(buffer);
        //配置 响应的头信息
        //文件下载的时候,需要显示的文件名
        HttpHeaders headers = new HttpHeaders();
        //设置防止中文文件名乱码
        headers.setContentDispositionFormData("attachment", URLEncoder.encode(name,"utf-8"));
        //new 返回的对象
        return new ResponseEntity<>(buffer, headers, HttpStatus.OK);
    }
http://localhost:8080/mvc/file/t1/t2.pdf

2.2 HttpServletResponse
    @GetMapping("/test2/{name}")
    private void test2(@PathVariable String name) throws Exception{
        String realPath = request.getServletContext().getRealPath("/save/");
        String allPath = realPath + name;
        File file = new File(allPath);
        if (!file.exists()){
            throw new FileNotFoundException(name+"文件不存在");
        }
        //代码运行到这里,说明文件是正常存在的
        String encodeFileName = URLEncoder.encode(name, StandardCharsets.UTF_8);
        //设置头信息--比如容易出错,注意查看单词拼写和标点符号
        response.setHeader("Content-Disposition","attachment;filename*=UTF-8''"+encodeFileName);
        //try……with……resource 自动关闭资源
        //FileInputStream inputStream = new FileInputStream(file) 读取 文件的 流信息
        //response.getOutputStream() 获取response对象中的向浏览器写的  流对象
        try(FileInputStream inputStream = new FileInputStream(file);
            OutputStream outputStream = response.getOutputStream())  {
            //把读取到的流信息,写出到浏览器中
            IOUtils.copy(inputStream,outputStream);
            outputStream.flush();
        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }
http://localhost:8080/mvc/file/test2/t2.pdf

十、异常

1、RestControllerAdvice注解

异常的知识点是非常重要的,在后续的SpringBoot项目中,依然在使用

//当前类,是当前项目,全局的异常处理类
//当产生异常的时候,触发类中的方法
@RestControllerAdvice
public class JavasmExceptionAdvice {

    //你想让异常产生的时候,返回给用户什么内容,这里就配置什么类型的返回值
    //当前的方法,是处理哪种异常的
    //当前项目产生的所有ArithmeticException,都会进入这个方法进行处理
    @ExceptionHandler(ArithmeticException.class)
    public String f1(ArithmeticException e){
        //打印异常,防止出现异常之后,返回了数据,但是程序员不知道哪行代码产生的异常
        e.printStackTrace();
        return e.getMessage();
    }
}

2、自定义异常

  • 自定义异常

//当程序中,产生了错误提示的时候,可以抛出自定义的异常
//主要是处理业务逻辑中,不合理的内容
public class JavasmException extends RuntimeException{

    public JavasmException() {
    }
    public JavasmException(String message){
        super(message);
    }
}
  • Controller使用

    @GetMapping("/t10")
    public String test10(Integer id){
        if (id ==10){
            throw new JavasmException("id不能是10");
        }
        int i = 10/id;
        return "Hello  Test10";
    }
  • 修改全局异常

//当前类,是当前项目,全局的异常处理类
//当产生异常的时候,触发类中的方法
@RestControllerAdvice
public class JavasmExceptionAdvice {

    //你想让异常产生的时候,返回给用户什么内容,这里就配置什么类型的返回值
    //当前的方法,是处理哪种异常的
    //当前项目产生的所有ArithmeticException,都会进入这个方法进行处理
    @ExceptionHandler(ArithmeticException.class)
    public String f1(ArithmeticException e){
        //打印异常,防止出现异常之后,返回了数据,但是程序员不知道哪行代码产生的异常
        e.printStackTrace();
        return e.getMessage();
    }

    @ExceptionHandler(JavasmException.class)
    public Map<String,Object> f2(JavasmException e){
        Map<String,Object> map = new HashMap<>();
        map.put("code",500);
        map.put("msg",e.getMessage());
        return map;
    }
    //处理哪些没有精准匹配的异常
    @ExceptionHandler(Exception.class)
    public  Map<String,Object> f3(Exception e){
        Map<String,Object> map = new HashMap<>();
        map.put("code",900);
        map.put("msg",e.getMessage());
        e.printStackTrace();
        return map;
    }
}

十一、拦截器

  • 拦截器和过滤器的区别是什么?

    • 过滤器:是Web容器,不属于框架,请求进入Tomcat之后,先经过过滤器

    • 拦截器:属于Spring框架,SpringWeb中,相当于Servlet内部的请求拦截

测试案例

  • 拦截器Java类

public class TestInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("---preHandler--在方法执行之前被执行");
        //返回值 必须是Boolean类型,如果返回true,允许进入下一站
        //如果返回false,说明没有通过验证,不让继续访问
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandler------方法执行之后被执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion------整个请求结束之后,执行的方法,渲染之后执行");
    }
}
  • WebMvcConfig配置类,添加拦截器

@Configuration
@ComponentScan(basePackages = "com.javasm")
@EnableWebMvc//开启WebMvc
public class WebMvcConfig implements WebMvcConfigurer {
    //在下方,添加各种MVC配置

    //注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TestInterceptor())
                //指定拦截器,拦截的路径,
                .addPathPatterns("/**")// /**表示拦截所有的路径
                .excludePathPatterns("/login","/imgs/**");
        //如果有多个拦截器,在下面继续配置
        
    }
    //配置放过静态资源文件
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/imgs/**")//浏览器访问的前缀
                .addResourceLocations("/imgs/");//资源在项目中的实际存储路径(相较于根路径)

        registry.addResourceHandler("/save/**")
                .addResourceLocations("/save/");
    }
}

登录拦截器

  • 拦截器Java类

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //只有登录的用户,才能访问Controller
        Object user = request.getSession().getAttribute("user");
        if (user != null){
            return true;
        }
        //代码执行到这里,说明没有登录
        Map<String,Object> map = new HashMap<>();
        map.put("code",1001);
        map.put("msg","用户未登录,禁止访问");
        String s = JSON.toJSONString(map);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(s);
        response.getWriter().close();
        return false;
    }
}
  • 配置类

        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/doLogin");

十二、数据转换

1、普通参数

"Method parameter 'ctime': Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; Failed to convert from type [java.lang.String] to type [java.util.Date] for value [2025-11-18]
@PostMapping("/doAdd1")
public String doAdd1(String uname, Date ctime){
    return uname;
}
@PostMapping("/doAdd1")
public String doAdd1(String uname,@DateTimeFormat(pattern = "yyyy-MM-dd") Date ctime){
    return uname;
}

2、实体类参数

Validation failed for argument [0] in public com.javasm.mvcdemo.webuser.model.WebUser com.javasm.mvcdemo.webuser.controller.LoginController.doAdd3(com.javasm.mvcdemo.webuser.model.WebUser): [Field error in object 'webUser' on field 'ctime': rejected value [2025-11-18]; codes [typeMismatch.webUser.ctime,typeMismatch.ctime,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [webUser.ctime,ctime]; arguments []; default message [ctime]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'ctime'; Failed to convert from type [java.lang.String] to type [java.util.Date] for value [2025-11-18]]] 
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WebUser {

    private Integer uid;
    private String username;

    private Company company;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date utime;

    private Date ctime;
}
@PostMapping("/doAdd3")
public WebUser doAdd3(WebUser webUser){
    return webUser;
}

3、自定义参数类型转换器

写一个转换类,做全局的时间处理

public class StringToDate implements Converter<String, Date> {

    //支持的日志格式,提前写出来
    private static final String[] PATTERNS ={
            "yyyy-MM-dd HH:mm:ss",
            "yyyy-MM-dd HH:mm",
            "yyyy-MM-dd",
            "yyyy-MM-dd'T'HH:mm",
            "yyyy/MM/dd HH:mm:ss",
            "yyyy/MM/dd"
    };

    @Override
    public Date convert(String string) {
        if (string ==null || string.trim().isEmpty()){
            return null;
        }
        for (String pattern : PATTERNS){
            try {
                SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.CHINA);
                return sdf.parse(string.trim());//转换成功返回
            } catch (ParseException e) {
                //解析失败是正常的
                continue;
            }
        }
        //如果代码执行到这里了
        throw new IllegalArgumentException("无法解析日期格式"+string+
                ",目前仅支持格式"+String.join(",",PATTERNS));
    }
}
  • 修改配置类WebMvcConfig

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToDate());
    }
  • DateTime

public class StringToDateTime implements Converter<String, LocalDateTime> {
    @Override
    public LocalDateTime convert(String source) {
        return LocalDateTime.parse(source, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }
}
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToDate());
        registry.addConverter(new StringToDateTime());
    }

4、JSON 数据转换

  • 引入依赖

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.16.1</version><!-- 与 Spring6 兼容-->
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jsr310</artifactId>
      <version>2.11.2</version>
    </dependency>
  • 修改实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class WebUser {

    private Integer uid;
    private String username;

    private Company company;

    //@DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date utime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date ctime;
    //@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime addTime;
}
{
    "uid":"1001",
    "username":"admin",
    "ctime":"2023-09-09 12:12:12",
    "utime":"2023-09-09 12:12:12"
   
}

第二天总结

重点

  • 文件上传

  • 流下载

  • @RestControllerAdvice

  • 拦截器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值