SpringMVC入门使用

本篇记录SpringMVC的使用,初学Java后端,各种大佬轻拍。

配置工作

  • 导入依赖

使用Spring5.0.2的版本,主要依赖是spring-web,其他都是学习过程中需要jar包,单纯只要SpringMVC不需要导入。


    5.0.2.RELEASE



    
        org.springframework
        spring-context
        ${spring.version}
    

    
        org.springframework
        spring-web
        ${spring.version}
    

    
        org.springframework
        spring-webmvc
        ${spring.version}
    

    
        javax.servlet
        servlet-api
        2.5
        provided
    

    
        javax.servlet.jsp
        jsp-api
        2.0
        provided
    

    
    
        com.fasterxml.jackson.core
        jackson-databind
        2.9.0
    
    
        com.fasterxml.jackson.core
        jackson-core
        2.9.0
    
    
        com.fasterxml.jackson.core
        jackson-annotations
        2.9.0
    

    
    
        commons-fileupload
        commons-fileupload
        1.3.1
    
    
        commons-io
        commons-io
        2.4
    

    
    
        com.sun.jersey
        jersey-core
        1.18.1
    
    
        com.sun.jersey
        jersey-client
        1.18.1
    

  • 配置web.xml

不管SpringMVC还是其他MVC框架,都是封装servlet的API,而SpringMVC分发请求是通过一个DispatcherServlet,再转发请求到Controller,所以我们需要在web.xml中添加配置。

  1. 配置DispatcherServlet前端控制器,在初始化参数中,配置SpringMVC的配置文件为springmvc.xml。
  2. 配置解决中文乱码的过滤器,CharacterEncodingFilter,指定编码为utf-8。


    Archetype Created Web Application
    
    
        dispatcherServlet
        org.springframework.web.servlet.DispatcherServlet
        
            contextConfigLocation
            
            classpath:springmvc.xml
        
        
        1
    
    
        dispatcherServlet
        /
    

    
    
        characterEncodingFilter
        org.springframework.web.filter.CharacterEncodingFilter
        
        
            encoding
            UTF-8
        
    
    
        characterEncodingFilter
        /*
    

  • 配置SpringMVC配置文件

上面DispatcherServlet中,我们指定SpringMVC的配置文件为springmvc.xml,所以我们需要在resources目录下,添加springmvc.xml文件。

  1. 配置spring创建容器时要扫描的包
  2. 配置视图解析器,指定jsp存放的配置,后缀等,这里指定为webapp下的WEB-INF里面的pages
  3. 配置静态资源不拦截,如html、css、js等
  4. 配置spring开启注解mvc的支持,开启后,就可以使用SpringMVC的注解



    
    

    
    
        
        
        
        
    

    
     
     
     

    
    

  • 编写通用页面

一般项目都有通用的成功、失败页面。我们都放在webapp下的WEB-INF里面的pages下。我们就简单的写一些文字来代替。

  1. 通用错误页面
<%--
  Created by IntelliJ IDEA.
  User: wally
  Date: 2020/6/23
  Time: 2:51 下午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>


    错误页面


友好的错误页面

${ errorMsg }
  1. 通用成功页面
<%--
  Created by IntelliJ IDEA.
  User: wally
  Date: 2020/6/22
  Time: 3:22 下午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    成功页面


成功

  • 编写Controller,输出HelloWord
  1. 使用@Controller注解,标识当前是一个Controller类
  2. 使用@RequestMapping注解,指定当前控制器管理的一级路径,这里为/user
  3. 使用@RequestMapping注解,指定sayHello()接口方法的响应的请求路径为/hello,完整路径为:http://localhost:8081/springmvc_sample_war_exploded/user/hello
  4. sayHello()接口方法,返回通用成功页面,直接返回success字符串,视图解析器会去pages下查找教success.jsp的文件
@Controller
@RequestMapping("/user")
public class HelloController {
    /**
     * 测试请求映射
     */
    @RequestMapping(path = "/hello")
    public String sayHello() {
        System.out.println("Hello Spring MVC");
        return "success";
    }
}
  • index.html中,添加请求跳转

a标签,只能发起GET请求,href超链接中指定请求url,点击就会跳转到success.jsp。

<%--
  Created by IntelliJ IDEA.
  User: wally
  Date: 2020/6/22
  Time: 3:05 下午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    首页


入门案例

入门案例

表单数据绑定到JavaBean

一般前端页面会有表单提交,表单数据很多时,一般我们会封装到一个JavaBean,而SpringMVC可以将数据映射到JavaBean中,省去我们从Request对象中做很多次get操作来获取。

这里我们前端页面,请求保存账户信息以及它的用户信息

  • 用户实体
/**
 * 用户实体
 */
public class User {
    private String uname;
    private Integer age;

    //省略get、set
}
  • 账户实体

账户实体中,包含一个User实体

/**
 * 账户实体
 */
public class Account implements Serializable {
    private String username;
    private String password;
    private Double money;
    /**
     * 嵌套自定义引用类型
     */
    private User user;

    //省略get、set
}
  • Controller

Controller类中添加一个saveAccount()方法,Account实体作为方法形参即可。

/**
 * 测试数据绑定到JavaBean
 */
@RequestMapping("/saveAccount")
public String saveAccount(Account account) {
    System.out.println("---- saveAccount() 调用成功 ----");
    System.out.println(account);
    return "success";
}
  • index.jsp

index.jsp添加一个表单,由于我们的User实体是嵌入到Account实体的,所以input标签的name属性,如user.name、user.age,级联写即可。


用户名:
密码:
金额:
用户姓名:
用户年龄:

自定义类型转换器

SpringMVC内置了一些转换器,例如时间转换,前端表单传递时间,使用字符串,也可以自动映射为实体的Date字段。
但例如有一些自定义格式,超过内置转换器的转换范围时,就需要我们自己自定义转换器了。

需求:例如我们使用2020-12-28这种格式,SpringMVC是不能帮我们转换的,这就需要我们自己提供转换器了。

  • User实体增加date字段,表示出生日期
/**
 * 用户实体
 */
public class User {
    private String uname;
    private Integer age;
    private Date date;

    //省略get、set
}
  • 自定义转换器

自定义转换器,需要实现Converter接口,有2个泛型,第一个泛型为转换前类型,第二个泛型为转换后的类型,这里我们是从String转为Date类型。
复写convert()转换方法,处理年-月-日的日期格式,例如:2020-12-28

/**
 * 字符串转日期类型
 */
public class StringToDateConverter implements Converter {
    public Date convert(String source) {
        try {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            return format.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("数据类型转换异常");
        }
    }
}
  • 在SpringMVC配置文件中,配置转换器

所有自定义转换器,都要在ConversionServiceFactoryBean,这个转换服务下,通过set标签排列。并将服务命名为conversionService。
最后,将conversionService服务,交给mvc:annotation-driven标签的conversion-service属性。




    //...省略其他配置

    
    
        
            
                
            
        
    

    
    

  • Controller,添加接口
/**
 * 测试自定义类型转换器
 */
@RequestMapping("/saveUser")
public String saveUser(User user) {
    System.out.println("---- saveUser() 调用成功 ----");
    System.out.println(user);
    return "success";
}
  • 前端页面index.jsp

添加表单,在date的input中填入:2020-12-28,点击提交,在后台能收到转成成功的Date即可。

保存用户

用户姓名:
用户年龄:
用户生日:

支持原生Servlet的API

虽然SpringMVC给我们提供了良好的封装,但如果我们需要用到原始的HttpServletRequest、HttpServletResponse对象时,要怎么做呢,同样很简单,只要在接口方法形参上写上即可。

  • Controller添加接口方法
/**
 * 测试获取Servlet原生API
 */
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response) {
    System.out.println("---- testServlet() 调用成功 ----");
    System.out.println(request);
    HttpSession session = request.getSession();
    System.out.println(session);
    ServletContext servletContext = session.getServletContext();
    System.out.println(servletContext);
    System.out.println(response);
    return "success";
}
  • index.jsp添加调用
测试原生Servlet的API

@RequestParam注解

上面讲到,表单请求,我们可以封装到一个JavaBean中,一般适用于参数很多和复杂的情况,如果参数很少,封装成JavaBean意义不大。那么我们就可以使用@RequestParam注解。

使用@RequestParam注解,注解内有一个value属性,标识表单字段变量,如果表单字段名和变量名一致,可以不写。
defaultValue属性,指定默认值,不传递参数或传null时生效。

/**
 * 测试@RequestParam注解
 */
@RequestMapping("/testParam")
public String testParam(@RequestParam("name") String username,
                        @RequestParam(required = false, defaultValue = "18") Integer age) {
    System.out.println("---- testParam() 调用成功 ----");
    System.out.println("username:" + username);
    System.out.println("age:" + age);
    return "success";
}
  • index.jsp增加调用
测试RequestParam注解

@RequestBody注解

除了上面讲参数对应到形参,SpringMVC还提供了@RequestBody注解,可以将表单的参数按:参数名1=值1&参数名2=值2,这样的格式获取,类似GET请求时,带参数的方式。
除了格式以外,还可以作为json上传的方式封装参数到JavaBean。

  • 第一种:按格式获取,Controller添加方法
/**
 * 测试@RequestBody注解,会将所有的表单数据按如下格式返回:username=hezihao&password=123
 */
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body) {
    System.out.println("---- testRequestBody() 调用成功 ----");
    System.out.println(body);
    return "success";
}
  • index.jsp增加调用

测试RequestBody注解

用户名:
密码:
  • 第二种:json上传方式,将参数封装到JavaBean
/**
 * 测试接收json数据请求
 */
@RequestMapping("/testAjax")
@ResponseBody
public User testAjax(@RequestBody User user) {
    System.out.println("---- testAjax() 调用成功 ----");
    System.out.println(user);
    //模拟结果
    user.setUname("haha");
    user.setAge(40);
    return user;
}
  • index.jsp增加调用,这里我引入了jQuery,将jq放在webapp目录下的js目录下即可。

点击按钮时,会发起ajax请求,后端程序接收到请求后,返回数据,前端页面alter弹出数据。


    首页
    
    




@PathVariable注解

GET请求除了表单提交外,还可以按Url的路径来请求,这时就需要用到@PathVariable注解。

  • Controller增加接口

Url为user/testPathVariable/xxx,在Url后拼上id。

/**
 * 测试@testPathVariable注解
 */
@RequestMapping("/testPathVariable/{sid}")
public String testPathVariable(@PathVariable(name = "sid") int id) {
    System.out.println("---- testPathVariable() 调用成功 ----");
    System.out.println("id:" + id);
    return "success";
}
  • index.jsp增加接口调用
测试PathVariable注解

@RequestHeader注解

当需要获取请求传递的Header请求头时,我们可以使用@RequestHeader注解来获取指定Key值的Header值。

  • Controller增加方法

例如获取默认浏览器都会带的Accept请求头。

@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader(value = "Accept") String header) {
    System.out.println("---- testRequestHeader() 调用成功 ----");
    System.out.println("header => Accept:" + header);
    return "success";
}
  • index.jsp增加接口调用
测试RequestHeader注解

@CookieValue

需要获取请求发过来的Cookie时,可以使用@CookieValue注解。

  • Controller添加方法

例如获取Session的ID,在前端浏览器上,这个id是保存在Cookie的,所以我们可以获取到。

@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue(value = "JSESSIONID") String cookieValue) {
    System.out.println("---- testCookieValue() 调用成功 ----");
    System.out.println("JSESSIONID:" + cookieValue);
    return "success";
}
  • index.jsp增加调用
测试CookieValue注解

SpringMVC的异常处理器

当我们的接口发生异常时,默认会返回异常信息的页面,这样返回给用户可不好,一般会返回一个漂亮又友好的页面。
SpringMVC给我们提供了全局异常处理器,我们可以捕获特定的异常,统一返回友好的错误页面。

  • 自定义异常

SysException系统异常,附带的message为我们自己手动设置的,异常处理器可以拿到异常中的message,返回为用户。

/**
 * 系统异常
 */
public class SysException extends Exception {
    private String message;

    public SysException(String message) {
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
  • 定义异常处理器

异常处理器需要实现HandlerExceptionResolver接口,复写resolveException()方法。
在这个方法里,我们需要判断异常对象的种类,如果是系统异常,则获取异常中的message,传递到到error错误页面。
如果是其他异常,统一返回请联系系统管理员的消息。

/**
 * 异常处理器
 */
public class SysExceptionResolver implements HandlerExceptionResolver {
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        SysException ex;
        if (e instanceof SysException) {
            ex = (SysException) e;
        } else {
            ex = new SysException("请联系系统管理员");
        }
        //跳转到友好的错误页面
        ModelAndView mv = new ModelAndView();
        mv.setViewName("error");
        mv.addObject("errorMsg", ex.getMessage());
        return mv;
    }
}
  • 配置异常处理器到springmvc.xml

有了异常处理器类还不够,Spring还不知道我们的配置,所以需要在SpringMVC的配置文件springmvc.xml中进行配置。




    //...省略其他配置

    
    

  • Controller添加接口方法

例如我们故意制造一个算数异常,try-catch后手动抛出。

/**
 * 测试异常处理器
 */
@RequestMapping("/testException")
public String testException() throws SysException {
    try {
        int result = 1 / 0;
    } catch (Exception e) {
        e.printStackTrace();
        throw new SysException(e.getMessage());
    }
    return "success";
}
  • index.jsp增加接口调用
测试异常处理器

SpringMVC拦截器

SpringMVC还提供了拦截器,给我们在请求前和请求后(响应前)做一些处理。

  • 自定义拦截器

拦截器需要实现HandlerInterceptor接口,提供以下方法进行复写。

  1. preHandle(),预处理,控制器方法执行前回调。如果返回true,则放行,执行下一个拦截器,如果没有下一个拦截器,则执行控制器中的方法。
  2. postHandle(),后处理方法,控制器方法执行后回调,jsp加载之前。
  3. afterCompletion(),jsp页面加载之后回调。

执行顺序:preHandle() => postHandle() => afterCompletion()。

我们定义了2个拦截器,当触发回调方法时,返回true,会执行下一个拦截器,返回false则相当于拦截了,后面的拦截器则不回调。

/**
 * 自定义拦截器
 */
public class MyInterceptor1 implements HandlerInterceptor {
    /**
     * 预处理,控制器方法执行前回调
     * @return 返回true,则放行,执行下一个拦截器,如果没有下一个拦截器,则执行控制器中的方法
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1 => preHandle()执行了");
        return true;
    }

    /**
     * 后处理方法,控制器方法执行后回调,jsp加载之前
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor1 => postHandle()执行了");
    }

    /**
     * jsp页面加载之后回调
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor1 => afterCompletion()执行了");
    }
}

public class MyInterceptor2 implements HandlerInterceptor {
    /**
     * 预处理,控制器方法执行前回调
     * @return 返回true,则放行,执行下一个拦截器,如果没有下一个拦截器,则执行控制器中的方法
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor2 => preHandle()执行了");
        return true;
    }

    /**
     * 后处理方法,控制器方法执行后回调,jsp加载之前
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor2 => postHandle()执行了");
    }

    /**
     * jsp页面加载之后回调
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor2 => afterCompletion()执行了");
    }
}
  • springmvc.xml中配置拦截器

和异常处理器异常,我们还需要告知Spring我们的拦截器配置。

  1. mvc:interceptor标签,定义拦截器。
  2. mvc:mapping标签,配置拦截的具体方法,可用*作为通配符。
  3. mvc:exclude-mapping标签,表示不拦截某个方法,一般我们用上面的mapping标签比较多。
  4. bean标签,class属性指定拦截器的类名。



    //...省略其他配置

    
    
        
            
            
            
            
            
            
        
    

    
        
            
            
            
            
        
    

SpringMVC文件上传

  • 增加依赖

SpringMVC的文件上传依赖commons-fileupload,commons-fileupload又依赖了commons-io。io包可以不指定,Maven会帮我们依赖传递的导入。



    commons-fileupload
    commons-fileupload
    1.3.1


    commons-io
    commons-io
    2.4

先来复习一下,传统方式,使用HttpServletRequest文件上传

  • index.jsp增加文件上传

1.form表单请求,请求方式必须为POST

  1. enctype,必须为multipart/form-data
  2. input标签为file类型

传统方式文件

选择文件:

s
  • 新建UploadController控制器
  1. 先用HttpServletRequest对象,获取路径,我们将文件放到项目部署的根目录下的uploads文件夹内
  2. 创建FileItemFactory文件工厂,解析文件项
  3. 遍历文件项,判断到是文件项时,再通过io流写文件
@Controller
@RequestMapping("/upload")
public class UploadController {
    /**
     * 传统方式-文件上传
     */
    @RequestMapping(value = "/testUpload1", method = RequestMethod.POST)
    public String testUpload1(HttpServletRequest request) throws Exception {
        //获取文件上传根目录
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        //创建文件夹
        File pathDir = new File(path);
        if (!pathDir.exists()) {
            pathDir.mkdirs();
        }
        //创建文件工厂
        FileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        //解析请求中的文件项
        List items = upload.parseRequest(request);
        for (FileItem item : items) {
            if (item.isFormField()) {
                //只是普通表单项,不是文件,忽略
            } else {
                //是文件,写文件
                String fileName = item.getName();
                //生成唯一id
                String uuid = UUID.randomUUID().toString().replace("-", "");
                fileName = fileName + "_" + uuid;
                item.write(new File(pathDir, fileName));
                //删除临时文件
                item.delete();
            }
        }
        return "success";
    }
}
  • SpringMVC方式

表单拷贝一份,请求testUpload2

SpringMVC方式文件

选择文件:

  • Controller新增testUpload2方法

SpringMVC的方式相比传统方式就简单很多,SpringMVC给我们提供了MultipartFile对象,使用这个对象的transferTo即可实现文件复制。

/**
 * SpringMVC方式-文件上传
 *
 * @param upload 多文件,变量名要和前端文件上传表单元素的name属性一致才行
 */
@RequestMapping(value = "/testUpload2", method = RequestMethod.POST)
public String testUpload2(HttpServletRequest request, @RequestParam("upload") MultipartFile upload) throws Exception {
    System.out.println("SpringMVC方式文件上传...");
    //获取文件上传路径,并创建目录
    String path = request.getSession().getServletContext().getRealPath("/uploads/");
    File pathDir = new File(path);
    if (!pathDir.exists()) {
        pathDir.mkdirs();
    }
    //重命名文件名
    String originalFilename = upload.getOriginalFilename();
    //生成唯一id
    String uuid = UUID.randomUUID().toString().replace("-", "");
    String fileName = originalFilename + "_" + uuid;
    //上传文件
    upload.transferTo(new File(pathDir, fileName));
    return "success";
}
  • 跨服务器上传

一般真实开发中,文件服务器是单独的一台服务器,不和业务服务器一起。我们可以使用jersey,来实现跨服务器上传。

  • 同样,先拷贝一份表单,请求testUpload3

跨服务器上传文件

选择文件:

  • Controller添加testUpload3方法
  1. 创建Client对象,调用resource方法,指定上传路径,一般是文件服务器的接口地址,返回WebResource对象。
  2. 使用WebResource对象的put()方法,获取MultipartFile对象的getBytes()获取Byte数据,再丢进去即可。
/**
 * 跨服务器方式-文件上传
 *
 * @param upload 多文件,变量名要和前端文件上传表单元素的name属性一致才行
 */
@RequestMapping(value = "/testUpload3", method = RequestMethod.POST)
public String testUpload3(@RequestParam("upload") MultipartFile upload) throws Exception {
    System.out.println("跨服务器方式文件上传...");
    //文件服务器上传文件请求路径
    String uploadPath = "http://localhost:9090/file_upload/uploads/";
    //重命名文件名
    String originalFilename = upload.getOriginalFilename();
    //生成唯一id
    String uuid = UUID.randomUUID().toString().replace("-", "");
    String fileName = originalFilename + "_" + uuid;
    //创建客户端对象
    Client client = Client.create();
    WebResource webResource = client.resource(uploadPath + fileName);
    //上传文件
    webResource.put(upload.getBytes());
    return "success";
}

出现的问题总结

表单请求等一路顺利,但到了文件上传,启动一直报java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileItemFactory的错。

首先想到的是我们缺少jar包,就是上面的commons-fileupload包,但是我们Maven的pom文件已经添加了,而且这个类也是可以跳转过去的。
搜索资料了很久,最后发现是Tomcat部署的lib目录,WEB-INF/lib,没有添加上这2个jar包,添加上,再启动即可。

有同学可能不知道在哪里加,打开Project Structure,选择导出的war包,点开WEB-INF/lib,再点击上面的+号,选择fileupload和commons-io,确定就可以了。

添加lib

项目地址

如果上面的描述不够清楚,我将代码都上传到了Github,有兴趣的同学可以clone。

你可能感兴趣的:(SpringMVC入门使用)