初始JavaEE篇 —— Spring Web MVC入门(上)
找往期文章包括但不限于本期文章中不懂的知识点:
个人主页:我要学编程程(ಥ_ಥ)-CSDN博客
所属专栏:JavaEE
目录
@RequestMappingg 注解介绍
Postman的介绍与使用
PostMapping 与 GetMapping 注解
构造并接收请求
接收简单参数
接收对象
接收数组
接收集合
接收JSON数据
JSON的介绍
JSON字符串与Java对象互转
从URL中获取路径参数(接收路径参数)
接收文件(上传文件)
获取Cookie/Session
获取Cookie
Session存储与获取
获取Header
知道如何去创建SpringBoot项目之后,我们就可以开始学习Spring Web MVC,其主要是学习如何使用 Spring 框架来构建 Web 应用程序,尤其是遵循 MVC(Model-View-Controller)设计模式。但是现在开发都是前后端分离的,因此MVC设计模式也算是有点老了,因此我们的侧重学习点是Web。Spring Web MVC 是 Spring Framework 的一部分,而SpringBoot是对Spring Framework的一个封装,因此后续学习Spring Web MVC 都是创建的SpringBoot项目,其包含了Spring Web MVC。Web的学习主要是分为两大核心:请求 与 响应。
前面在快速上手SpringBoot中,我们访问HelloWorld时,需要先在浏览器的地址栏中输入URL,这就是一个请求,虽然在客户端很简单,但是在服务端的编写是需要再普通类和方法上加上注解,我们就先来学习一下注解。
@RequestMappingg 注解介绍
RequestMapping 翻译过来的意思是 请求映射,但更常见的称呼是路径映射,即通过浏览器来访问时,其对应的资源路径是啥。如下所示:
路径映射既可以映射相应的类,也可以映射到相应的方法。上面只是映射到相应的方法,下面来演示一下映射到相应的类:
@RestController
@RequestMapping("/HelloController") // 映射类
public class HelloController {@RequestMapping("/hello") // 映射方法public String hello() {return "Hello World";}
}
注意:
1、如果我们此时还是去访问hello的话,就会出现下面这样的错误:
右下方有提示:Not Found 以及 404 都是在告诉我们资源不存在,也就是客户端错误。
2、我们最终访问的都是具体的方法,而不是类,因此我们在输入URL时,都得输入对应的方法所在的路径映射(如果所在的类也有的话,也得加上)。
3、在日常开发中,我们都会为类以及方法都加上对应的路径映射。
4、最终的资源路径一定要是唯一的才行,不能出现一个资源路径对应着多份资源,这是错误的。但有例外的情况:
@RestController
@RequestMapping("/HelloController") // 映射类
public class HelloController {@RequestMapping("/hello") // 映射方法public String hello() {return "Hello World";}
}@RequestMapping("/HelloController") // 映射类
class HelloController2 {@RequestMapping("/hello") // 映射方法public String hello() {return "Hello World";}
}
虽然同一个资源路径对应着多份资源,但是这里 HelloController2 并不会在公开出去。带有@RestController注解的类会被 Spring 扫描并注册为控制器,简单理解就是带有该注解的类会被Spring看成正规军,正规军中的所有人必须配备一套装备,而备用军可以存在多个人公用一套装备的情况。这里的路径映射唯一是指在带有@RestController注解的类中。还有一种特殊情况:请求的方式不同,资源路径相同时没关系的。例如,一个是 GET请求,一个是 POST请求。
5、这里的路径映射可以是多层路径,且最前面的斜杠可以去掉,但是后面的不行。如下所示:
@RequestMapping("UserController")
@RestController
public class UserController {@RequestMapping("User/Admin")public String user() {return "我要学编程,你好!";}
}
虽然路径映射在第一层不需要我们自己加上斜杠,但是在为了规范我们还是需要加上。虽然路径映射中可以带有中文和空白字符,但同样开发规范是不允许的。
Postman的介绍与使用
当前最为主流的开发模式:前后端分离。在这种模式下,前端技术人员基于"接口文档",开发前端程序;后端技术人员也基于"接口文档",开发后端程序。由于前后端分离,对我们后端技术人员来讲,在开发过程中,是没有前端页面的,那我们怎么测试自己所开发的程序呢?
方式1:像前面那样,直接使用浏览器。在浏览器中输入地址,测试后端程序
弊端:在浏览器地址栏中输入地址这种方式都是GET请求,如何我们要用到POST请求怎么办呢?要解决POST请求,需要程序员自己编写前端代码(比较麻烦)
方式2:使用专业的接口测试工具(Postman工具)
下载链接:Download Postman | Get Started for Free
下载好了之后,我们就可以来简单使用了:
1、创建仓库:
通过上面的方式就是已经创建了一个新的空白仓库了。
2、模拟请求并发送:
3、接收响应:
如果我们想要保存上述的请求话,可以直接点击右上方的save保存到某个仓库中,或者在创建新仓库时直接创建新的请求:
总之创建请求的方式有很多,随意选择一种即可。
PostMapping 与 GetMapping 注解
上述发送请求与接收响应都是最基础的。我们也可以来测试RequestMapping支持啥样的请求。
由此可见:RequestMapping既支持GET请求,也是支持POST请求的。那可以限制吗?可以的。
// 限制了s1只支持POST请求@RequestMapping(value = "/s1",method = RequestMethod.POST)public String s1() {return "hello s1";}// 限制了s2只支持GET请求@RequestMapping(value = "/s2",method = RequestMethod.GET)public String s2() {return "hello s2";}
先构造出上面两个方法,我们再使用Postman来构造请求并发送,观察其响应。
同理,我们对s2构造POST请求时,也会出现上述的情况。
限制方法的请求除了上述使用method参数来限制之外,还可以使用 GetMapping 注解 PostMapping 注解:
// 只支持GET请求@GetMapping("/s1")public String s1() {return "hello s1";}// 只支持POST请求@PostMapping("/s2")public String s2() {return "hello s2";}
这里就不再演示效果了。
注意:请求方法的分类,我们在前面学习HTTP请求时,已经学习完了,虽然有好几种,但在日常的开发中,我们只会使用 GET 和 POST。因此这里演示也只是使用这两种。
构造并接收请求
我们在访问的各种资源时,就是在发送不同的请求,有些请求是需要参数的,因此我们接下来就是学习如何接收不同的参数。
接收简单参数
我们先来看接收一个参数的情况:
@RestController
@RequestMapping("/Request")
public class RequestController {@RequestMapping("/r1")public String r1(String query_string) {return "接收的参数为:"+query_string;}
}
上述图中虽然传输了一个参数,但是这个参数并未被我们的代码给接收到,这是因为我们在构造是的参数和代码参数列表中的参数名称并不一致,只有当我们的参数名称一致时,传递的参数也是有效参数。
上面是一种参数的情况,可能会出现多个参数。
@RequestMapping("/r2")public String r2(String userName, String password) {return "用户名:"+userName+" 密码:"+password;}
上述这种方式虽然可以传递多个参数,但是在有些场合下,会被认为是不安全的传输。如果在传输用户账户的过程中,采用这种方式可能被分专业人员认为这是不安全的,因为我们把密码已经暴露出去了。但是我们学习了HTTPS之后,就会明白安不安全是看在信息被别人截取的情况下,别人是否能够知道里面的信息,即是否为加密传输。
上述在地址栏中的传输信息就是典型的GET请求,而在正文中的传输信息就是典型的POST请求
注意:
1、客户端在传输参数时,要符合Java的语法规范,否则就会发生客户端错误。例如,需要的参数是一个 Integer 类型,而客户端在传输时,传递的是一个 String 类型,这就会发生客户端错误。
2、如果我们的代码在接收参数时,是基本数据类型的话,在面对数据类型传输不符合语法与传输正常时,都是一样的结果。但是在面对不传输参数时,基本数据类型对应的代码就会报错。因为当我们不传参数时,默认是赋值null,而null对应的是对象,在基本类型看来,需要进行解包操作,而对null进行解包就会报错。如下所示:
@RequestMapping("/r3")public String r3(int number) {return "接收的参数为:"+number;}@RequestMapping("/r4")public String r4(Integer number) {return "接收的参数为:"+number;}
但是也有一种例外,对于boolean类型的数据来说, 即使是基本数据类型,不传参数,也是会有默认值false的,而不是null,这是Spring框架本身做的特殊处理。
接收对象
参数到后面可能越来越多,不可能直接传输参数,我们就需要封装成对象。例如,在登录某个网站时,用户在前端界面上输入了相关信息(可能有非常多的信息),这时传输到后端时,不会用一个一个的参数来接收,而是通过封装成对象来接收(前端先封装成对象了)。
创建一个UserInfo类:
public class UserInfo {// 属性private String name;private String gender;private Integer age;// getter 与 setter 方法public String getName() {return name;}public String getGender() {return gender;}public Integer getAge() {return age;}public void setName(String name) {this.name = name;}public void setGender(String gender) {this.gender = gender;}public void setAge(Integer age) {this.age = age;}// 重写toString方法@Overridepublic String toString() {return "UserInfo{" +"name='" + name + '\'' +", gender='" + gender + '\'' +", age=" + age +'}';}
}
@RequestMapping("/r5")public String r5(UserInfo userInfo) {return "接收的参数为:"+userInfo;}
在Postman的界面就只需要将构造参数即可,Postman会自动将这些参数构造成一个对象。这就和用户只需要输入对应的参数即可,前端的逻辑会将这些参数构造成一个对象。
注意:这些参数的名称要和后端代码的属性的名称一致。
既然每次在传输时,参数要前后端保持一致的话,那有没有什么办法将参数重命名呢?前端只需要输入简单的首字母,传输到后端时,再对传输的参数重命名即可,这样就简化了前端传输的繁琐。
@RequestMapping("/r6")public String r6(String query_string) {return "接收的参数为:"+query_string;}// 通过 @RequestParam("q")注解 将 q 赋值给 query_string@RequestMapping("/r7")public String r7(@RequestParam("q") String query_string) {return "接收的参数为:"+query_string;}
对于 r6 来说,前端的参数名称只能是 query_string,但是对于 r7 来说,前端的参数可以是 q,后端在接收时,会将 q 赋值给 query_string,这就简化了前端,也同时提高了后端代码的可读性。
但同时也有一个弊端:对于 r6 来说,当前端传输的参数不是 query_string 时,最终返回的值是 null,而对于 r7 来说,如果前端参数名称与 @RequestParam("q") 中的 q不同时,就会报错:客户端错误,也就是说加上这个注解之后,参数就是必传参数了。
如果想要修改为非必传参数也是可以的。
@RequestMapping("/r7")
public String r7(@RequestParam(value = "q",required = false) String query_string) {return "接收的参数为:"+query_string;
}
接收数组
@RequestMapping("/r8")public String r8(String[] str) {return "接收的参数为:"+ Arrays.toString(str);}
传输数组参数时,有两种方式:
1、使用多个相同参数的名称传递:
2、 一个参数中传输多个值:
接收集合
@RequestMapping("/r9") public String r9(List<String> list) {return "接收的参数为:"+list;}
如果我们也是按照传递数组的方式去传递集合的话,服务端就会报错。
IDEA中的错误日志如下:
使用传输数组的方式传输集合时,默认就是数组,而不是集合,我们得使用参数重命名的方式来修改:
@RequestMapping("/r9")public String r9(@RequestParam List<String> list) { // 将数组映射到Listreturn "接收的参数为:"+list;}
接收JSON数据
JSON的介绍
首先得知道什么是JSON?简单来说,JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,用于数据的序列化和传输。它易于阅读和编写,同时也易于机器解析和生成,广泛应用于网络应用程序之间进行数据交互。
JSON的语法非常简单,主要有两种结构组成:数组和对象。
对象由大括号 {} 包围,内部是键值对组成,键与值之间采用 冒号 分隔,键值对之间采用 逗号 分隔。键必须是字符串,字符串需要用双引号引起来。
{"key1": value1,"key2": value2
}
数组由方括号 []
包围,内部包含多个值,值之间用逗号分隔。
数组中的元素可以是任何类型:字符串、数字、布尔值、数组、对象或 null。
[value1,value2,value3
]
下面就是一个JSON数据:
{"name": "我要学编程","age": 20,"gender": "男"
}
我们在书写JSON数据时,可以使用 在线的JSON格式化工具 来校验和书写:
这种工具直接在浏览器中搜索即可:JSON中文网等。
JSON字符串与Java对象互转
JSON本质上是一个字符串,通过文本来存储和描述数据。我们也可以通过一些依赖将其转为Java中的对象,也可以将Java中的对象转换为JSON数据。由于SpringBoot项目中已经集成了JSON转换工具,因此我们可以直接使用,而不需要导入依赖。
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.54</version>
</dependency>
上述只是JSON数据与Java对象互转的一个依赖,除此之外还有 Jackson、Gson、FastJson。而SpringBoot项目中集成的就是Jackson。
下面就来演示一下JSON与Java对象的互转:
public class JSONUtils {public static void main(String[] args) throws JsonProcessingException {UserInfo userInfo = new UserInfo();userInfo.setName("我要学编程");userInfo.setAge(20);userInfo.setGender("男");ObjectMapper objectMapper = new ObjectMapper();// Java对象转换为JSON数据:writeValueAsString(Java对象)方法String json = objectMapper.writeValueAsString(userInfo);System.out.println(json);// 将JSON数据转换为Java对象:readValue(JSON数据,该类的 Class 对象)方法UserInfo u1 = objectMapper.readValue(json, UserInfo.class);System.out.println(u1);}
}
结果:
Jackson是使用ObjectMapper对象提供的两个方法,可以完成对象和JSON字符串的互转。writeValueAsString:把对象转为JSON字符串,readValue:把字符串转为对象。
注意:不同依赖对于JSON与Java对象互转的方法不一样,上面演示的是SpringBoot项目中集成的依赖:Jackson。
上述都只是在学习JSON数据,以及在Java中怎么实现JSON与Java对象的互转,下面我们来学习如何传递JSON数据。
@RequestMapping("/r10")public String r10(@RequestBody UserInfo userInfo) {return userInfo.toString();}
后端在接收JSON数据时,并不是真的接收JSON数据,而是接收Spring已经给我们转换好的Java中的对象,我们就得使用注解 @RequestBody 标识需要转换为哪个对象才行。
注意:虽然Spring将转换的细节都封装好了,但我们还是需要了解的。
1、ObjectMapper对象在调用readValue方法,将JSON数据转换为对象时,该对象所对应的类一定要有无参的构造方法和getter方法。因为在转换为对象时,是先通过无参的构造方法创建出一个对象,在通过setter方法给对象的属性赋值。因此如果该类没有无参的构造方法,最终就会报错。但是由于反射机制的存在,即使我们不提供setter方法,通过getter方法也是可以转换成功的。因此需要无参的构造方法来创建对象,再使用反射机制通过getter方法来给对象的属性赋值(或者使用setter方法来赋值给对象的属性)。
2、我们也可以让Jackson不使用无参的构造方法来创建对象,这就需要使用注解来告诉Jackson使用带参数的构造方法。
上述的所有注意事项都是针对Jackson的,不一定适用于其他的依赖。
从URL中获取路径参数(接收路径参数)
前端在传递参数时,有很多种方式。可以通过URL传输,也可以通过Body正文传输,甚至还可以手动添加到header中。我们前面学习的接收参数都是通过查询字符串来传输的,但是有些参数是通过URL中不是查询字符串的部分来传输的,而是通过路径参数来传输的。例如,下面这种URL:
https://example.com/users/123
这里的123可能就是用户ID信息,CSDN的个人主页也是采用的这种方式:
我要学编程(ಥ_ಥ)-CSDN博客https://blog.csdn.net/2301_80854132 这个是我的个人主页,后面的数字就是我的个人ID。我们现在就是想要来获取这个参数。
@RequestMapping("/r11/{id}")public String r11(@PathVariable Integer id) {return "接收的参数为:"+id;}
当我们在Postman中尝试去访问该方法时,就需要输入对应的URL,前面是 r11,后面是的 id 只能是数字。如下所示:
注意:URL中 r11/{id} 中的 id 就是属于路径参数。路径参数的名称是可以随意设置的,这里我们只是设置为了id,也可以设置为其他的,只要保持方法的参数列表中的名称与路径参数的名称一致即可。
上述只是一个路径参数的接收,也可以接收多个路径参数。
@RequestMapping("/r12/{id}/{user}")public String r12(@PathVariable Integer id, @PathVariable String user) {return "接收的参数为:"+id +": "+user;}
注意:如果有多个参数,那么需要使用多个注解对参数进行绑定。
除此之外,我们还可以对参数进行重命名:直接注解后面绑定原始的参数名称即可。
@RequestMapping("/r13/{id}/{xxx}")public String r13(@PathVariable Integer id, @PathVariable("xxx") String user) {return "接收的参数为:"+id +": "+user;}
注意:这里一定不能设置成非必传参数。这个代码的目的是为了获取URL中的参数,那么前端一定会传递参数,退一万步来说,即使我们设置成了非必传参数的话,在实际传递的过程中,并未传输参数,那么最终就会404,因为URL中所对应的参数是需要{id} 和 {xxx}的,我们不传就会找不到。当然有一种情况是例外:我们在输入请求路径时,刚好有一个是 /r13/{id},这样即使最终没有传递参数,也会自动找到该路径,但是这是画蛇添足了。既然有了单独的 /r13/{id} 来接收了id了,为什么还要将 {xxx} 设置成非必传呢?可以不需要设置成非必传呀!当匹配不到 {xxx} 时,就会匹配另一个呀!所以我们不用也不要将其设置成非必传参数。
接收文件(上传文件)
在现在最火的AI时代,我们有很多问题都可以直接去问AI,而不是选择以往的方式去浏览器中搜索了,AI的处理能力还是很强的。当我们想要阅读一本书籍时,但由于时间关系没有充分的时间去细致的阅读,我们就可以将该书的电子版上传给AI,让其给我们简述本书的大概内容即可。这个过程中就涉及了后端接收文件。
@RequestMapping("/r14")public String r14(@RequestPart("file") MultipartFile file) {// 获取文件名称String fileName = file.getOriginalFilename();return "接收的文件名称为:"+fileName;}
我们也可以将文件上传到指定位置(服务器专门用来存放文件的位置) ,像百度网盘,阿里云盘,我们通常会将一些重要的图片或者视频保存在里面。这里就是将用户上传的文件保存到本地服务器上。
@RequestMapping("/r15")public String r15(@RequestPart("file") MultipartFile file) throws IOException {// 获取文件名称String fileName = file.getOriginalFilename();// 上传文件到指定路径(D盘的temp目录下)file.transferTo(new File("D:\\temp\\"+fileName));System.out.println("文件上传成功"); // 这是输出在控制台的日志上return "接收的文件名称为:"+fileName;}
获取Cookie/Session
由于HTTP协议是无状态协议("无状态"是指在HTTP协议中,每一次请求都是独立的,服务器不会记录任何关于客户端请求的状态或信息。也就是说,服务器不会保存之前请求的信息,每次请求都被视为一个全新的请求。每个HTTP请求都是独立的,它们之间没有任何关联。),我们就需要通过其他的一些机制来实现请求之间存在关联。例如,当我登录一个网站之后,进行其他的查询搜索,这个登录网站是一个HTTP请求,查询搜索是另一个HTTP请求,对于用户来说,登录之后就可以使用了,但是对于服务器来说,两次请求是毫无关系的,那怎么判断是该用户而不是新用户呢?
用户通过浏览器登录成功之后,服务器就会将我的个人信息存储到本地,并创建一个Session(会话),将Session Id通过响应中的Set-Cookie返回给客户端,客户端就会将这个Cookie存储在本地,并在之后的请求中将Cookie自动附加在HTTP请求头中。这样后续服务器就可以通过Cookie中的Session Id识别出当前用户是谁,知道接下来的操作该怎么进行。
Cookie 和 Session的区别:
1、Cookie 是客户端保存用户信息的一种机制,Session是服务器保存用户信息的一种机制(通常保存在服务器的内存中)。
2、Cookie 和 Session之间主要是通过Session Id关联起来,Session Id 是Cookie 和 Session之间的桥梁。
3、虽然Cookie 和 Session 经常在一起配合使用,但也不是必须配合的。
获取Cookie
获取Cookie的方式不止一种,既可以使用原始的方式,也可以使用Spring封装之后的。
原始的方式是通过 Servlet 所提供的API 来获取 http 请求中的相关信息。我们先来看一下相关的接口:HttpServletRequest、HttpServletResponse。
我们可以在IDEA上输入这两个接口,并摁住 Ctrl + 鼠标左键,进入这个接口内部观察相关信息。
HttpServletRequest 接口的常用方法:
1、getCookies():返回当前请求中所有的Cookie,如果没有,则返回null。
2、getHeader(String name):返回指定请求头的值,以字符串的形式返回。
3、getSession(boolean create):获取当前会话,如果create为true,则在没有会话时创建一个新的会话;反之,则不创建。
4、getSession():获取当前会话,如果没有会话,则自动创建一个新的会话(相当于是对上一个方法的封装)。
5、getMethod():获取请求方法。
6、getQueryString():获取URL中的查询字符串。
7、getRequestURI():获取请求的URL(不包含查询字符串)。
8、getParameter(String name):获取请求中的参数名为name的值。
上面的只做了解即可,毕竟现在Spring都封装完成了,提供了更加方便的注解给我们使用,因此响应的相关方法,我们就不再学习了。
原始的方式
@RequestMapping("/getCookie")
public String getCookie(HttpServletRequest request) {Cookie[] cookies = request.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {System.out.println(cookie.getName()+": "+cookie.getValue());}} else {System.out.println("Cookie中没有信息~");}return "Cookie获取成功~";
}
这里的 HttpServletRequest 在我们需要使用的时候,就可以加上,不需要使用就不用加。
服务端代码编写完成并启动之后,我们就可以使用Postman来构造Cookie并发送请求给服务端。
使用Postman构造Cookie的两种方式:
1、在Header中添加Cookie的信息:
2、直接添加Cookie的信息:
添加Cookie之后,就可以通过Postman发送请求了。输出的信息就在控制台上面。
注意:使用第二种方式添加Cookie信息时,域名要与我们访问路径的域名一致才行,不然就添加不成功。
接下来,我们学习通过Spring的注解来获取:
@RequestMapping("/getCookie")
public String getCookie(@CookieValue("userName") String userName,@CookieValue("password") String password) {if (userName == null || password == null) {System.out.println("Cookie信息可能为null");} else {System.out.println(userName+": "+password);}return "Cookie获取成功~";
}
Spring框架帮我们获取了Cookie中对应key的Value值,并赋值给了参数列表中的形参。
注意:
1、使用Spring的注解去获取Cookie时,一定要有传输对应的key与Value,否则就会发生400。
当然我们也可以设置默认值:
这样当Cookie中没有对应的key时,就不会发生400了。
2、通过 使用原始的方式 与 使用Spring注解的方式 获取Cookie,我们可以发现虽然Spring给我们封装了,但是我们在获取Cookie时需要手动写一个一个的参数去获取,非常的麻烦,因此我们在获取Cookie时,还是使用原始的方式比较方便。
Session存储与获取
在客户端与服务器第一次通信的过程中,服务器会创建一个新的Session,并返回Session Id 给客户端。服务器得先创建一个Session对象:
@RequestMapping("/setSession")
public String setSession(HttpServletRequest request) {// 这里的原理是根据Session Id去查询对应的Session// 如果是无参或者参数为true,没有查询到,就返回新的Session对象// 如果参数为false,没有查询到,就会返回nullHttpSession session = request.getSession(); // 默认创建Session对象// HttpSession session = request.getSession(true); // 默认创建Session对象if (session != null) {session.setAttribute("userName", "zhangsan");session.setAttribute("password", "123456");}return "Session存储成功";
}
当我们通过Postman发送请求后,可以在Cookie中看到对应的Session Id。这个原理就是当客户端第一次与服务器通信时,服务器会创建新的Session,并将Session Id通过请求中的Set Cookie返回给客户端。
上面两种方式都可以观察到Session Id 的值。
存储了Session之后,后续就可以直接去获取Session的值了。
@RequestMapping("/getSession")
public String getSession(HttpServletRequest request) {HttpSession session = request.getSession(false);if (session != null) {return "userName: "+session.getAttribute("userName");} else {return "用户并未登录~";}
}
注意:当我们更新服务端的代码并重新启动程序之后,之前所有的Session信息都会被清除。因为Session默认是存储在内存中的,因此我们需要在访问getSession之前,先访问setSession。
除了上面的获取Session的方法之外,Spring也封装了注解给我们访问。
@RequestMapping("/getSession")
public String getSession(@SessionAttribute(value = "userName") String userName) {if (userName != null) {return "userName: "+userName;} else {return "用户并未登录~";}
}
注意:当使用Spring的注解来获取session时,如果session中并不存在注解中的value,请求就会发生错误:400。
如果想要不存在改value时,返回null的话,就需要添加参数:required = false。
@RequestMapping("/getSession")
public String getSession(@SessionAttribute(value = "userName", required = false) String userName) {if (userName != null) {return "userName: "+userName;} else {return "用户并未登录~";}
}
注意:这里的required 与 前面的 RequestParam 中的required的含义不一样。SessionAttribute 中的 required = false 是表示即使需要获取的value不存在于session的作用域(session中所有key与value组成的集合),也不会抛异常,而是赋值为null;RequestParam 中的 required = false 表示的是当前参数是非必传参数。
还可以使用Spring MVC内置的对象HttpSession来访问。
@RequestMapping("/getSession")
public String getSession(HttpSession session) {// 如果Session的作用域中不存在 key=userName 的value,就会返回null,而不是抛异常String userName = (String)session.getAttribute("userName");if (userName != null) {return "userName: "+userName;} else {return "用户并未登录~";}
}
在上述的获取Cookie 和 Session的过程中,我们发现相比于Spring封装的注解,还是原始的方式比较好用,虽然获取的步骤比较繁琐,但是至少不需要我们去刻意避免特殊情况。虽然我们程序员再使用时会注意,但是最终用户在使用时,并不会注意这些东西,如果直接抛出400的话,可能会让用户的体验变差,因此在开发中,还是使用原始的方式比较好。
获取Header
原始的获取Header的方式也是从HttpServletRequest中获取:
// 原始的方式获取Header
@RequestMapping("/getHeader")
public String getHeader(HttpServletRequest request) {// 获取HeaderString header = request.getHeader("User-Agent");return "User-Agent: "+header;
}
也可以使用Spring封装的注解来访问:
@RequestMapping("/getHeader")
public String getHeader(@RequestHeader("User-Agent") String header) {return "User-Agent: "+header;
}
我们只需要将要获取属性填入注解内部即可,然后就会将对应的值赋值给形参。
好啦!本期 初始JavaEE篇 —— Spring Web MVC入门(上)的学习之旅 就到此结束啦!我们下一期再一起学习吧!
相关文章:
初始JavaEE篇 —— Spring Web MVC入门(上)
找往期文章包括但不限于本期文章中不懂的知识点: 个人主页:我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏:JavaEE 目录 RequestMappingg 注解介绍 Postman的介绍与使用 PostMapping 与 GetMapping 注解 构造并接收请求 接收简单参数 接收对象…...
STM32的HAL库开发-通用定时器输入捕获实验
一、通用定时器输入捕获部分框图介绍 1、捕获/比较通道的输入部分(通道1) 首先设置 TIM_CCMR1的CC1S[1:0]位,设置成01,那么IC1来自于TI1,也就是说连接到TI1FP1上边。设置成10,那个IC1来自于TI2,连接到TI2FP1上。设置成…...
nodejs:express + js-mdict 网页查询英汉词典,能播放.spx 声音
向 DeepSeek R1 提问: 我想写一个Web 前端网页,后台用 nodejs js-mdict , 实现在线查询英语单词,并能播放.spx 声音文件 1. 项目结构 首先,创建一个项目目录,结构如下: mydict-app/ ├── public/ │ …...
【蓝桥杯嵌入式】5_PWM
全部代码网盘自取 链接:https://pan.baidu.com/s/1PX2NCQxnADxYBQx5CsOgPA?pwd3ii2 提取码:3ii2 1、PWM占空比可调 以往届的赛题举例 将PA6、PA7分别设置为TIM16_CH1和TIM17_CH1 打开TIM16和TIM17,并设置PWM输出模式及其频率 设置占空比初…...
ESM-IF1:从AF2的预测结构中学习逆折叠
作者研究了从蛋白质骨干原子坐标预测蛋白质序列的问题。迄今为止,机器学习解决此问题的方法一直受限于可用的实验测定蛋白质结构的数量。作者使用AlphaFold2为1200万个蛋白质序列预测的结构,从而将训练数据扩充了近三个数量级。相比现有方法,…...
kafka服务端之控制器
文章目录 概述控制器的选举与故障恢复控制器的选举故障恢复 优雅关闭分区leader的选举 概述 在Kafka集群中会有一个或多个broker,其中有一个broker会被选举为控制器(Kafka Controler),它负责管理整个集群中所有分区和副本的状态。…...
Redis双写一致性(数据库与redis数据一致性)
一 什么是双写一致性? 当修改了数据库(MySQL)中的数据,也要同时更新缓存(redis)中的数据,缓存中的数据要和数据库中的数据保持一致 双写一致性,根据业务对时间上的要求,…...
feign Api接口中注解问题:not annotated with HTTP method type (ex. GET, POST)
Bug Description 在调用Feign api时,出现如下异常: java.lang.IllegalStateException: Method PayFeignSentinelApi#getPayByOrderNo(String) not annotated with HTTPReproduciton Steps 1.启动nacos-pay-provider服务,并启动nacos-pay-c…...
开源2+1链动模式AI智能名片S2B2C商城小程序:突破流量与创意困境的新路径
摘要:本文深入剖析当前互联网行业中流量集中于巨头以及创意边际效应递减的困境,并探讨开源21链动模式AI智能名片S2B2C商城小程序在应对这些困境时所展现的独特优势与应用策略。通过对行业现状的分析以及该小程序功能特点的研究,旨在为企业在艰…...
python编程-内置函数compile(),exec(),complex(),eval()详解
1. compile() 函数 用途:将一个字符串源代码编译为字节码对象,这样可以直接被Python解释器执行,或者通过exec()或eval()函数来执行。 参数: source:一个字符串或AST(抽象语法树)对象&am…...
websocket自动重连封装
websocket自动重连封装 前端代码封装 import { ref, onUnmounted } from vue;interface WebSocketOptions {url: string;protocols?: string | string[];reconnectTimeout?: number; }class WebSocketService {private ws: WebSocket | null null;private callbacks: { [k…...
解锁C/C++:链表数据结构的奇幻之旅
目录 一、引言二、链表基础概念2.1 链表是什么2.2 链表的类型三、C 语言实现链表3.1 定义链表节点3.2 创建链表3.3 链表操作3.3.1 遍历链表3.3.2 插入节点3.3.3 删除节点3.3.4 查找节点3.4 完整示例代码四、C++ 实现链表4.1 定义链表节点类4.2 创建链表4.3 链表操作4.3.1 遍历链…...
x64、aarch64、arm与RISC-V64:详解四种处理器架构
x64、aarch64、arm与RISC-V64:详解四种处理器架构 x64架构aarch64架构ARM架构RISC-V64架构总结与展望在计算机科学领域,处理器架构是构建计算机系统的基石,它决定了计算机如何执行指令、管理内存和处理数据。x64、aarch64、arm与RISC-V64是当前主流的四种处理器架构,它们在…...
nuxt3中报错: `setInterval` should not be used on the server.
那是因为在后端渲染没有浏览器的执行环境,一些浏览器环境提供的对象和方法都无法使用,代码判断下就行。 if (import.meta.client) {setInterval(() > {}, 1000) }Import meta Nuxt API...
python编程-集合内置函数和filter(),集合常见操作
在Python中,列表、集合、字典是三种常用的数据结构,它们各自拥有一些内置函数,用于执行各种操作。 一、列表的常用内置函数 #1、append(obj): 在列表末尾添加新的对象。list_a [1, 2, 3] list_a.append(4) print(list_a) # 输出: [1,…...
三极管的截止、放大、饱和区
三极管的几个区,都有什么用: 截止区:晶体管不导通,用于开关电路的“关”状态。 放大区:晶体管用于信号放大,集电极电流与基极电流成正比。 饱和区:晶体管完全导通,用于开关电路的“…...
python爬虫--简单登录
1,使用flask框架搭建一个简易网站 后端代码app.py from flask import Flask, render_template, request, redirect, url_for, sessionapp Flask(__name__) app.secret_key 123456789 # 用于加密会话数据# 模拟用户数据库 users {user1: {password: password1}…...
苹果公司宣布正式开源 Xcode 引擎 Swift Build145
2025 年 2 月 1 日,苹果公司宣布正式开源 Xcode 引擎 Swift Build145。 Swift 是苹果公司于 2014 年推出的一种开源编程语言,用于开发 iOS、iPadOS、macOS、watchOS 和 tvOS 等平台的应用程序。 发展历程 诞生:2014 年,苹果在全球…...
齿轮减速机和平行轴减速机有何区别?
减速机是传动系统中重要的组成部分,常用的减速机有四大系列,分别是平行轴减速机、同轴减速机、直角减速机和齿轮减速机。那么大家知道齿轮减速机和平行轴减速机投什么区别吗? 齿轮减速机的轴不一定是平行的,还可能存在相交轴或交错…...
基于Hexo实现一个静态的博客网站
原文首发:https://blog.liuzijian.com/post/8iu7g5e3r6y.html 目录 引言1.初始化Hexo2.整合主题Fluid3.部署评论系统Waline4.采用Nginx部署 引言 Hexo是中国台湾开发者Charlie在2012年创建的一个开源项目,旨在提供一个简单、快速且易于扩展的静态博客生…...
MIT6.824 Lecture 1-Introduction
balance:性能和容错 Faulty tolerance: Availablity、Recoverability、NV storage(非易失性存储,比较贵)、Replication(多个数据副本) consistency: Put(key,…...
【Redis实战】投票功能
1. 前言 现在就来实践一下如何使用 Redis 来解决实际问题,市面上很多网站都提供了投票功能,比如 Stack OverFlow 以及 Reddit 网站都提供了根据文章的发布时间以及投票数计算出一个评分,然后根据这个评分进行文章的展示顺序。本文就简单演示…...
1Panel应用推荐:WordPress开源博客软件和内容管理系统
1Panel(github.com/1Panel-dev/1Panel)是一款现代化、开源的Linux服务器运维管理面板,它致力于通过开源的方式,帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用,1Panel特别开通应用商店&am…...
GGML、GGUF、GPTQ 都是啥?
GGML、GGUF和GPTQ是三种与大型语言模型(LLM)量化和优化相关的技术和格式。它们各自有不同的特点和应用场景,下面将详细解释: 1. GGML(GPT-Generated Model Language) 定义:GGML是一种专为机器学习设计的张量库,由Georgi Gerganov创建。它最初的目标是通过单一文件格式…...
MySQL主从复制原理及工作过程
一、主从复制原理 1、MySQL将数据变化记录到二进制日志中; 2、Slave将MySQL的二进制日志拷贝到Slave的中继日志中; 3、Slave将中继日志中的事件在做一次,将数据变化,反应到自身(Slave)的数据库 详细步骤&…...
Unity VideoPlayer播放视屏不清晰的一种情况
VideoPlayer的Rnder Texture可以设置Size,如果你的视屏是1920*1080那么就设置成1920*1080。 如果设置成其他分辨率比如800*600会导致视屏不清晰。...
发布:大彩科技DN系列2.8寸高性价比串口屏发布!
一、产品介绍 该产品是一款2.8寸的工业组态串口屏,采用2.8寸液晶屏,分辨率为240*320,支持电阻触摸、电容触摸、无触摸。可播放动画,带蜂鸣器,默认为RS232通讯电平,用户短接屏幕PCB上J5短接点即可切换为TTL电…...
Oh3.2项目升级到Oh5.0(鸿蒙Next)具体踩坑记录(一)
目录 1.自动修复部分 Cause: The project structure and configuration require an upgrade. Solution: 1. Use Migrate Assistant to auto-upgrade the project structure and configuration. 2. Manually upgrade the project structure and configuration by following th…...
pytest+request+yaml+allure 接口自动化测试全解析[手动写的跟AI的对比]
我手动写的:Python3:pytest+request+yaml+allure接口自动化测试_request+pytest+yaml-CSDN博客 AI写的:pytest+request+yaml+allure 接口自动化测试全解析 在当今的软件开发流程中,接口自动化测试扮演着至关重要的角色。它不仅能够提高测试效率,确保接口的稳定性和正确性…...
Redis存储⑤Redis五大数据类型之 List 和 Set。
目录 1. List 列表 1.1 List 列表常见命令 1.2 阻塞版本命令 1.3 List命令总结和内部编码 1.4 List典型使用场景 1.4.1 消息队列 1.4.2 分频道的消息队列 1.4.3 微博 Timeline 2. Set 集合 2.1 Set 集合常见命令 2.2 Set 集合间命令 2.3 Set命令小结和内部编码 2.…...
使用PyCharm进行Django项目开发环境搭建
如果在PyCharm中创建Django项目 1. 打开PyCharm,选择新建项目 2.左侧选择Django,并设置项目名称 3.查看项目解释器初始配置 4.新建应用程序 执行以下操作之一: 转到工具| 运行manage.py任务或按CtrlAltR 在打开的manage.pystartapp控制台…...
C# 综合运用介绍
.NET学习资料 .NET学习资料 .NET学习资料 C# 作为一种由微软开发的面向对象编程语言,在软件开发领域占据着重要地位。凭借其简洁、类型安全以及与.NET 框架的紧密结合等特性,C# 被广泛应用于多个领域。下面将详细介绍 C# 的综合运用。 一、C# 语言特性…...
Docker 和 Docker Compose
Docker 和 Docker Compose 是两个相关但用途不同的工具,它们在容器化应用的管理和部署中扮演不同的角色。以下是它们的核心区别: 1. 功能定位 Docker: 是一个容器化平台,用于创建、运行和管理单个容器。适用于单个容器应用的开发和测试。通过…...
文件上传到腾讯云存储、签名及设置过期时间
将文件上传到腾讯云对象存储(COS,Cloud Object Storage)可以通过腾讯云提供的 SDK 实现。以下是详细的步骤和示例代码,帮助您完成文件上传操作。 步骤 注册腾讯云账号并创建存储桶: (1)登录腾讯…...
从0开始达芬奇(6)
软件交互 就是与PR,AE软件进行交互。(这个就不多说啦) 快捷键(以下是TIM总结的常用快捷键)...
如何在Windows上使用Docker
引言 WSL2(Windows Subsystem for Linux2)是微软开发的一种技术,允许在 Windows 操作系统上运行 Linux 环境。它提供了一个兼容层,使得用户可以在 Windows 系统中直接运行 Linux 命令行工具、应用程序和开发工具,而无需…...
细胞计数专题 | 如何减少台盼蓝沉淀?
台盼蓝(Trypan Blue)是一种在生物学研究中广泛使用的染料,尤其常用于细胞活力检测。当细胞死亡时,其细胞膜会变得对台盼蓝具有通透性,染料因而能够进入细胞并与细胞内的蛋白质结合,产生染色效果。由此&…...
go流程控制
流程控制是每种编程语言控制逻辑走向和执行次序的重要部分,流程控制可以说是一门语言的“经脉”。 Go 语言中最常用的流程控制有 if 和 for,而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。 if else…...
Spring Web MVC项目的创建及使用
一、什么是Spring Web MVC? Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中,通常被称为Spring MVC。 1.1 MVC的定义 MVC 是 Model View Controller 的缩写,它是软件工程中的一种软件架构…...
RabbitMQ 从入门到精通:从工作模式到集群部署实战(四)
#作者:闫乾苓 系列前几篇: 《RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)》:link 《RabbitMQ 从入门到精通:从工作模式到集群部署实战(二)》: lin…...
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_get_options函数
声明 就在 main函数所在的 nginx.c 中: static ngx_int_t ngx_get_options(int argc, char *const *argv); 实现 static ngx_int_t ngx_get_options(int argc, char *const *argv) {u_char *p;ngx_int_t i;for (i 1; i < argc; i) {p (u_char *) argv[i]…...
TCP长连接、HTTP短轮询、HTTP长轮询、HTTP长连接、WebSocket的区别
1.TCP长连接 (1)概念:该连接属于传输层的协议。客户端和服务器之间建立连接后,不立即断开该连接,而是一直保持这个状态,以便后续数据的持续、连续传输。(2)应用场景:适合…...
在 Flownex 中创建自定义工作液
在这篇博文中,我们将了解如何在 Flownex 中为流网添加和定义一种新的流体温度相关工作材料。 Flownex 物料管理界面 在 Flownex 中使用与温度相关的流体材料时,了解其特性与温度的关系非常重要。这种了解可确保准确预测各种热条件下的流体行为࿰…...
基于Spring Boot的图书个性化推荐系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
【抽象代数】1.1. 运算及关系
集合与映射 定义1. 设 为 的子集,定义 到 的映射 : 使得 ,称 为 到 的嵌入映射。 定义2. 设 为 的子集, 为 到 的映射, 为 到 的映射,如果 ,称为的开拓, 为 的限制&…...
拥抱开源,助力创新:IBM永久免费云服务器助力开源项目腾飞
近年来,开源项目蓬勃发展,为全球科技进步做出了巨大贡献。然而,服务器成本高昂常常成为开源项目的巨大障碍。许多优秀的项目因缺乏资源而难以持续发展,甚至夭折。令人振奋的是,IBM云计算平台推出了一项重磅活动&#x…...
Windows Docker笔记-简介摘录
Docker是一个开源的容器化平台,可以帮助开发人员将应用程序与其依赖项打包在一个独立的容器中,然后在任何安装的Docker的环境中快速、可靠地运行。 几个基本概念和优势: 1. 容器 容器是一个轻量级、独立的运行环境,包含了应用程…...
threejs 建筑设计(室内设计)软件 技术调研之五 墙体生成后自动生成房间(地面)
运用threejs 开发 建筑设计(室内设计)软件 技术调研 四 墙体添加真实门窗并保持原材质 在线体验地址:http://47.96.130.245:8080/design/index.html 实现功能: 墙体材质变换后,自动根据墙体的顶点生成相应的房间 视…...
C++:string类的模拟实现
目录 1.引言 2.C模拟实现 2.1模拟实现构造函数 1)直接构造 2)拷贝构造 2.2模拟实现析构函数 2.3模拟实现其他常规函数 1)c_str函数 2)size函数 3)begin/end函数 4)reserve函数 5)re…...
UE制作2d游戏
2d免费资产: Free 2D Game Assets - CraftPix.net 需要用到PaperZD插件 官网下载后启用即可 导入png素材 然后全选 - 创建Sprite 创建 人物基类 设置弹簧臂和相机 弹簧臂设置成旋转-90 , 取消碰撞测试 设置子类Sprite 拖到场景中 绑定设置输入映射,让角色移动跳跃 神似卡拉比…...