一.SpringMVC常用注解

1.1.@RequestMapping

@RequestMapping注解是一个用来处理请求地址映射的注解,可用于映射一个请求或一个方法,可以用在类或方法上。

  • 标注在方法上运行代码

用于方法上,表示在类的父路径下追加方法上注解中的地址将会访问到该方法

@Controller
public class HelloController {

    @RequestMapping("/requestTest")
    public String requestTest(){
        return "success";
    }
}

此时请求映射所映射的请求的请求路径为:

http://localhost:8080/springmvc01/requestTest

springmvc01表示项目名

  • 标注在类和方法上

用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

注意:当你在类上添加RequestMapping注解后,如果要请求映射,就意味着请求要先映射到标注类的位置,然后再映射到该类的方法上

@Controller
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/requestTest")
    public String requestTest(){
        return "success";
    }
}

此时请求映射所映射的请求的请求路径为:

http://localhost:8080/springmvc01/hello/requestTest

springmvc01表示项目名

  • 参数列表

参数

说明

value

@RequestMapping 的 value 属性必须设值; @RequestMapping 的 value 属性是通过当前请求的请求地址来匹配请求; 从源码中可以看到value属性是一个字符串类型的数组,因此说明可以将多个请求映射到一个方法上,只需要给 value 来指定一个包含多个路径的数组。

method

@RequestMapping的method属性是通过当前请求的请求方式来匹配请求; 浏览器向服务器发送请求,请求方式有很多GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS、TRACE。可以使用 method 属性来约束请求方式。

headers

@RequestMapping的headers属性是通过当前请求的请求头信息来匹配请求; @RequestMapping的headers属性是一个字符串类型的数组,可以通过下面四种表达是来设置匹配关系 例如: “header”:要求请求映射的请求必须为包含 header的请求头信息 “!header”:要求请求映射的请求必须为不包含 header的请求头信息 “header=value”:要求请求映射的请求必须为包含 header的请求头信息,并且header的值必须为value “header!=value”:要求请求映射的请求必须为包含 header的请求头信息,并且header的值必须不是value

params

@RequestMapping的params属性是通过当前请求的请求参数来匹配请求; @RequestMapping的params属性是一个字符串类型的数组,可以通过下面四种表达是来设置匹配关系 例如: “param”:要求请求映射的请求必须为包含 param的请求参数 “!param”:要求请求映射的请求是不能包含param的请求参数 “param=value”:要求请求映射的请求必须包含 param 的请求参数,且 param 参数的值必须为 value “param!=value”: 要求请求映射的请求是必须包含 param 的请求参数,其值不能为 value。

示例一:@RequestMapping的params属性

@RequestMapping(value = "/test",params = "username")
public String test(){
    return "success";
}

注意:我们设置了params属性,就意味着该请求映射的请求必须包含username才能够请求成功。

示例二:@RequestMapping的headers属性

@RequestMapping(value = "/test",headers = "Host = localhost:8081")
public String test(){
    return "success";
}

注意:如果当前请求不满足headers属性,此时页面就会显示404错误,即资源未找到。

扩展:

@GetMapping:处理get方式请求的映射 @PostMapping:处理post方式请求的映射 @PutMapping:处理put方式请求的映射 @DeleteMapping:处理delete方式请求的映射 @GetMapping就相当于@RequestMapping(method=RequestMethod.GET),它会将get映射到特定的方法上。

1.2.@RequestParam

@RequestParam主要用于将请求参数区域的数据映射到控制层方法的参数上

参数

说明

value

请求中传入参数的名称,如果不设置后台接口的value值,则会默认为该变量名。

required

该参数是否为必传项。默认是true,表示请求中一定要传入对应的参数,否则会报404错误,如果设置为false时,当请求中没有此参数,将会默认为null,而对于基本数据类型的变量,则必须有值,这时会抛出空指针异常。如果允许空值,则接口中变量需要使用包装类来声明。

defaultValue

参数的默认值,如果请求中没有同名的参数时,该变量默认为此值。注意默认值可以使用SpEL表达式,如"#{systemProperties[‘java.vm.version’]}"

示例:

@RequestMapping("/queryBooks")
public List<Book> queryBooks(
    @RequestParam(required = false,defaultValue = "0",value="page") int page,
    @RequestParam(required = false,defaultValue = "10",value = "rows") int rows){
        return bookService.queryBooks(page,rows);
}

1.3.@ModelAttribute

@ModelAttribute一个具有如下三个作用:

  • 绑定请求参数到命令对象:放在功能处理方法的入参上时,用于将多个请求参数绑定到一个命令对象,从而简化绑定流程,而且自动暴露为模型数据用于视图页面展示时使用;
  • 暴露表单引用对象为模型数据:放在处理器的一般方法(非功能处理方法)上时,是为表单准备要展示的表单引用对象,如注册时需要选择的所在城市等,而且在执行功能处理方法(@RequestMapping注解的方法)之前,自动添加到模型对象中,用于视图页面展示时使用;
  • 暴露@RequestMapping方法返回值为模型数据:放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。

示例一:绑定请求参数到命令对象

如用户登录,我们需要捕获用户登录的请求参数(用户名、密码)并封装为用户对象,此时我们可以使用@ModelAttribute绑定多个请求参数到我们的命令对象。

public String test1(@ModelAttribute("user") UserModel user)

它的作用是将该绑定的命令对象以“user”为名称添加到模型对象中供视图页面展示使用。我们此时可以在视图页面使用${user.username}来获取绑定的命令对象的属性。

示例二:暴露表单引用对象为模型数据

@ModelAttribute
public void init(Model model){
    model.addAttribute("book",new Book());
}

如上代码会在执行功能处理方法之前执行,并将其自动添加到模型对象中。

@RequestMapping("/toBookList")
public String toBookList(){
    System.out.println("toBookList");
    return "book/bookList";
}

示例三:暴露@RequestMapping方法返回值为模型数据

@ModelAttribute注解的返回值会覆盖@RequestMapping注解方法中的@ModelAttribute注解的同名命令对象。

public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)

1.4.@SessionAttributes

在默认情况下,当ModelMap中的属性作用域是request级别时,也就是说,当本次请求结束后,ModelMap中的属性将销毁。如果希望在多个请求中共享ModelMap中的属性,必须将其属性转存到session中,这样ModelMap的属性才会被跨请求访问;

spring允许我们有选择地指定ModelMap中的哪些属性需要转存到session中,以便下一个请求属对应的ModelMap的属性列表中还能访问到这些属性。

SpringMVC为我们提供这样一个注解来实现上面的场景:@SessionAttributes:将ModelMap的属性值共享到session中。

注意:@SessionAttributes注解只能使用在类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttributes设置的参数只用于暂时的传递(存入sessionAttributeStore),而不是长期的保存,长期保存的数据还是要放到Session中。

有两种方式将ModelMap中的属性值共享到session中:

1.使用注解的value属性:可以通过属性名指定需要放到会话中的属性;

@Controller 
@SessionAttributes("user")  //将ModelMap中key为user的属性共享到session中
public class DemoController {
    @RequestMapping("/hello")  
    public String hello(ModelMap model) {
        //向ModelMap中添加key为user和user1的属性
        model.addAttribute("user", new User(520, "U love me"));
        model.addAttribute("user1", new User("I love U"));
        return "result";
    }
}

2.使用注解的types属性:还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中。

@SessionAttributes(types = {User.class})
@Controller
public class DemoController{
    @RequestMapping("/hello")
    public String hello(Map<String, Object> map){
        map.put("user1", new User(520, "U love me"));
        return "hello";
    }
}

1.5.@RequestBody

@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(即请求体中的数据的);

GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。

简言之:

  • 一个请求:只有一个@RequestBody;
  • 一个请求:可以有多个@RequestParam。

Content-type: (1)application/x-www-form-urlencoded:@RequestBody不是必须加的 (2)mutipart/form-data:@RequestBody不能处理这种格式 (3)其他格式,比如application/json,application/xml等,必须使用@RequestBody来处理

@RequestMapping("/hello")
public String toHello1(@RequestBody Book book){
    log.info(">>>> 使用@RequestBody传递JSON格式的参数:{}", JSON.toJSONString(book));
    return "index";
}

@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面的类)时,会根据json字符串中的key来匹配对应实体类的属性,如果匹配一致且json中的该key对应的值符合(或可转换为)实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性。

1.6.@RequestHeader

使用 @RequestHeader 注解可以获取指定的请求头信息。如果想要获取所有的请求头信息,可以使用 Map<String,String>、MultiValueMap<String,String>、HttpHeaders 这三个 Map 中的任何一个封装所有请求头的 name 和 value。

参数列表

参数

说明

name

name 和 value 互为别名,当只有一个参数时,可以省略 value,直接("xxx") 就可以了

value

name 和 value 互为别名,当只有一个参数时,可以省略 value,直接("xxx") 就可以了

required

默认情况下,如果请求头中缺少了指定的 name,那么将会报错。 如果没有添加required = false,当请求头中没有这个zking请求头时就会报错。

defaultValue

如果请求头中缺少了指定的 name ,那么会报错,可以使用 defaultValue 这个属性指定默认值,就可以避免报错 ;如果请求头缺少指定 name ,该属性设置的值将会作为默认值,如果该属性不设置值,它有自己的默认值 DEFAULT_NONE

示例:

@GetMapping("/headParams")
public Map userInfo(
    @RequestHeader(value = "zking",defaultValue = "hello zking") String username,
    // 将请求头中 name=Accept-Encoding 赋值给形参 encoding
    @RequestHeader("Accept-Encoding") String encoding,
    // 将请求头中 name=Host 赋值给形参 host
    @RequestHeader("Host") String host,
    // 将所有请求头的 name 和 value 封装到 Map 集合 headsMap 中
    @RequestHeader Map<String,String> headsMap) {
        Map map = new HashMap<String, Object>();
        map.put("username",username);
        map.put("Accept-Encoding",encoding);
        map.put("Host",host);
        map.put("headsMap",headsMap);

        return map;
}

由于请求头中不存在 name=zking 这个信息,所以如果只用 value=zking 会抛出异常。

解决方案: 1、required 的默认值为 true ,也就是请求头中没有 name=zking 会报错,将其值改为 false,即没有该头信息也不报错

@RequestHeader(value = "zking",required = "false") String username

2、不修改 required=true 这个默认值,当头信息中不包含 name=zking ,给它一个默认值 hello zking

@RequestHeader(value = "zking",defaultValue = "hello zking") String username

1.7.@PathVariable

该注解请求URI中的模板变量部分到处理器功能处理方法的方法参数上的绑定。

即当使用@RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。

//@PathVariable可以用来映射URL中的占位符到目标方法的参数中
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id)
{
    System.out.println("testPathVariable:"+id);
    return SUCCESS;
}

Rest

即 Representational State Transfer。(资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

  • 资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。要获取这个资源,访问它的URI就可以,因此 URI 即为每一个资源的独一无二的识别符。
  • 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式。
  • 状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生状态转化State Transfer)。而这种转化是建立在表现层之上的,所以就是 表现层状态转化。具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GETPOSTPUTDELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

示例:

- /order/1 HTTP GET    :得到 id = 1 的 order 
- /order/1 HTTP DELETE :删除 id = 1 的 order 
- /order/1 HTTP PUT    :更新 id = 1 的 order 
- /order   HTTP POST   :新增 order

1.8.@CookieValue

@CookieValue注解主要是将请求的Cookie数据,映射到功能处理方法的参数上。

参数列表

参数

说明

value

绑定的参数名称,String类型。

required

是否必须包含value,boolean类型,默认为 true,表示请求参数中必须包含对应的参数;若不存在,将抛出异常。

defaultValue

默认值,String类型。当没有传参时将使用此值赋值。

示例:

@RequestMapping("/testCookieValue")
public Map<String, Object> testCookieValue(
    @CookieValue("JSESSIONID") String cookie) {
    response.put("cookie", cookie);
    return response;
}

二.SpingMVC的参数传递

1.简单类型参数

package com.YU.web;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author YU
 * @create 2023-09-05 15:14
 */
@Slf4j
@Controller
@RequestMapping("/param")
public class ParamController {
    @RequestMapping("/hello1")
    public String index(String bname,Integer bid) {
        log.info("简单类型参数:bname:{},bid:{}",bname,bid);
        return "index";
    }
}

这里我们通过在地址栏输入携带的参数进行访问,通过日志信息查看是否将参数传递到前端页面

springmvc controller跳转404_spring

日志信息:

 

springmvc controller跳转404_mvc_02

由日志信息可得知,我们的简单类型参数通过前端地址栏可以进行传输到后台 

2. 复杂类型参数

@RequestMapping("/hello2")
    public String index2(Book book, HttpServletRequest request) {
        //原生Servlet获取参数
        log.info("复杂类型参数:bname:{},bid:{}",
                request.getParameter("bname"),
                request.getParameter("bid"));
        //复杂传参
        log.info("复杂类型参数:book:{}",book.toString());
        return "index";
    }

这里我们通过复杂传参对比原生Servlet传参

访问地址栏带参数:

springmvc controller跳转404_java_03

日志信息:

 

springmvc controller跳转404_mvc_04

由此我们可以得出,传参时可以将对象直接进行传递,通过对象获取对应属性 

3. requestParam传参

@RequestMapping("/hello3")
    public String index3(
            @RequestParam String bname,
            @RequestParam(required =false) Integer bid) {
        //@requestParam参数
        log.info("@requestParam参数:bname:{},bid:{}",bname,bid);
        return "index";
    }

我们将字段bname加上@RequestParam注解,这样当我们在访问时必须携带bname参数

springmvc controller跳转404_mvc_05

@RequestParam传参可以对参数进行限制,必须携带或者可以不携带

4.PathVariable路径传参

@RequestMapping("/hello4/{bid}")
    public String index4(@PathVariable("bid") Integer bid) {
        //@PathVariable
        log.info("@PathVariable: bid:{}",bid);
        return "index";
    }

我们可以通过在地址栏的路径中进行传参,而不是用变量去携带某一个参数 

 

springmvc controller跳转404_请求头_06

日志信息:

springmvc controller跳转404_servlet_07

5.RequestBody传参

@RequestMapping("/hello5")
    public String index5(Map map) {
        //@PathVariable
        log.info("@RequestBody: map:{}",map);
        return "index";
    }
    @RequestMapping("/hello6")
    public String index6(@RequestBody Map map) {
        //@PathVariable
        log.info("@RequestBody: map:{}",map);
        return "index";
    }

 这里我们列出两种情况进行对比,第一个是没有@RequestBody注解的,第二个有@RequestBody注解

1.这里我们使用Eolink进行发送请求,首先是对hello6进行发送请求(有@RequestBody注解)

springmvc controller跳转404_mvc_08

日志信息结果:

 

springmvc controller跳转404_mvc_09

2.然后对hello5(无@RequestBody注解)

springmvc controller跳转404_java_10

日志信息结果:

springmvc controller跳转404_java_11

结论:

当我们需要去传递json格式数据时,需要用到RequestBody注解进行传递,否则请求时响应不到结果

6.RequestHeader传参

@RequestHeader是对请求头进行传参获取响应数据

@RequestMapping(value = "/hello7")
    public String index7(@RequestHeader("jwt") String jwt) {
        //@PathVariable
        log.info("@RequestHeader: jwt:{}",jwt);
        return "index";
    }

我们在对该路径进行传参响应的时候设置请求头信息 

springmvc controller跳转404_java_12

日志信息:

springmvc controller跳转404_mvc_13

 7.总结集成所有常用类型传参

@RequestMapping(value = "/hello8")
    public String index8(
            Book book,
            @RequestBody Map map,
            @RequestHeader("jwt") String jwt) {
        //@PathVariable
        log.info("@RequestHeader: jwt:{}",jwt);
        return "index";
    }

请求体中的json数据(Map) 

springmvc controller跳转404_请求头_14

 Query参数(对象Book)

springmvc controller跳转404_请求头_15

请求头参数 

springmvc controller跳转404_servlet_16

最终日志信息结果:

springmvc controller跳转404_mvc_17

 结论

常用的传参方式可以一起使用包括请求头传参,复杂传参,RequestHeader传参

三.RequestMapping

由上述案例可得知,我们在演示时用的都是RequestMapping,但是在实际开发中RequestMapping并不常用,而且安全系数低,在我们向浏览器发送请求时,如果该方法体用的RequestMapping注解,我们可以使用任意的请求方式进行访问,当遇到需要添加数据的情况时,一些不法分子发现这些漏洞时会使用循环get请求不断向服务器发送请求添加数据,造成服务器压力崩溃,所以在实际开发中我们尽量使用标明好的请求方式注解,如:GetMapping、POSTMapping等等....

RequestMapping=GetMapping+POSTMapping+PutMapping+DeleteMapping

四.返回值

1.返回JSON(1)

@RequestMapping("/hello1")
    public void hello1(HttpServletResponse response){
        Map<Object, Object> map = new HashMap<>();
        map.put("code",200);
        map.put("msg","成功添加");
        try {
            ResponseUtil.writeJson(response,map);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

运行结果:

springmvc controller跳转404_请求头_18

2. Map 返回JSON(2)

@ResponseBody
    @RequestMapping("/hello2")
    public Map hello2(HttpServletResponse response){
        Map<Object, Object> map = new HashMap<>();
        map.put("code",200);
        map.put("msg","成功添加");
        return map;
    }

运行结果:

springmvc controller跳转404_请求头_19

3.String

@RequestMapping("/hello3")
    public String hello3(){
        return "index";
    }

运行结果:

springmvc controller跳转404_spring_20

4. String+model

@RequestMapping("/hello4")
    public String hello4(Model model, HttpServletRequest request){
        model.addAttribute("currentName","死仔");
        request.setAttribute("location","网左网右");
        return "index";
    }

运行结果: 

5.ModelAndView

@RequestMapping("/hello5")
    public ModelAndView hello5(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("sign","真的呆");
        mv.setViewName("index");
        return mv;
    }

 运行结果:

springmvc controller跳转404_spring_21

五.页面跳转

1.场景一:转发到后台的方法中(当前类)

@RequestMapping("/hello6")
    public String hello6(){
        return "forward:hello2";
    }

2.场景二:转发到后台的方法中(其他类)

// 场景二:转发到后台的方法中(其他类)
    @RequestMapping("/hello7")
    public String hello7(){
        return "forward:/param/hello1";
    }

3.场景三:重定向到后台的方法中(当前类)

@RequestMapping("/hello8")
    public String hello8(){
        return "redirect:hello2";
    }

4.场景四:重定向到后台的方法中(其他类)

@RequestMapping("/hello9")
    public String hello9(){
        return "redirect:/param/hello1";
    }

结论

返回值中有forward、redirect,会绕过视图解析器

当我们使用方法体中forward转发时,不管哪种场景,地址栏都不会发生变化

反之,重定向时,地址栏都会变成当前方法体指向的方法名

 六.SpringMVC总结优点与缺点

优点

  1. 简化配置:注解可以替代繁琐的XML配置,使配置更加简洁和直观。注解使用注解处理器进行解析,可以自动完成配置和初始化的工作,减少了手动配置的工作量。
  2. 提高可读性:通过使用注解,开发人员可以更清晰地了解代码的作用和意图。注解的语义明确,可以提高代码的可读性和可维护性。
  3. 提高开发效率:注解简化了很多重复性的工作,如依赖注入、请求映射等。开发人员可以专注于核心业务逻辑而不必关注繁琐的配置细节,从而提高开发效率。
  4. 提供丰富的功能扩展:Spring框架提供了多种注解,可以应用于不同的场景和功能需求,如控制器的请求映射、数据验证、事务管理等。这些注解提供了灵活的扩展机制,使得开发人员可以根据具体需求进行定制化的开发。
  5. 支持AOP:Spring框架通过注解提供了对面向切面编程(AOP)的支持。使用注解修饰目标方法或类,可以方便地定义切点、切面和通知,提供横切关注点的解耦和重用能力。

缺点:

  1. 隐式依赖:注解的使用可能导致隐式依赖,不像显式配置那样清晰可见。有时候可能会造成代码的可读性和维护性下降,特别是在大型项目中。
  2. 学习成本:使用注解需要对注解的语法和规范有一定的了解和掌握。不熟悉注解的开发人员可能需要花费一些时间学习和适应。
  3. 自定义性受限:某些注解的功能和行为是固定的,如果需要自定义一些特定的逻辑,可能需要编写额外的代码或使用其他方式实现。
  4. 过度使用:过度使用注解可能导致代码变得难以理解和维护。在使用注解时,应该遵循适度使用的原则,确保注解的使用符合代码的整体设计思路和架构。