day61(Spring MVC框架基础1:接受客户端请求,@RequestMapping注解,响应正文,接受请求参数,POJO)
day61(Spring MVC框架基础:接受客户端请求,@RequestMapping注解,响应正文,接受请求参数,POJO)
1. 接收客户端的请求
1.创建Spring MVC工程
-
请参考 http://doc.canglaoshi.org/doc/idea_tomcat/index.html创建项目,首次练习的项目名称请使用springmvc01。
-
【案例目标】开发使用Spring MVC框架的项目,将其部署到Tomcat,最终,部署此项目启动Tomcat,用户在浏览器中输入指定的URL提交请求后,项目可以对此进行简单的响应
-
在pom.xml中添加spring-webmvc依赖项:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.14</version> </dependency>
-
提示:如果后续运行时提示不可识别Servlet相关类,则补充添加以下依赖项:
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
-
以上代码中的provided表示此依赖不会参与测试或部署,因为当Web项目部署到Tomcat中后,Tomcat环境会包含此依赖项
-
-
准备2个配置类
-
一个是Spring框架的配置类:
package cn.tedu.springmvc.config; import org.springframework.context.annotation.Configuration; @Configuration // 此注解不是必须的 public class SpringConfig { }
-
另一个是Spring框架的配置类:
package cn.tedu.springmvc.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration // 此注解不是必须的 @ComponentScan("cn.tedu.springmvc") // 必须配置在当前配置类,不可配置在Spring的配置类public class SpringMvcConfig implements WebMvcConfigurer { }
-
-
需要创建项目的初始化类,此类必须继承自AbstractAnnotationConfigDispatcherServletInitializer,并在此类中重写父类的3个抽象方法,返回正确的值(各方法的意义请参见以下代码中的注释):
package cn.tedu.springmvc; import cn.tedu.springmvc.config.SpringConfig; import cn.tedu.springmvc.config.SpringMvcConfig; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;/** * Spring MVC项目的初始化类 */ public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {@Override protected Class<?>[] getRootConfigClasses() { // 返回自行配置的Spring相关内容的类 return new Class[] { SpringConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { // 返回自行配置的Spring MVC相关内容的类 return new Class[] { SpringMvcConfig.class }; } @Override protected String[] getServletMappings() { // 返回哪些路径是由Spring MVC框架处理的 return new String[] { "*.do" }; } }
-
最后,创建控制器类,用于接收客户端的某个请求,并简单的响应结果:
package cn.tedu.springmvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller // 必须是@Controller,不可以是此前在Spring框架中学习到的其它组件注解 public class UserController { public UserController() { System.out.println("UserController.UserController()"); } // http://localhost:8080/springmvc01_war_exploded/login.do @RequestMapping("/login.do") @ResponseBody public String login() { return "UserController.login()"; } }
-
全部完成后,启动项目,会自动打开浏览器并显示主页,在主页的地址栏URL上补充/login.do即可实现访问,并看到结果
-
在启动过程中,你还可以在IntelliJ IDEA的控制台中看到控制器类的构造方法中输出的内容
-
关于以上案例:
- 当启动Tomcat时,会自动将项目打包并部署到Tomcat,通过自动打开的浏览器中的URL即可访问主页,在URL中有很长一段是例如 springmvc01_war_explored 这一段是不可以删除的,其它的各路径必须补充在其之后,例如 /login.do 就必须在此之后
- 当启动Tomcat时,项目一旦部署成功,就会自动创建并加载AbstractAnnotationConfigDispatcherServletInitializer的子类,即当前项目中自定义的SpringMvcInitialier,无论这个类放在哪个包中,都会自动创建并加载
- 由于会自动调用这个类中所有方法,所以会将Spring MVC框架处理的请求路径设置为*.do,并执行对 cn.tedu.springmvc 的组件扫描,进而会创建 UserController 的对象
- 由于在 UserController 中配置的方法使用了 @RequestMapping("/login.do"),则此时还将此方法与/login.do进行了绑定,以至于后续随时访问/login.do时都会执行此方法
-
关于以上案例的注意事项:
- 注意:组件扫描必须配置在Spring MVC的配置类中
- 注意:控制器类上的注解必须是@Controller,不可以是@Component、@Service、@Repository
- 各注解的作用并不是注解自身决定的,而是运行环境或框架决定的,在Spring框架中,这4个注解是完全等效的,但是,在部分版本的Spring MVC框架中并不相同
- 注意:方法返回的值应该是ASCII码字符,不推荐使用中文、中文标点符号等非ASCII码字符,否则可能显示为乱码
- 某些版本的Spring MVC默认的字符编码是ISO-8859-1,只支持ASCII字符
- 乱码的问题暂不解决
2. @RequestMapping注解
-
@RequestMapping注解的主要作用是配置请求路径与处理请求的方法的映射关系,例如将此注解添加在控制器中某个方法之前:
// http://localhost:8080/springmvc01_war_exploded/login.do @RequestMapping("/login.do") @ResponseBody public String login() { return "UserController.login()"; }
- 注意:配置的请求路径值必须是初始化类getServletMapping()方法返回值可以匹配的,例如方法返回值是 *.do,则配置的请求路径必须以.do作为后缀
-
除了方法之前,此注解还可以添加在控制器类之前,例如:
@Controller @RequestMapping("/user") public class UserController { }
-
在类上添加了此注解并配置路径后,每个方法实际映射到的请求路径都是“类上的配置的路径 + 方法上的配置的路径”,例如/user/login.do
-
通常,在开发实践中,推荐为每一个控制器类都配置此注解,以指定某个URL前缀
- 在使用@RequestMapping配置路径时,无意义的/ 会被忽略,例如在类上配置为 /user/,在方法上配置为 /login.do,则拼接的结果会是/user//login.do,实际有效值为 /user/login.do
- 必要的 / 会被补充,例如在类上配置为user,在方法上配置为login.do,实际有效值仍是 /user/login.do
- 在开发实践中,应该保持统一风格,例如:无论在类上,还是在方法上,配置的请求路径均以 / 作为第1个字符(某些特殊的URL配置例外)
-
在@RequestMapping还可以配置:
- method:请求方式
- headers:请求头
- params:请求参数
- consumes:请求文档类型
- produces:响应文档类型
-
例如,在@RequestMapping注解中,增加配置method属性,可以限制客户端的请求方式:
@RequestMapping(value = "/login.do" , method = RequestMethod.POST)@ResponseBody public String login() { return "UserController.login()"; }
- 如果按照以上代码,则/login.do路径只能通过POST方式发起请求才可以被正确的处理,如果使用其它请求方式(例如GET),则会导致HTTP的405错误。
-
在部分版本的Spring MVC框架中,响应结果不会自动添加响应头的文档类型,可通过配置@RequestMapping的produces属性显式的添加,以解决中文乱码问题
@RequestMapping(value = "/login.do" , produces="text/html; charset=utf-8") @ResponseBody public String login() { return "UserController.login()"; }
- 可以在类上进行此配置,则类中每个处理请求的方法均应用此配置
-
如果没有配置method属性,则表示可以使用任何请求方式,包括:GET,HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRAC
-
另外,Spring MVC框架还提供了@RequestMapping的相关注解,例如:
- @GetMapping –
- @PostMapping –
- @PutMapping –
- @DeleteMapping 等等
-
@GetMapping等注解是已经限制了请求方式的注解
- 以@GetMapping为例,限制了请求方式必须是GET,除此以外,使用方式与@RequestMapping完全相同
-
在开发实践中,在类的上方肯定使用@RequestMapping(其它的@XxxMapping不可以加在类上),方法上一般都使用@GetMapping、@PostMapping等注解,除非在极特殊的情况下,某些请求同时允许多种请求方式,才会在方法上使用@RequestMapping
2.小结
- 使用@RequestMapping的主要作用是配置请求路径
- @RequestMapping可以添加在类上,也可以添加在方法上,同时配置时,最终的URL是由类上的与方法上的配置值组合的
- 当需要限制请求方式为某1种时,应该在处理请求的方法上使用@GetMapping、@PostMapping等注解
- 在开发实践中,通常:
- 在类上使用@RequestMapping,配置请求路径,并配置produces以指定响应的文档类型
- 在方法上使用@GetMapping、@PostMapping等限制了请求类型的注解,配置请求路径
3.响应正文
1.概念
- @ResponseBody注解表示:响应正文
- 一旦配置为“响应正文” ,则处理请求的方法的返回值就会直接响应到客户端去
- 如果没有配置为“响应正文” ,则处理请求的方法的返回值表示“视图组件的名称” ,当方法返回后,服务器端并不会直接响应,而是根据“视图组件的名称”在服务器端找到对应的视图组件,并处理,最后,将处理后的视图响应到客户端去,这不是前后端分离的做法
2.用法
- @ResponseBody注解可以添加在方法上,则仅作用于当前方法,也可以添加在类上,则作用于当前类的所有方法
- 在Spring MVC框架中,还提供了@RestController注解,它同时具有@Controller和@ResponseBody注解的效果,所以,在响应正文的控制器上,只需要使用@RestController即可,不必再添加@Controller和@ResponseBody注解。
- Spring MVC内置了一系列的转换器(Converter),用于将方法的返回值转换为响应到客户端的数据(并补充其它必要数据),并且,SpringMVC会根据方法的返回值不同,自动选取某个转换器,例如,当方法的返回值是String时,会自动使用StringHttpMessageConverter,这个转换器的特点就是直接将方法返回的字符串作为响应的正文,并且,在许多版本的Spring MVC框架中,其默认的响应文档的字符集是ISO-8859-1,所以在在许多版本的Spring MVC中响应String正文时默认不支持非ASCII字符(例如中文)
3.响应JSON格式的正文
-
在开发实践中,不会使用String作为处理请求的方法的返回值类型,主要是因为普通的字符串不足以清楚的表现多项数据,通常建议使用XML或JSON语法来组织数据
-
主流的做法是向客户端响应JSON格式的字符串,需要在项目中添加jackson-databind的依赖项:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency>
-
以上jackson-databind依赖项中也有一个转换器,当SpringMVC调用的处理请求的方法的返回值是Spring MVC没有匹配的默认转换器时,会自动使用jackson-databind的转换器,而jackson-databind转换器就会解析方法的返回值,并将其处理为JSON格式的字符串,在响应头中将Content-Type设置为application/json
-
注意:在Spring MVC项目中,还需要在Spring MVC的配置类上添加@EnableWebMvc注解,否则响应时将导致出现HTTP的406错误。
-
示例代码(返回值类型):
public class UserVO { private String username; private String password; private String email; // 请自行补充以上3个属性的Setter & Getter }
-
注意:jackson-databind会自动调用属性的Setter / Getter方法
// http://localhost:8080/springmvc01_war_exploded/user/info.do@GetMapping("/info.do") public UserVO info() { UserVO userVO = new UserVO(); userVO.setUsername("chengheng"); userVO.setPassword("1234567890"); userVO.setEmail("chengheng@qq.com"); return userVO; }
-
示例代码(Spring MVC配置类):
@Configuration @EnableWebMvc @ComponentScan("cn.tedu.springmvc")Spring的配置类public class SpringMvcConfig implements WebMvcConfigurer { }
4.小结
- 响应正文的定义:将处理请求的方法的返回值作为响应到客户端的正文数据
- 响应正文的做法:
- 在方法上添加@ResponseBody
- 或,在类上添加@ResponseBody
- 或,在类上使用@RestController取代@Controller
- 响应正文的格式:响应的结果通常需要包含多项数据,响应1个字符串并不便于表示这些数据,通常响应JSON格式的字符串
- 响应正文的原理:Spring MVC内置了一系列的转换器(Converter),根据处理请求的方法的返回值不同,自动选取某个转换器,将方法的返回值转换为响应到客户端的数据,当方法的返回值没有匹配的默认转换器时,会自动使用jackson-databind的转换器
- 当需要响应JSON格式的正文时,你需要:–
- 添加jackson-databind依赖 –
- 在Spring MVC配置类上添加@EnableWebMvc注解–
- 自定义类,作为处理请求的方法的返回值类型 –
- 类的属性必须添加Setter & Getter –
- 使得处理请求的方法是响应正文的
4. 接收请求参数
-
在Spring MVC中,当需要接收客户端的请求参数时,只需要将各参数直接声明为处理请求的方法的参数即可,例如:
// /user/reg.do?username=root&password=123456&age=25 @RequestMapping("/reg.do") public String reg(String username, String password, Integer age){System.out.println("username = " + username + " , password = " + password + " , age = " + age); return "OK"; }
-
在声明参数时,你可以将参数声明为你期望的数据类型,Spring会自动的执行类型转换
- 经过网络传输得到的数据,最原始的类型都是String
- Spring会自动尝试转换类型,通常,类型必须是基础数据类型及其包装类、常用类型(例如String等)、Spring定义的类型等
- 如果自动转换类型失败,会抛出相应的异常
-
如果客户端提交的请求中根本没有匹配名称的参数,则以上获取到的值将是null
-
如果客户端仅提交了参数名称,却没有值,则以上获取到的值将是""(长度为0的字符串)
-
如果客户端提交了匹配名称的参数,并且值是有效的,则可以获取到值
-
以上名称应该是由服务器端决定的,客户端需要根据以上名称来提交请求参数
-
当有必要的情况下,可以在以上各参数的声明之前添加@RequestParam注解,其作用主要有:
- 配置name属性:客户端将按照此配置的值提交请求参数,而不再是根据方法的参数名称来提交请求参数
- 配置required属性:是否要求客户端必须提交此请求参数,默认为true,如果不提交,则出现400错误,当设置为false时,如果不提交,则服务器端将此参数值视为null
- 配置defaultValue属性:配置此请求参数的默认值,当客户端没有提交此请求参数时,视为此值
-
如果需要客户端提交的请求参数较多,可以将这些参数封装为自定义的数据类型,并将自定义的数据类型作为处理方法的参数即可,例如:
public class UserRegDTO { private String username; private String password; private Integer age; // 生成Setters & Getters // 生成toString(),为了方便查看数据 }
-
在控制器中:
// /user/reg.do?username=root&password=123456&age=25 @RequestMapping("/reg.do") public String reg(UserRegDTO userRegDTO) { System.out.println(userRegDTO); return "OK"; }
-
你也可以将多个请求参数区分开来,一部分直接声明为处理请求的方法的参数,另一部分封装起来。
-
理论上来说,由于一个个的声明请求参数更加简单并且直观,所以,当请求参数数量非常少时,应该使用这种做法,当参数较多,或参数数量可能调整(例如需求变化引起的调整),则应该使用封装的数据类型
-
在开发实践中,考虑到需要使用到的其它框架的特性,使用封装的做法更为常见
2.小结
- 你可以将请求参数一个个的声明为处理请求方法的参数,也可以将多个参数封装到一个自定义类中,使用自定义类作为处理请求的方法的参数,Spring MVC框架会自动接收客户端提交的请求参数,并用于调用你编写的处理请求的方法
- 在大部分情况下,推荐使用将参数封装到自定义类的做法
- 你需要保证非String类型的参数是Spring框架可以成功自动转换类型的,或者,对转换失败有进一步的处理,或可以接受转换失败带来的后果
5. 课后阅读
1. 关于POJO
- POJO:Plain Ordinary Java Object,即:普通Java对象
- 所有用于封装属性的类型都可以统称为POJO
- 常见的POJO的类名后缀有:BO、DO、VO、DTO等,不同的后缀表示不同的意义,例如:
- VO = Value Object / View Object 通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要.
- DTO = Data Transfer Object
- 所有POJO类都应该遵循以下规范:
- 实现Serializable接口 –
- 所有属性都是私有的 –
- 所有属性都有规范的Setter & Getter
- 由开发工具生成的,即是规范的
- 规范的重写了hashCode()和equals()
- 2个类型相同、所有属性值都相同的对象,equals()应该返回true,否则,返回false
- 2个类型相同、所有属性值都相同的对象,hashCode()返回值应该相同,否则,不同
- 由开发工具生成的,即是规范的,不同开发工具生成的方法源码可能不同,这不重要
- 通常,应该重写toString()输出每个属性的值
- 所有POJO类的类名后缀:
- 在一个项目中,哪些情景下使用哪种后缀并没有统一的规定,通常是各项目内部决定
- 注意:在为封装属性的类进行命名时,以上BO、DO、VO、DTO等这些后缀的每一个字母都应该是大写的!
- 参考资料:在《阿里巴巴Java开发手册》中要求POJO类名后缀的每个字母都是大写的
- 【强制】类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:DO / BO / DTO / VO / AO
- 正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
- 反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
- 【强制】类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:DO / BO / DTO / VO / AO
- 参考资料:在《阿里巴巴Java开发手册》中要求不允许使用POJO作为类名后缀
- 领域模型命名规约
- 1) 数据对象:xxxDO,xxx 即为数据表名。
- 2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
- 3) 展示对象:xxxVO,xxx 一般为网页名称。
- 4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
- 参考资料:在《阿里巴巴Java开发手册》中要求所有POJO类的属性都不要设置默认值
- 定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
- 反例:POJO 类的 gmtCreate 默认值为 new Date();但是这个属性在数据提取时并没有置入具 体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。
- 定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
- 参考资料:在《阿里巴巴Java开发手册》中提供了使用类名后缀的参考,但不是强制约定
- 分层领域模型规约:
- DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
- DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
- BO(Business Object):业务对象。由 Service 层输出的封装业务逻辑的对象。
- AO(Application Object):应用对象。在 Web 层与 Service 层之间抽象的复用对象模型, 极为贴近展示层,复用度不高。
- VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
- Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止 使用 Map 类来传输。
- 分层领域模型规约:
2.VO、DTO、Entity的区别
1.含义
1、entity 里的每一个字段,与数据库相对应,
2、vo 里的每一个字段,是和你前台 html 页面相对应,
3、dto 这是用来转换从 entity 到 vo,或者从 vo 到 entity 的中间的东西 。(DTO中拥有的字段应该是entity中或者是vo中的一个子集)
2.举个例子:
你的html页面上有三个字段,name,pass,age
你的数据库表里,有两个字段,name,pass , 注意没有 age。
而你的 vo 里,就应该有下面三个成员变量 ,因为对应 html 页面上三个字段 。
3.举个例子:
业务经理让你做这样一个业务“年龄大于 20 的才能存入数据库,这个时候,你就要用到 dto 了,
1)你要先从页面上拿到 vo,然后判断 vo 中的 age 是不是大于 20。
2)如果大于 20,就把 vo 中的 name 和 pass 拿出来,放到 dto 中。
3)然后在把 dto 中的 name 和 pass 原封不动的给 entity,然后根据 entity 的值,在传入数据库。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!