SpringMVC详解

目录

[1 MVC](#1 MVC)

[2 创建Spring MVC项目](#2 创建Spring MVC项目)

[3 建立连接](#3 建立连接)

[3.1 @RestController](#3.1 @RestController)

[3.2 @RequestMapping](#3.2 @RequestMapping)

[4 请求](#4 请求)

[4.1 请求中有单个参数](#4.1 请求中有单个参数)

[4.2 请求中的参数类型](#4.2 请求中的参数类型)

[4.3 请求中有多个参数](#4.3 请求中有多个参数)

[4.4 请求中传递对象](#4.4 请求中传递对象)

[4.5 请求中参数的重命名](#4.5 请求中参数的重命名)

[4.6 请求中传递数组](#4.6 请求中传递数组)

[4.7 请求中传递集合](#4.7 请求中传递集合)

[4.8 请求中传递JSON](#4.8 请求中传递JSON)

[4.9 请求中URL中的参数](#4.9 请求中URL中的参数)

[4.10 请求中传递文件](#4.10 请求中传递文件)

[4.11 获取Cookie](#4.11 获取Cookie)

(1)方式1:传统Servlet获取

(2)方式2:Spring中获得请求中Cookie的方式

[4.12 获取Session](#4.12 获取Session)

(1)方式1:传统Servlet获取

[(2)方式2:从Spring MVC内置对象HttpSession来获取](#(2)方式2:从Spring MVC内置对象HttpSession来获取)

(3)方式3:@SessionAttribute注解把Session的值直接赋值给方法的参数

[4.13 请求中获取Header](#4.13 请求中获取Header)

[5 响应](#5 响应)

[5.1 返回页面](#5.1 返回页面)

[5.2 @RestController与@Controller的区别与联系](#5.2 @RestController与@Controller的区别与联系)

(1)@RestController源码

(2)@Controller源码

(3)@ResponseBody源码

(4)区别与联系

[5.3 返回数据@ResponseBody](#5.3 返回数据@ResponseBody)

[5.4 返回html代码片段](#5.4 返回html代码片段)

[5.5 返回json](#5.5 返回json)

[5.6 返回js与css](#5.6 返回js与css)

[5.7 设置状态码](#5.7 设置状态码)

[5.8 设置Header](#5.8 设置Header)

[6 lombok工具](#6 lombok工具)

[6.1 引入依赖](#6.1 引入依赖)

(1)创建时引入

(2)pom.xml手动引入

[6.2 lombok使用](#6.2 lombok使用)

[6.3 lombok的细粒度注解](#6.3 lombok的细粒度注解)


Spring MVC全名是Spring Web MVC,它是基于Servlet API构建的Web框架,也是实现MVC思想的框架。

1 MVC

MVC思想是一种分模块处理的思想,比如常见的网站,浏览器页面是View视图,Controller控制器是请求发送给服务器处理请求的地方,而Model模型则是核心的业务逻辑,比如请求希望从数据库读取数据,那么Model层就负责和数据库的交互。

由于MVC思想在现在已经被前后端分离的思想替代,后端不需要关注视图层,因此学习SpringMVC主要是学习Web框架的用法。

2 创建Spring MVC项目

创建SpringMVC项目的方式是通过创建SpringBoot项目,引入Spring Web依赖(Spring Web MVC),即可创建。创建流程详细见SpringBoot系列:

Spring Web的用法主要是:1.建立连接。2.处理请求。3.返回响应。

3 建立连接

java 复制代码
@RestController

public class UserController {

    // 路由器规则注册

    @RequestMapping("/helloMVC")

    public String hello(){

        return "hello,Spring MVC";

    }

}

3.1 @RestController

在这里,@RestController注解和@RequestMapping注解经常是搭配使用的。@RestController注明了该类就是Controller类,可以理解为mvc思想中的Controller,在这个类中处理请求和返回响应。只有添加了这个注解,Spring收到请求后才会在该类中查看有无对应的url和处理方法。

3.2 @RequestMapping

(1)@RequestMapping ****注解提供了URL的映射关系,用于注册接口的路由映射(用户请求的URL和项目中某个类的某个方法对应)。****它有两种作用域:

1.作用于类

java 复制代码
@RequestMapping("/MVC")

@RestController

public class UserController {

    // 路由器规则注册

    @RequestMapping("/helloMVC")

    public String hello(){

        return "hello,Spring MVC";

    }

}

此时用户要访问的URL就变为:类路径+方法路径,即ip地址:8080/MVC/helloMVC。

2.作用于方法

java 复制代码
@RestController

public class UserController {

    // 路由器规则注册

    @RequestMapping("/helloMVC")

    public String hello(){

        return "hello,Spring MVC";

    }

}

此时用户要访问的URL是:方法路径,即ip地址:8080/helloMVC。

注意:@RequestMapping注解可以写多级路径,同时也可以省略第一级路径前的/(推荐不省略),如果没有Spring会自动拼接。

(2)@RequestMapping既支持Get请求,又支持Post请求,也支持其他的请求方式。

@RequestMapping(value = "/getRequest",method= RequestMethod.POST)可以使用这样的方式指定请求的方法。

4 请求

由于Servlet API在使用过程中,经常重复多次使用,比如获取请求中参数的值:HttpServletRequest req和req.getParameter()。因此Spring为了简化开发流程,就把重复性的事封装到框架中,获取参数直接通过方法的参数获取

4.1 请求中有单个参数

java 复制代码
    @RequestMapping("/oneParameter")

    public String oneParameter(String name){

        return "单个参数的值:" + name;

    }

这里需要注意,方法的参数name必须和请求的参数的键一样。

4.2 请求中的参数类型

java 复制代码
    @RequestMapping("/int")

    public String intMethod(int age){

        return "单个参数的值:" + age;

    }

但是当我们不传参数age时,就会发生下面的现象:

服务器报错,提示age是基本数据类型,默认值为0。但是不传参数默认值为null,null无法被转化为基本类型。因此,如果要传的参数存在空值的情况,推荐使用包装类型参数。

java 复制代码
    @RequestMapping("/integer")

    public String integerMethod(Integer age){

        return "单个参数的值:" + age;

    }

注意:如果传其他类型的参数,可能会出现404。比如传String,就无法转化成Integer。

4.3 请求中有多个参数

java 复制代码
    @RequestMapping("/mulParameter")

    public String mulParameter(String name, Integer age){

        return "name:" + name + " age:" + age;

    }

传递多个参数不需要指定顺序,只需要确保键和方法的参数对应。

4.4 请求中传递对象

java 复制代码
@RequestMapping("/object")
public String object(UserInfo user){

    return user.toString();

}

public class UserInfo {

    private Integer id;

    private String name;

    private Integer age;



    public Integer getId() {

        return id;

    }



    public void setId(Integer id) {

        this.id = id;

    }



    public String getName() {

        return name;

    }



    public void setName(String name) {

        this.name = name;

    }



    public Integer getAge() {

        return age;

    }



    public void setAge(Integer age) {

        this.age = age;

    }



    @Override

    public String toString() {

        return "UserInfo{" +

                "id=" + id +

                ", name='" + name + '\'' +

                ", age=" + age +

                '}';

    }

}

请求中如果传递对象,那请求的写法就依次写对象属性的键值对即可,Spring会自动把参数键值对与对象的属性和值对应。

注意:如果传递的参数个数少于对象的属性数量,未传参的属性就会被Java赋值为默认值(Java做的事)。因此,如果对象中使用基本数据类型,就不会出现null无法赋值为int等类似的情况了,而是int默认被赋值为0。

4.5 请求中参数的重命名

java 复制代码
    @RequestMapping("/rename")

    public String rename(@RequestParam("name") String username, Integer age){

        return "username:" + username + " age:" + age;

    }

当方法中参数名不希望使用请求中的参数名时,可以进行重命名。在方法参数中希望重命名的参数前添加@RequestParam("name")注解,"name"是请求中的参数名,username是重命名后的参数名。

观察@RequestParam注解的源码实现:

注意:当添加@RequestParam注解后,required属性默认为true,这说明重命名后的参数变成了必传参数,如果前端不传这个参数,就会报错。要把参数变为非必传参数,就需要在注解中添加属性@RequestParam(value = "name",required = false)。

4.6 请求中传递数组

java 复制代码
@RequestMapping("/arr")

public String arr(String[] array){

    return "arr=" + Arrays.toString(array) + ",length=" + array.length;

}

请求中传递数组有两种方式,1.参数名=值,值,值,......2.参数名=值&参数名=值&参数名=值......。

4.7 请求中传递集合

java 复制代码
    @RequestMapping("/list")

    public String list(@RequestParam("list") List<Integer> list){

        return "list=" + list.toString() + ",length=" + list.size();

    }

请求中传递集合,必须使用@RequestParam("list")和方法中的参数进行绑定(因为多个相同的参数名的多个值默认封装为数组)。同理,如果不是必传参数,也要加上required = false。

4.8 请求中传递JSON

java 复制代码
    @RequestMapping("/json")

    public String json(@RequestBody UserInfo userInfo){

        return userInfo.toString();

    }

传递json参数时,json字符串被放在请求的Body中,因此要获取json,就要在json反序列化为对象的参数前添加 @RequestBody 注解,表示从请求的Body中获取传递的参数。

注意:SpringBoot项目集成了json的依赖jackson,因此不需要再手动添加依赖到pom.xml中。

注意:在反序列化时,如果对象的类中只写了含参构造方法,此时默认的无参构造方法就会消失,因此json字符串反序列化为对象时,由于Spring内部调用的是无参构造方法,于是就会出现jackson报出的异常:com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.springmvc.UserInfo` (no Creators, like default constructor, exist)。

java 复制代码
    public UserInfo(Integer id, String name, Integer age) {

        this.id = id;

        this.name = name;

        this.age = age;

    }

要解决该问题,就需要给出含参构造方法时,保留无参构造方法。

java 复制代码
    public UserInfo() {

    }

4.9 请求中URL中的参数

这里并不是指获取?以后的参数,而是获取?前的某个路径。

java 复制代码
    @RequestMapping("/urlParameter/{name}/{age}")

    public String urlParameter(@PathVariable("name") String username, @PathVariable()Integer age){

        return "username:" + username + ",age:" + age;

    }

需要使用到@PathVariable注解,该注解和@RequestParam注解类似,可以重命名,可以选择参数是否是必传的(尽量不修改,就让参数是必传的,否则就容易出现请求路径和方法的url不匹配)。

当需要从url获取参数时,就要约定方法的url格式:/路径/{要获取的参数}/......。同时要获取的参数名必须和方法中参数名对应,且必须加上@PathVariable注解。

4.10 请求中传递文件

@RequestMapping("/fileUpload")

public String fileUpload(@RequestPart MultipartFile file) throws IOException {

String filename = file.getOriginalFilename();

file.transferTo(new File("D:\\FileTest\\" + filename));

return "文件上传成功:" + filename;

}

文件上传需要用到MultipartFile类来存储文件对象,可以不使用@RequestPart注解(建议加上,该注解也可以进行重命名等),上传的文件需要用到transferTo()来把文件对象保存到File对象所指向的路径。

4.11 获取Cookie

(1)方式1:传统Servlet获取

优点:可以获取到全部的Cookie。

java 复制代码
    @RequestMapping("/getCookie1")

    public String getCookie1(HttpServletRequest request) {

        // Servlet中获得请求中Cookie的方式(可以获得所有的Cookie)

        Cookie[] cookies = request.getCookies();

        // lambda表达式循环所有的cookie

        if(cookies != null){

            Arrays.stream(cookies).forEach(cookie -> {

                System.out.println(cookie.getName() + ":" + cookie.getValue());

            });

            return "获取cookie成功" ;

        }

        return "cookie为null" ;

    }

首先需要在Postman中创建Cookie,否则获取到的cookies就为null。

(2)方式2:Spring中获得请求中Cookie的方式

但是只能获取使用@CookieValue 注解了的参数对应的值。

java 复制代码
    @RequestMapping("/getCookie2")

    public String getCookie2(@CookieValue("username") String username) {

        // Spring中获得请求中Cookie的方式(只能获得参数列表用@CookieValue注解的Cookie)

        return "username : " + username;

    }

4.12 获取Session

(1)方式1:传统Servlet获取

java 复制代码
    @RequestMapping("/setSession")

    public String setSession(HttpServletRequest request) {

        HttpSession session = request.getSession();

        session.setAttribute("username","zhangsan");

        return "设置Session成功";

    }

    @RequestMapping("/getSession1")

    public String getSession1(HttpServletRequest request) {

        // 方式1:Servlet中从HttpServletRequest中获取session

        // 如果session不存在, 不会自动创建

        HttpSession session = request.getSession(false);

        String username = null;

        if (session != null && session.getAttribute("username") != null) {

            username = (String) session.getAttribute("username");

        }

        return "username:" + username;

    }

首先需要设置Session:

(2)方式2:从Spring MVC内置对象HttpSession来获取

java 复制代码
    @RequestMapping("/getSession2")

    public String getSession2(HttpSession session) {

        // 方式2:从Spring MVC内置对象HttpSession来获取

        String username = null;

        if (session != null && session.getAttribute("username") != null) {

            username = (String) session.getAttribute("username");

        }

        return "username:" + username;

    }

(3)方式3:@SessionAttribute注解把Session的值直接赋值给方法的参数

java 复制代码
    @RequestMapping("/getSession3")

    public String getSession3(@SessionAttribute(value = "username",required = false) String username) {

        // 方式3:@SessionAttribute注解把Session的值直接赋值给username

        return "username:" + username;

    }

4.13 请求中获取Header

java 复制代码
    @RequestMapping("/getHeader1")

    public String getHeader1(HttpServletRequest request) {

        // 方式1:传统Servlet获取Header

        String userAgent = request.getHeader("User-Agent");

        return "User-Agent:" + userAgent;

    }

    @RequestMapping("/getHeader2")

    public String getHeader2(@RequestHeader("User-Agent") String userAgent) {

        // 方式2:SpringMVC中使用@RequestHeader注解获取指定Header的键值对

        return "User-Agent:" + userAgent;

    }

获取Header也有传统Servlet的方式和SpringMVC注解的方式,使用@RequestHeader注解需要填写需要获取的Header的键的名字,并将值赋给方法的参数。

5 响应

5.1 返回页面

java 复制代码
@RestController

@RequestMapping("/respMVC")

public class ResponseController {

    @RequestMapping("/returnHTML")

    public String returnHTML(){

        return "/index.html";

    }

}

这里返回页面的路径写法是从/resources/static开始 ,如果页面被放在该目录的下一级目录,就需要加上目录名,且路径前的/不能省略

当使用@RestController,返回的其实不是页面,而是数据:

如果想要返回页面,就需要把@RestController替换成@Controller注解:

java 复制代码
@Controller

@RequestMapping("/respMVC")

public class ResponseController {

    @RequestMapping("/returnHTML")

    public String returnHTML(){

        return "/index.html";

    }

}

那么这两个注解的区别和联系到底是什么?

5.2 @RestController与@Controller的区别与联系

(1)@RestController源码

java 复制代码
@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Controller

@ResponseBody

public @interface RestController {

    @AliasFor(

        annotation = Controller.class

    )

    String value() default "";

}

@Target({ElementType.TYPE})、@Retention(RetentionPolicy.RUNTIME)、@Documented这三个注解是元注解。

其中@Target注解标识了注解的范围,TYPE类型表示类、接口或枚举等等。

@Retention注解标识注解的生命周期:SOURCE(源码时期)、CLASS(字节码时期)和RUNTIME(运行时期)。

@Documented注解用于该注解标识的内容应该包含在生成的Javadoc文档中。

剩下两个注解则是我们需要注意的重点:

(2)@Controller源码

java 复制代码
@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Component

public @interface Controller {

    @AliasFor(

        annotation = Component.class

    )

    String value() default "";

}

@Controller注解即是为@ RestController 提供请求管理功能(根据请求的URL,在所有@ RestController 注解下的类寻找请求的方法的路径)的注解 ,定义一个控制器,Spring框架启动时加载,把这个对象交给Spring管理。该注解标识的类下,所有的方法返回的数据类型均是html页面。因此只有用该注解,我们的Controller才能返回页面。

(3)@ResponseBody源码

java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface ResponseBody {

}

@ResponseBody注解即是为@ RestController 提供返回数据功能的注解 。该注解可以标注在类(类中所有的方法都返回数据),也可以标注在方法(该方法返回数据)。返回的数据类型是非视图,即text/html。因此用该注解,我们的Controller才能在返回的响应中填入数据。

(4)区别与联系

@RestController=@Controller + @ResponseBody ****。****因此@RestController既有请求管理的功能,又具有返回数据的功能,但是不具有返回视图(html页面)的功能。

如果想要返回页面,在类注解上@Controller。如果想要返回数据,直接使用@RestController注解或@ResponseBody注解。在一个类中有些方法返回页面,有些方法返回数据,那就给这个类加上@Controller,给想要返回数据的方法加上@ResponseBody。

5.3 返回数据@ResponseBody

java 复制代码
    @ResponseBody

    @RequestMapping("/returnData")

    public String returnData(){

        return "/index.html";

    }

5.4 返回html代码片段

java 复制代码
    @ResponseBody

    @RequestMapping("/returnHTMLPart")

    public String returnHTMLPart(){

        return "<h1>html片段</h1>";

    }

5.5 返回json

java 复制代码
    @ResponseBody

    @RequestMapping("/returnJSON1")

    public UserInfo returnJSON1(){

        UserInfo userInfo = new UserInfo(100,"zhangsan",20);

        return userInfo;

    }

    @ResponseBody

    @RequestMapping("/returnJSON2")

    public Map<String,String> returnJSON2(){

        Map<String,String> map = new HashMap<>();

        map.put("name","zhangsan");

        return map;

    }

返回json有多种方式,这是SpringMVC进行的响应数据的动态类型转换,对象、HashMap等具有键值对都可以自动被转化为json字符串。在响应中对应的Content-Type的值为application/json。

5.6 返回js与css

java 复制代码
    @RequestMapping("/returnJS")

    public String returnJS(){

        return "/a.js";

    }

    @RequestMapping("/returnCSS")

    public String returnCSS(){

        return "/b.css";

    }

返回js和css也是属于视图类型的,因此需要用到@Controller注解,去掉@ResponseBody注解。返回的响应中Content-Type的值分别为text/css和application/javascript。

5.7 设置状态码

java 复制代码
    @ResponseBody

    @RequestMapping("/returnStatus")

    public String returnStatus(HttpServletResponse response){

        response.setStatus(404);

        return "设置状态码成功";

    }

需要注意,状态码的设置并不影响页面的展示。

5.8 设置Header

java 复制代码
    @ResponseBody

    @RequestMapping("/returnHeader")

    public String returnHeader(HttpServletResponse response){

        response.setHeader("MyHeaderKey","MyHeaderValue");

        return "设置Header成功";

    }

6 lombok工具

lombok工具是一系列注解的工具包,用于自动提供数据类Getter、Setter等方法。

6.1 引入依赖

(1)创建时引入

(2)pom.xml手动引入

可以去Maven查询网站https://mvnrepository.com搜索lombok引入依赖

XML 复制代码
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->

<dependency>

    <groupId>org.projectlombok</groupId>

    <artifactId>lombok</artifactId>

    <version>1.18.30</version>

    <scope>provided</scope>

</dependency>

也可以下载插件EditStarters,在插件中引入:

下载好插件后,打开pom.xml文件,右键选择Generate,点击Edit Starters,再选择一个网络通畅的源进行下载。

6.2 lombok使用

java 复制代码
@Data

public class UserInfo {

    private Integer id;

    private String name;

    private Integer age;

    public UserInfo() {

    }

    public UserInfo(Integer id, String name, Integer age) {

        this.id = id;

        this.name = name;

        this.age = age;

    }

}

在数据类前加上@Data注解,lombok就会自动生成Getter、Setter等方法并和Java源文件组合到一起编译成.class文件。观察反编译后的字节码文件:

java 复制代码
public class UserInfo {

    private Integer id;

    private String name;

    private Integer age;



    public UserInfo() {

    }



    public UserInfo(Integer id, String name, Integer age) {

        this.id = id;

        this.name = name;

        this.age = age;

    }



    public Integer getId() {

        return this.id;

    }



    public String getName() {

        return this.name;

    }



    public Integer getAge() {

        return this.age;

    }



    public void setId(Integer id) {

        this.id = id;

    }



    public void setName(String name) {

        this.name = name;

    }



    public void setAge(Integer age) {

        this.age = age;

    }



    public boolean equals(Object o) {

        if (o == this) {

            return true;

        } else if (!(o instanceof UserInfo)) {

            return false;

        } else {

            UserInfo other = (UserInfo)o;

            if (!other.canEqual(this)) {

                return false;

            } else {

                label47: {

                    Object this$id = this.getId();

                    Object other$id = other.getId();

                    if (this$id == null) {

                        if (other$id == null) {

                            break label47;

                        }

                    } else if (this$id.equals(other$id)) {

                        break label47;

                    }



                    return false;

                }



                Object this$age = this.getAge();

                Object other$age = other.getAge();

                if (this$age == null) {

                    if (other$age != null) {

                        return false;

                    }

                } else if (!this$age.equals(other$age)) {

                    return false;

                }



                Object this$name = this.getName();

                Object other$name = other.getName();

                if (this$name == null) {

                    if (other$name != null) {

                        return false;

                    }

                } else if (!this$name.equals(other$name)) {

                    return false;

                }



                return true;

            }

        }

    }



    protected boolean canEqual(Object other) {

        return other instanceof UserInfo;

    }



    public int hashCode() {

        int PRIME = true;

        int result = 1;

        Object $id = this.getId();

        result = result * 59 + ($id == null ? 43 : $id.hashCode());

        Object $age = this.getAge();

        result = result * 59 + ($age == null ? 43 : $age.hashCode());

        Object $name = this.getName();

        result = result * 59 + ($name == null ? 43 : $name.hashCode());

        return result;

    }



    public String toString() {

        return "UserInfo(id=" + this.getId() + ", name=" + this.getName() + ", age=" + this.getAge() + ")";

    }

}

这些没有定义的方法都是lombok使用@Data注解自动生成的。

6.3 lombok的细粒度注解

|--------------------------|------------------------------------|
| 注解 | 作用 |
| @Getter | 自动添加getter方法(放在想要添加的属性前) |
| @Setter | 自动添加setter方法(放在想要添加的属性前) |
| @ToString | 自动添加toString方法 |
| @EqualsAndHashCode | 自动添加equals和hashCode方法 |
| @NoArgsConstructor | 自动添加无参构造方法 |
| @AllArgsConstructor | 自动添加全参构造方法,顺序是属性定义顺序 |
| @NonNull | 属性不能为Null |
| @RequiredArgsConstructor | 自动添加必需属性的构造方法,final+@NonNull的属性为必需 |

而@Data = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor + @NoArgsConstructor。

相关推荐
Swift社区1 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT2 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy2 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss3 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续4 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0444 小时前
ReAct模式解读
java·ai
轮到我狗叫了4 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
Volunteer Technology5 小时前
三高项目-缓存设计
java·spring·缓存·高并发·高可用·高数据量
栗子~~6 小时前
bat脚本- 将jar 包批量安装到 Maven 本地仓库
java·maven·jar