Spring MVC是Spring Web MVC的缩写,而Spring Web MVC 是基于Servlet API构建的原始Web 框架,从一开始就包含在Spring框架中,,他的正式名称“Spring Web MVC”是来源于其模块名称Spring MVC。
从上述定义我们可以得出两个关键信息:
MVC 是Model View Controller 的缩写,它是软件⼯程中的⼀种软件架构模式,它把软件系统分 为模型、视图和控制器三个基本部分。
模型是应用程序中,用于处理应用程序的数据逻辑部分,通常模型对象负责在数据库中存取数据。
简单来说model是不仅仅是一个数据模型,也是一个数据访问器,他可以从数据库中存储和读取数据。
而View是应用程序中显示数据的部分,通常视图是依据模型数据创建的
Controller控制器是应用程序中处理用户交互的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
MVC 是⼀种思想,⽽Spring MVC 是对MVC 思想的具体实现。
总结来说,Spring MVC 是⼀个实现了MVC 模式,并继承了Servlet API 的Web 框架。既然是Web 框架,那么当⽤户在浏览器中输⼊了url 之后,我们的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 Boot 创建项⽬相同(Spring MVC 使⽤Spring Boot 的⽅式创建), 在创建的时候选择Spring Web 就相当于创建了Spring MVC 的项⽬。
在Spring MVC 中使⽤@RequestMapping 来实现URL 路由映射,也就是浏览器连接程序的作⽤。
接下来要实现的功能是访问地址:http://localhost:8080/testcontrollersayhii,能打印hi”信息。
首先说明
@RequestMaping(“/test”)是路由注册,我们只有通过这个URL才能找到我们服务器这边的Spring框架里面的这个类
@Contorller类似于启动项,用Controller修饰这个类才会被注入到Spring中去(加载当前类),否则只会返回404。
@ResponseBody,如果只有前面两个,很遗憾还是没有办法得到正确需求,我们看一下返回的是啥
这是个历史遗留问题,因为MVC中数据返回有两大形式,一种是返回页面,一个是返回数据,但是MVC默认是返回页面,所以我们添加@ResponseBody使其返回的是一个body数据
此外我们将Controller换成Component可不可以呢?
也是可以的,但是并不建议这样,是Controller层就用@Controller修饰。
此外还有一个复合注解@RestController,他可以替代Controller+Responsebody
在另外说一下这个@RequestMaping()注解,有依据话请记住
**“实现链接的最小请求单元是方法”**换句话说,我们要实现用URl用客户端从服务器端请求一个数据,最低最低只给定方法的URL即可。换句话说我在前面的例子中是@RequestMaping()修饰类,然后又修饰类里面的方法,所以我要获取hi,所用的URL是http://127.0.0.1:8080/testcontroller/sayhi。但是我不修饰类,就修饰这个方法,也是可以获取到hi的
package com.example.demo3;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
//@Controller
//@RequestMapping("/testcontroller")
//@ResponseBody
@RestController
public class TestController {
@RequestMapping("/sayhi")
public String sayhi(){
return "hi";
}
}
此外这个注解在现在的SpringBoot 中既支持GET方式,有支持POST方式
但是如果开发中需要发送Post请求咋办呢?我们刚刚发送POST请求是通过POSTMAN发送的,那么正常访问呢?
实际上RequestMaping注解提供了参数选择
@RequestMapping(value = "/URL路径",method= RequestMethod.POST)
package com.example.demo3;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@Controller
@RequestMapping("/testcontroller")
@ResponseBody
@RestController
public class TestController {
@RequestMapping(value="/sayhi",method= RequestMethod.POST)//注意后面不是不是双引号引起来
public String sayhi(){
return "hi";
}
}
或者我们还可以使用PostMaping注解
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/testcontroller")
@ResponseBody
@RestController
public class TestController {
// @RequestMapping(value="/sayhi",method= RequestMethod.POST)//注意后面不是不是双引号引起来
@PostMapping("/sayhi")
public String sayhi(){
return "hi";
}
}
在Spring MVC 中可以直接⽤⽅法中的参数来实现传参
我们在后端将String name 作为形参传入sayhi中,那么我在get请求中输入键值对(参数),就会后端就会直接获取到,非常的便捷。
package com.example.demo3;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/testcontroller")
@ResponseBody
//@RestController
public class TestController {
// @RequestMapping(value="/sayhi",method= RequestMethod.POST)//注意后面不是不是双引号引起来
// @PostMapping("/sayhi")
@GetMapping("/sayhi")
public String sayhi(String name){
return ("hi"+name);
}
}
注意这是前端与后端的参数一定是匹配的,即URl里面的key一定和形参的名字是一样的,Spring MVC并不专注发过来几个参数,值关心有没有对于的key。
只要没有对应的key就会返回null
同时这种方式也支持多个参数,真的是太方便啦!
package com.example.demo3;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/testcontroller")
@ResponseBody
//@RestController
public class TestController {
// @RequestMapping(value="/sayhi",method= RequestMethod.POST)//注意后面不是不是双引号引起来
// @PostMapping("/sayhi")
@GetMapping("/sayhi")
public String sayhi(String name, int age){
return ("hi"+name+"他今年"+age+"岁了");
}
}
但是上述演示是有一点问题的,在Spring Boot(Spring MVC)中传参一定要传包装类而不是基本类型。就看上面的例子,我如果在浏览者URL中不输入参数,理论上应该返回两个null即“hinull他今年null岁了”但是实际上会直接报404
或者我传的key不对,也会报错
所以我们要使用Integer
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/testcontroller")
@ResponseBody
//@RestController
public class TestController {
// @RequestMapping(value="/sayhi",method= RequestMethod.POST)//注意后面不是不是双引号引起来
// @PostMapping("/sayhi")
@GetMapping("/sayhi")
public String sayhi(String name, Integer age) {
return ("hi" + name + "他今年" + age + "岁了吧啊");
}
}
//错误,因为GetMapping只能修饰字段名
@GetMapping("/sayhi")
private String name;
public String sayhi(String name){
return ("hi" + name );
}
@GetMapping("/sayhi")
public String sayhi(String name){
String name;//错误,显示name需要初始化
return ("hi" + name );
}
实际开发中传递对象是更普遍的做法。比如这样一个场景,用户注册后上传用户的参数到服务器,如果用户的参数非常多:用户名、密码、学校、性别、出生年月、实习经历等。我们不可能向之前那样用一个个的形参去接收。并且Spring MVC 可以⾃动实现参数对象的赋值。
我们在domle文件下新建一个User类
package com.example.demo3.model;
import lombok.Data;
@Data
public class User {
private String name;
private String password;
private Integer age;
}
然后在testcontrller中调用这个对象
import com.example.demo3.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/testcontroller")
@ResponseBody
//@RestController
public class TestController {
// @RequestMapping(value="/sayhi",method= RequestMethod.POST)//注意后面不是不是双引号引起来
// @PostMapping("/sayhi")
@GetMapping("/sayhi")
public String sayhi(String name,Integer age) {
return ("hi" + name + "他今年" + age + "岁了吧啊");
}
@GetMapping("/show-user")
public String showUser(User user) {
return user.toString();
}
}
我们来看这样一个场景:假如说前端和后端对时间的命名不一致,前端开始和结束时间分别命名为t1 和 t2,而后端命名为stime和etime,那么在需要修改前端参数名的前提下我们怎么处理呢?
我们就可以使⽤@RequestParam 来重命名前后端的参数值
package com.example.demo3.controller;
import com.example.demo3.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/testcontroller")
@ResponseBody
public class TestController {
@GetMapping("/show-time")
public String showTime(@RequestParam("t1") String stime ,@RequestParam("t2")String etime){
return ("开始时间" + stime + "结束时间" + etime);
}
}
但是@RequestParam这个注解存在一个默认情况
我们可以可以先看源码
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
/**
* Alias for {@link #name}.
*/
@AliasFor("name")
String value() default "";
/**
* The name of the request parameter to bind to.
* @since 4.2
*/
@AliasFor("value")
String name() default "";
/**
* Whether the parameter is required.
* Defaults to {@code true}, leading to an exception being thrown
* if the parameter is missing in the request. Switch this to
* {@code false} if you prefer a {@code null} value if the parameter is
* not present in the request.
*
Alternatively, provide a {@link #defaultValue}, which implicitly
* sets this flag to {@code false}.
*/
boolean required() default true;
/**
* The default value to use as a fallback when the request parameter is
* not provided or has an empty value.
* Supplying a default value implicitly sets {@link #required} to
* {@code false}.
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
我们会发现
boolean required() default true;
required这个默认值是true
@RequestParam这个注解中required这个属性是设置这个参数是不是一定要输入,true即必须要输入,换句话说,默认情况下如果前端URL参数中么有t1或者t2是会报错的
我们可以先去试一下用post构建一个json对象传一传看一看
会发现根本没有传过去user,那是因为
@GetMapping("/show-user")
public String showUser(User user) {
return user.toString();
}
这里接收的是一个普通对象,并不是json对象。并且也是因为实际上json对象构造好之后是存在body标签下的,并不在parameterl(参数)里面,所以这里为null
所以我们需要使用@RequestBody 接收JSON对象
@GetMapping("/show-json-user")
public String showJsonTime(@RequestBody User user){
return user.toString();
}
虽然这里成功了,但是并不严谨,因为get请求应该是没有body的,所以我们这里要使用post
@PostMapping("/show-json-user")
public String showJsonTime(@RequestBody User user){
return user.toString();
}
实际上URL在实际使用的时候,比如说要一个个输入参数多麻烦,可不可以省略key,只保留value呢?
127.0.0.1:8080/testcontroller/sayhi?name=zhangsan & password=1235456
这个URL就等价于
1270.0.1:8080/testcontroller/sayhi/zhangsan/123456
这就是在URL直接传参,我们可以看一下这样可不可以
@GetMapping("/showparameter/{name}/{age}")
public String showparameter(@PathVariable String name,@PathVariable String age){
return ("hi" + name + "他今年" + age + "岁了吧啊");
}
@RequestMapping("/show/{name}/and/{age}")
public String show_parameter(@PathVariable("name") String n,@PathVariable("age") String a){
return ("hi" + n + "他今年" + a + "岁了吧啊");
}
这里的上传是从前端角度而言的,从后端角度来说是获取前端发过来的文件,然后保存到某一个本地地址
@RequestMapping("/upfile")
public String upfile(@RequestPart("坤坤") MultipartFile file) throws IOException {
String path = "D:\\java_code\\img.png";
file.transferTo(new File(path));
return "上传成功!";
}
发现D盘java_code下确实有一个img.png文件。
但是这样是不具有解耦性的,我们永远只能保存一张图片,并且只能是图片。
我们在实际开发中用户传文件服务器接收这样的场景需要满足下面几个特性:
那么我们怎么处理呢?
看下面这段代码
@RequestMapping("/myupfile")
public String myupfile(@RequestPart("myfile") MultipartFile file) throws IOException {
String resPath = "D:\\java_code//新建文件夹1\\";//java中\\才表示一个\
String desFileName = UUID.randomUUID().toString();//uuid可以作为文件的唯一标识。也就是这里就生成了一个唯一的文件名
System.out.println(desFileName);
// String resouceFileNameAndSuffix = file.getName();
//注意getname只会获取文件名,并且获取的文件名是传输的时候的文件名,不会获取文件名和后缀
String resouceFileNameAndSuffix = file.getOriginalFilename();//注意这个获取的源文件的原始的文件名
System.out.println(resouceFileNameAndSuffix);
String desSuffix = resouceFileNameAndSuffix.substring(resouceFileNameAndSuffix.lastIndexOf("."));//新文件或者叫目标文件的后缀
System.out.println(desSuffix);
resPath = resPath+desFileName+desSuffix;//目标文件的地址
System.out.println(resPath);
file.transferTo(new File(resPath));
System.out.println("上传一次");
return "上传成功!";
}
C盘桌面准备三个文件
分别上传
控制台打印结果
D盘\java_code\新建文件夹1
注意:
file.getName()和file.getOriginalName()返回值是截然不同的,我们可以看下面这个代码
@GetMapping("/getname")
public void getname(@RequestPart("myfile") MultipartFile file) {
System.out.println(file.getName());
System.out.println(file.getOriginalFilename());
}
我们传一个文件到服务器,或执行getName()和getOriginalFilename()
传的文件本地名称叫坤坤.png,构成键值对后key的名字是myfile。
那么后端接受后控制台打印的结果是
1.传统的获取requset信息的方法
@RequestMapping("/traditionalGetRequest")
public void traditionalGetRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
//这个stringBuilder作为响应返回
response.setContentType("text/html");//这里设置了响应的content-type(content是内容的意思,也就是body标签里面),告诉浏览器,响应body里面的数据格式是html的文本格式
stringBuilder.append(request.getProtocol());//返回请求协议的名称和版本。
stringBuilder.append("
");//这就相当与在stringbulder里面加入了换行标签,因为之前将响应的格式设置为html格式,所以这个
标签输出的时候就会换行。
stringBuilder.append(request.getMethod());// 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
stringBuilder.append("
");
stringBuilder.append(request.getRequestURI());//从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。
stringBuilder.append("
");
stringBuilder.append(request.getContextPath());//指示请求上下文的请求 URL 部分,也就是返回context path
stringBuilder.append("
");
stringBuilder.append(request.getQueryString());//返回包含在路径后的请求 URL 中的查询字符串(query string),也就是返回键值对。
stringBuilder.append("
");
Enumeration<String> headNames = request.getHeaderNames();//获取head里面每一个头部(header)的名称(每一个键值对的key),如host,user-agent等
while (headNames.hasMoreElements()) {//Enumeration的迭代器,遍历每一个枚举量
String headerName = headNames.nextElement();//用headerName存每一个获取到的头部名称
stringBuilder.append(headerName + ":" + request.getHeader(headerName));//getHeader()是获取键值对中value的内容,也就是host,user-agent后面的内容
stringBuilder.append("
");
}
response.getWriter().write(stringBuilder.toString());//将stringBuilder转成String写入到响应中去
}
所以在servlet中我们用的webAPI现在依旧可以用
2.传统获取cookie的方法
@RequestMapping("/traditionalGetCookie")
public String traditionalGetCookie(HttpServletRequest request, HttpServletResponse response) throws IOException {
Cookie[] cookies = request.getCookies();
for (Cookie cookie:cookies) {
log.error(cookie.getName() + "=" + cookie.getValue());//这里testroller被SLF4修饰,可以调用日志方法,按错误的界别来打印输出cookie
}
return "get cookie";
}
简洁的获取Cookie(指定cokie名获取cookie)—@CookieValue
@RequestMapping("/traditionalGetCookie")
public String traditionalGetCookie(HttpServletRequest request, HttpServletResponse response) throws IOException {
Cookie[] cookies = request.getCookies();
for (Cookie cookie:cookies) {
log.error(cookie.getName() + "=" + cookie.getValue());
}
return "get cookie";
}