SpringMVC

什么是 Spring MVC?

官⽅对于 Spring MVC 的描述是这样的:

Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning. The formal name, “Spring Web MVC,”comesfrom the name of its source module (spring-webmvc), but it is morecommonly known as“Spring MVC”.

翻译为中文:
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来自其源模块的名称(Spring-webmvc),但它通常被称为“Spring MVC” 。

从上述定义我们可以得出两个关键信息:

  1. Spring MVC 是⼀个 Web 框架。
  2. Spring MVC 是基于 Servlet API 构建的。

然而要真正的理解什么是 Spring MVC?我们首先要搞清楚什么是 MVC?

MVC 定义

MVC 是 Model View Controller 的缩写,它是软件工程中的⼀种软件架构模式,它把软件系统分为模型、视图和控制器三个基本部分:

在这里插入图片描述

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据
  • View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的
  • Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据

MVC 和 Spring MVC 的关系

MVC 和 Spring MVC 的关系 就是 IoC 和 DI 的关系,就是乐观锁和 CAS的关系
MVC 是一种思想,而 Spring MVC 是对 MVC 思想的具体实现

总结来说,Spring MVC 是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架。既然是 Web框架,那么当用户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项目就可以感知到用户的请求

为什么要学 Spring MVC?

现在绝⼤部分的 Java 项目都是基于 Spring(或 Spring Boot)的,而 Spring 的核心就是 Spring MVC。也就是说 Spring MVC 是 Spring 框架的核心模块,而 Spring Boot 是 Spring 的脚⼿架,因此我们可以推断出,现在市面上绝⼤部分的 Java 项目约等于 Spring MVC 项目,这是我们要学 Spring
MVC 的原因。

在创建 Spring Boot 项目时,我们勾选的 Spring Web 框架其实就是 Spring MVC 框架,例如:

在这里插入图片描述

简单来说,咱们之所以要学习 Spring MVC 是因为它是⼀切项目的基础,我们以后创建的所有Spring、Spring Boot 项目基本都是基于 Spring MVC 的。

Spring MVC 学习

学习 Spring MVC 只需要掌握以下 3 个功能:

  1. 连接的功能:将用户(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调用到我们的Spring 程序。
  2. 获取参数的功能:用户访问的时候会带⼀些参数,在程序中要想办法获取到参数
  3. 输出数据的功能:执行了业务逻辑之后,要把程序执行的结果返回给用户

实现用户和程序映射

在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射,也就是浏览器连接程序的作用

路由映射:

所谓的路由映射指的是,当用户访问⼀个 url 时,将⽤户的请求对应到程序中某个类的某个方法的过程就叫路由映射

@RequestMapping 注解介绍

@RequestMapping 是 Spring Web 应用程序中最常被用到的注解之⼀,它是用来注册接口的路由映射的

创建⼀个 UserController 类,实现用户到 Spring 程序的互联互通,具体实现代码如下:

@Controller
@RequestMapping("/user") //类上面的RequestMapping可以省略
public class UserController {
    @ResponseBody//表示返回一个非静态的页面数据
    @RequestMapping("/sayhi") //方法上的RequestMapping不可以省略
    public String sayHi() {
        return "hello world";
    }
}

运行项目,访问 127.0.0.1:8080/user/sayhi 就能打印 “hello world” 的信息了

在这里插入图片描述

@RequestMapping 即可修饰类,也可以修饰方法,当修饰类和方法时,访问的地址是类 + 方法

@RequestMapping 也可以直接修饰方法,例如:

@Controller
public class UserController {
    @ResponseBody//表示返回一个非静态的页面数据
    @RequestMapping("/sayhi") //方法上的RequestMapping不可以省略
    public String sayHi() {
        return "hello world";
    }
}

在这里插入图片描述

@RequestMapping 是 post 还是 get 请求?

@RequestMapping 默认是 get 方式的请求,可以使用 Fiddler 进行抓包验证

在这里插入图片描述

如果我们想让 RequestMapping 是 post 请求,可以吗?
当然可以,我们可以显示的指定 @RequestMapping 为 post 请求

@Controller
public class UserController {
    @ResponseBody//表示返回一个非静态的页面数据
    @RequestMapping(value = "/sayhi",method = RequestMethod.POST) //方法上的RequestMapping不可以省略
    public String sayHi() {
        return "hello world";
    }
}

我们还可以使用 postman 来验证是否能进行 post 请求,并用 Fiddler 进行抓包验证

在这里插入图片描述

在这里插入图片描述

@RequestMapping 限定参数之后,将只支持某种类型的请求方式,比如只支持 get 方式请求:

@RequestMapping(value = "/sayhi",method = RequestMethod.GET)

除此之外,还可以使用 @GetMapping

@GetMapping("/sayhi")

只支持 post方式请求:

@RequestMapping(value = "/sayhi",method = RequestMethod.POST)

除此之外,还可以使用

@PostMapping("/sayhi")

获取参数

用户传递的参数形式可能存在多种,因此需要采用对应的获取参数的方式

获取单个参数

在 Spring MVC 中可以直接用方法中的参数来实现传参:

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    @RequestMapping("/m1")
    public String method_1(String name) {
        return "name:" + name;
    }
}

在这里插入图片描述

注意:在获取参数时,我们需要保证前端的参数和类中的参数保持一致,否则,将会出现null异常

例如:

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    @RequestMapping("/m1")
    public String method_1(String name) {
        if(name.equals("fl")) {
            return "name is fl";
        }
        return "name not is fl";
    }
}

在这里插入图片描述

因此,在获取单个参数时,一定要注意参数的匹配

获取多个参数

通过前端 url 中添加参数:

@ResponseBody//表示返回一个非静态的页面数据
@Controller
public class UserController {
    @RequestMapping("login")
    public String login(String username, String password) {
        return "用户名:" + username + "密码" + password;
    }
}

在这里插入图片描述

如果在 url 中不填写参数,那么它们都将是 null

在这里插入图片描述

除此之外,url 中参数的顺序并不受影响

如果,通过表单的方式传递数据,也是一样的获取,但是只会获取到表单中存在的数据,表单中不存在的数据则为 null

例如,使用 postman 模拟表单进行 get 请求

在这里插入图片描述

获取对象

并且 Spring MVC 可以自动实现参数对象的赋值

UserInfo 类:

@Data
public class UserInfo {
    private int id;
    private String name;
    private String password;
    private int age;
}

获取对象代码实现:

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    @RequestMapping("/m2")
    public String method_2(UserInfo user) {
        return user;
    }
}

在这里插入图片描述
这里会根据前端传来的参数构建一个 UserInfo 对象

这里为什么会返回一个 Json 格式的数据,明明方法中没有设置数据返回的格式?通过 Fiddler 抓包可以查看原因:

在这里插入图片描述

Spring MVC 项目会根据返回的数据类型,自动设置合适的数据返回格式,如果返回的是 HashMap 或者对象,格式就会是 Json,如果返回的是 String,那么格式将会是 text/html

例如:

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    @RequestMapping("/m2")
    public Object method_2(UserInfo user) {
        return "user : " + user;
    }
}

在这里插入图片描述

通过 Fiddler 抓包验证:

在这里插入图片描述

后端参数重命名(后端参数映射)

某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,比如前端传递了⼀个userinfo给后端,而后端又是用 user 字段来接收的,这样就会出现参数接收不到的情况,如果出现这种情况,我们就可以使用 @RequestParam 来重命名前后端的参数值

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    @RequestMapping("/m3")
    public String method_3(@RequestParam("name") String username) {
        return "username : " + username;
    }
}

在这里插入图片描述

当我们使用 @RequestParam 注解时,它有一个默认参数required,默认为 true, 表明前端必须传递给后端这个参数,否则,程序将报错

在这里插入图片描述

可以手动设置 required 为 false,表示非必传参数

public String method_3(@RequestParam(value = "name",required = false) String username) 

在这里插入图片描述

@RequestParam注意事项:
如果在参数中添加@RequestParam 注解,那么前端一定要传递此参数,否则就会报错,如果想要解决此问题,可以给@RequestParam 里面添加required = false。

@RequestBody 接收JSON对象

服务器端实现JSON数据的接收需要使用@RequestBody注解,并且在方法的参数前加上该注解

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    @RequestMapping("/m4")
    public String method_4(@RequestBody UserInfo userInfo) {
        return "用户信息 : " + userInfo;
    }
}

在这里插入图片描述

如果不加 @RequestBody,则后端接受不到 Json 数据

在这里插入图片描述

@PathVariable 获取URL中参数

对于一个url链接:URL:http(s): //xxxxxx?yyyyy,通常情况下,我们将xxxxx称之为url地址,而yyyyy称为url的参数部分

之前我们无论是获取单个参数,还是多个参数,亦或是一个对象,都是从url的参数部分获取的,但是使用 @PathVariable 注解,可以获取 url地址 中的参数

为什么要将参数伪装在 url 地址部分?
因为对于搜索引擎来说,参数部分是很容易变的,url 地址是相对稳定的,因此搜索引擎就会认为它的权重是比较高的,也就导致优先级比较高,被用户搜索到的概率更高,流量就会变高

后端实现代码:

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    @RequestMapping("/m5/{name}/{password}")
    public String method_5(@PathVariable String name, @PathVariable String password) {
        return "name" + name + " | password" + password;
    }
}

在这里插入图片描述

在编写后端代码时,url 地址中的参数需要用 {} 括起来,在前端访问时,也需要将参数写全,不能省略,否则服务器会找不到,就会报错

在这里插入图片描述

@RequestPart上传文件

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    @RequestMapping("upmig")
    public boolean upImg(Integer uid, @RequestPart("img") MultipartFile file) {
        boolean result = false;
        //保存图片到本地目录
        try {
            file.transferTo(new File("D:\\img.png"));
            result = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }
}

使用 postman 模拟上传文件:

在这里插入图片描述

在这里插入图片描述

D磁盘上确实有这个文件,说明上传成功,但却有一个问题:
如果其他人也来上传文件,由于保存时的路径和图片名称一致,后者上传的图片势必会将前者上传的图片进行覆盖。其次,如果上传的图片格式是.jpg,但却将其改为.png,因此不能将图片的后缀固定,保持原有的图片格式。最后,这里的目录也要进行修改,因为绝大部分的生产环境都是 Linux,Linux 下没有C盘,D盘的概念。

首先需要解决一下目录问题:添加三个 .yml 配置文件,它们分别用于

application-prod.yml:生产环境的配置

# 图片保存的路径
img:
  path: /root/img/

application-dev.yml:开发环境的配置文件

# 图片保存路径
img:
  path: D:/

application.yml:设置配置文件的运行平台

spring:
  profiles:
    active: dev

配置好后,我们尝试着从配置文件中读取路径的信息

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
    //从配置文件中读取图片的保存路径
    @Value("${img.path}")
    private String imgpath;

    @RequestMapping("/imgpath")
    public String sayHi1() {
        return "imgpath" + imgpath;
    }
}

在这里插入图片描述

如果在生产环境下,只需要将配置文件的运行平台从 dev 改为 prod 即可,非常方便

不同平台配置文件的命名规则:appl ication-平台. yml (properits)

其次就是需要解决图片名称的问题

常见的两种方式就是使用时间戳UUID。如果使用时间戳,确实存在两个人同时进行提交的这种可能,就会引起名称冲突,因此不可取。所以我们采用UUID

String uuid = UUID.randomUUID().toString()

最后就是解决固定后缀问题

MultipartFile 里面提供了一个方法,这个方法是能够获得原始的文件名称的

在这里插入图片描述

获取到原始文件名后,就能获取到后缀名

最终代码:

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	//从配置文件中读取图片的保存路径
    @Value("${img.path}")
    private String imgpath;

	@RequestMapping("upmig")
    public boolean upImg(Integer uid, @RequestPart("img") MultipartFile file) {
        boolean result = false;
        //1.目录
        //2.图片名称
        //3.获取上传图片的格式
        String fileName = file.getOriginalFilename();//获取图片的原始名称
        fileName = fileName.substring(fileName.lastIndexOf("."));//获取图片后缀
        fileName = UUID.randomUUID().toString() + fileName;//最终的图片名称
        //保存图片到本地目录
        try {
            file.transferTo(new File(imgpath + fileName));
            result = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }
}

在这里插入图片描述

在这里插入图片描述

获取Cookie/Session/header

获取 Request 和 Response 对象

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping("method_6")
    public void method_6(HttpServletRequest request, HttpServletResponse response) {
        String name = request.getParameter("name");
        try {
            response.getWriter().write("hello " + name);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

在这里插入图片描述

SpringMVC 是基于Severlet API 的,因此每个关于路由的方法都会隐藏 HttpServletRequest 和 HttpServletResponse,如果我们想要使用,需要显示指定

传统获取 header/cookie

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping("/method_7")
    public String param10(HttpServletResponse response, HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");//获取header
        // 获取所有 cookie 信息
        Cookie[] cookies = request.getCookies();
        for(Cookie item : cookies){
            log.info("Cookie Name: " + item.getName() + " | Cookie Value: " + item.getValue());
        }
        return "";
    }
}

在前端开发者控制台中模拟一个cookie

在这里插入图片描述

最终结果:

在这里插入图片描述

简洁的获取 Cookie—@CookieValue

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping("/cookie")
	//读取名为"fl"的cookie,把它放在 ck 中
    public String cookie(@CookieValue("fl") String ck) {
        return "cookie:" + ck;
    }
}

运行结果:

在这里插入图片描述

简洁获取 Header—@RequestHeader

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping("/header")
    public String header(@RequestHeader("User-Agent") String userAgent) {
        return "User-Agent: " + userAgent;
    }
}

运行结果:

在这里插入图片描述

传统 Session 存储和获取

SpringMVC 的 Session 存储和 Servlet 是一样的,是使用 HttpServletRequest 中获取的:

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping("/setsess")
    public String setsess(HttpServletRequest request) {
        // 获取 HttpSession 对象,参数设置为 true 表示如果没有 session 对象就创建⼀个session
        HttpSession session = request.getSession(true);
        if (session != null) {
            session.setAttribute("username", "java");
        }
        return "session 存储成功";
    }
}

存储 Session 之前:

在这里插入图片描述

存储 Session 之后:

在这里插入图片描述

读取 Session 可以使用 HttpServletRequest,如下代码所示:

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping("/sess")
    public String sess(HttpServletRequest request) {
        // 如果 session 不存在,不会⾃动创建
        HttpSession session = request.getSession(false);
        String username = "暂⽆";
        if (session != null && session.getAttribute("username") != null) {
            username = (String) session.getAttribute("username");
        }
        return "username:" + username;
    }
}

获取 Session 更简洁的方式

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping("/sess2")
    //获取session中key为username的value,并放在name中
    //required默认为true,如果session中没有对应的username,则会报错
    //将required设置为false,如果session中没有对应的username,则不会报错
    public String sess2(@SessionAttribute(value = "username", required = false) String name) {
        return "username:" + name;
    }
}

在这里插入图片描述

返回数据

返回静态页面

通过上面的内容我们发现,无论是使用哪种路由方法,在 UserController 类上,我们都加上了 @ResponseBody 注解,这个注解表示返回一个非静态页面,如果我们不加这个注解再访问会出现什么现象呢?

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping(value = "/sayhi1")
    public String sayHi2() {
        return "hello";
    }
}

在这里插入图片描述

很显然,报错了。如果现在在resource/static下添加一个hello.html页面,再将返回的内容改为"hello.html",再进行访问呢?

在这里插入图片描述

在这里插入图片描述

总结:@Controller 注解 默认返回的是一个视图(.html文件),如果不存在这个视图,则会返回404报错。如果我们想返回一个非静态的页面,需要加上 @ResponseBody 注解

@Controller:可以修饰类:表示当前类中所有方法都会返回一个非静态页面的数据。可以修饰方法:表示当前方法返回的是一个非静态页面的数据

使用 @RestController 可以达到 @Controller + @ResponseBody 的效果

在这里插入图片描述

返回 text/html

@RestController
public class UserController {
	@RequestMapping("/method_8")
    public String method_8() {
        return "<h1>Hello,HTML~</h1>";
    }
}

在这里插入图片描述

在这里插入图片描述

一般情况下,如果返回的不是 Map,或者对象,返回数据基本都是以 text/html 格式

返回 JSON 对象

@Controller
@ResponseBody//表示返回一个非静态的页面数据
public class UserController {
	@RequestMapping("/method_9")
    public HashMap<String, String> method_9  () {
        HashMap<String, String> map = new HashMap<>();
        map.put("Java", "Java Value");
        map.put("MySQL", "MySQL Value");
        map.put("Redis", "Redis Value");
        return map;
    }
}

在这里插入图片描述

在这里插入图片描述

再次强调:Spring MVC 项目会根据返回的数据类型,自动设置合适的数据返回格式,如果返回的是 HashMap 或者对象,格式就会是 Json,如果返回的是 String,那么格式将会是 text/html

请求转发或请求重定向

return 不但可以返回⼀个视图,还可以实现跳转,跳转的方式有两种:

  1. forward 是请求转发
  2. redirect:请求重定向

请求转发实现方式1

@Controller
public class TestController {
    @RequestMapping("/fw")
    public String myForword() {
        return "forward:/hello.html";
        //forward可以省略掉,然后写成
        //return "/hello.html";
        //或者写成
        //return "hello.html";
    }
}

在这里插入图片描述

成功访问到了 hello.html 页面,通过抓包查看响应

在这里插入图片描述

结果是响应中就带了 hello.html 的内容,说明是服务器帮用户实现的

请求转发实现方式2

使用 Servlet 的方式:

@Controller
public class TestController {
    @RequestMapping("/fw1")
    public void myForword1(HttpServletRequest request, HttpServletResponse response) {
        try {
            //获取请求的调度器,并填上要跳转的地址
            request.getRequestDispatcher("/hello.html").forward(request, response);
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

请求重定向实现方式1

@Controller
public class TestController {
    @RequestMapping("rd")
    public String myRedirect() {
        return "redirect:/hello.html";
    }
}

在这里插入图片描述

当我们在url框中输入 127.0.0.1:8080/rd 时,按下回车键,url 最终会变成 127.0.0.1:8080/hello.html,这个可以通过抓包进行验证

在这里插入图片描述

在这里插入图片描述

请求重定向实现方式2

使用 Servlet 的方式:

@Controller
public class TestController {
    @RequestMapping("rd1")
    public void myRedirect1(HttpServletResponse response) {
        try {
        	//进行重定向
            response.sendRedirect("/hello.html");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

请求转发或请求重定向区别

  1. 定义不同

请求转发:发生在服务端程序内部,当服务器端收到一个客户端的请求之后,会先将请求,转发给目标地址,再将目标地址返回的结果转发给客户端
请求重定向:请求重定向指的是服务器端接收到客户端的请求之后,会给客户端返回了一个临时响应头,这个临时响应头中记录了,客户端需要再次发送请求(重定向)的 URL 地址,客户端再收到了地址之后,会将请求发送到新的地址上,这就是请求重定向

  1. 跳转方不同

请求转发是服务器端的行为
而请求重定向是客户端的行为

  1. 数据共享不同

请求转发是服务器端实现的,所以整个执行流程中,客户端(浏览器端)只需要发送一次请求,因此整个交互过程中使用的都是同一个 Request 请求对象和一个 Response 响应对象,所以整个请求过程中,请求和返回的数据是共享的;而请求重定向是客户端发送两次完全不同的请求,所以两次请求中的数据是不同的。

  1. 最终 URL 地址不同

请求转发是服务器端代为请求,再将结果返回给客户端的,所以整个请求的过程中 URL 地址是不变的;而请求重定向是服务器端告诉客户端,“你去另一个地访问去”,所以浏览器会重新再发送一次请求,因此客户端最终显示的 URL 也为最终跳转的地址,而非刚开始请求的地址,所以 URL 地址发生了改变。

  1. 代码实现不同
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值