目录
(2)方式2:从Spring MVC内置对象HttpSession来获取
(3)方式3:@SessionAttribute注解把Session的值直接赋值给方法的参数
5.2 @RestController与@Controller的区别与联系
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 建立连接
@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.作用于类
@RequestMapping("/MVC")
@RestController
public class UserController {
// 路由器规则注册
@RequestMapping("/helloMVC")
public String hello(){
return "hello,Spring MVC";
}
}
此时用户要访问的URL就变为:类路径+方法路径,即ip地址:8080/MVC/helloMVC。
2.作用于方法
@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 请求中有单个参数
@RequestMapping("/oneParameter")
public String oneParameter(String name){
return "单个参数的值:" + name;
}
这里需要注意,方法的参数name必须和请求的参数的键一样。
4.2 请求中的参数类型
@RequestMapping("/int")
public String intMethod(int age){
return "单个参数的值:" + age;
}
但是当我们不传参数age时,就会发生下面的现象:
服务器报错,提示age是基本数据类型,默认值为0。但是不传参数默认值为null,null无法被转化为基本类型。因此,如果要传的参数存在空值的情况,推荐使用包装类型参数。
@RequestMapping("/integer")
public String integerMethod(Integer age){
return "单个参数的值:" + age;
}
注意:如果传其他类型的参数,可能会出现404。比如传String,就无法转化成Integer。
4.3 请求中有多个参数
@RequestMapping("/mulParameter")
public String mulParameter(String name, Integer age){
return "name:" + name + " age:" + age;
}
传递多个参数不需要指定顺序,只需要确保键和方法的参数对应。
4.4 请求中传递对象
@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 请求中参数的重命名
@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 请求中传递数组
@RequestMapping("/arr")
public String arr(String[] array){
return "arr=" + Arrays.toString(array) + ",length=" + array.length;
}
请求中传递数组有两种方式,1.参数名=值,值,值,......2.参数名=值&参数名=值&参数名=值......。
4.7 请求中传递集合
@RequestMapping("/list")
public String list(@RequestParam("list") List<Integer> list){
return "list=" + list.toString() + ",length=" + list.size();
}
请求中传递集合,必须使用@RequestParam("list")和方法中的参数进行绑定(因为多个相同的参数名的多个值默认封装为数组)。同理,如果不是必传参数,也要加上required = false。
4.8 请求中传递JSON
@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)。
public UserInfo(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
要解决该问题,就需要给出含参构造方法时,保留无参构造方法。
public UserInfo() {
}
4.9 请求中URL中的参数
这里并不是指获取?以后的参数,而是获取?前的某个路径。
@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。
@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注解了的参数对应的值。
@RequestMapping("/getCookie2")
public String getCookie2(@CookieValue("username") String username) {
// Spring中获得请求中Cookie的方式(只能获得参数列表用@CookieValue注解的Cookie)
return "username : " + username;
}
4.12 获取Session
(1)方式1:传统Servlet获取
@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来获取
@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的值直接赋值给方法的参数
@RequestMapping("/getSession3")
public String getSession3(@SessionAttribute(value = "username",required = false) String username) {
// 方式3:@SessionAttribute注解把Session的值直接赋值给username
return "username:" + username;
}
4.13 请求中获取Header
@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 返回页面
@RestController
@RequestMapping("/respMVC")
public class ResponseController {
@RequestMapping("/returnHTML")
public String returnHTML(){
return "/index.html";
}
}
这里返回页面的路径写法是从/resources/static开始,如果页面被放在该目录的下一级目录,就需要加上目录名,且路径前的/不能省略。
当使用@RestController,返回的其实不是页面,而是数据:
如果想要返回页面,就需要把@RestController替换成@Controller注解:
@Controller
@RequestMapping("/respMVC")
public class ResponseController {
@RequestMapping("/returnHTML")
public String returnHTML(){
return "/index.html";
}
}
那么这两个注解的区别和联系到底是什么?
5.2 @RestController与@Controller的区别与联系
(1)@RestController源码
@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源码
@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源码
@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
@ResponseBody
@RequestMapping("/returnData")
public String returnData(){
return "/index.html";
}
5.4 返回html代码片段
@ResponseBody
@RequestMapping("/returnHTMLPart")
public String returnHTMLPart(){
return "<h1>html片段</h1>";
}
5.5 返回json
@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
@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 设置状态码
@ResponseBody
@RequestMapping("/returnStatus")
public String returnStatus(HttpServletResponse response){
response.setStatus(404);
return "设置状态码成功";
}
需要注意,状态码的设置并不影响页面的展示。
5.8 设置Header
@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引入依赖:
<!-- 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使用
@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文件。观察反编译后的字节码文件:
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。