springmvc讲解(1 )点击此处即可
我们知道springmvc最重要的两个功能①简化参数接收 ②简化数据响应
在当前,主要有两种开发模式,一是前后端分离【主要】,二是前后端混合开发。
①前后端分离模式
指将前端的界面和后端的业务逻辑通过接口分离开发的一种方式。开发人员使用不同的技术栈和框架,前端开发人员主要负责页面的呈现和用户交互,后端开发人员主要负责业务逻辑和数据存储。前后端通信通过 API 接口完成,数据格式一般使用 JSON 或 XML。前后端分离模式可以提高开发效率,同时也有助于代码重用和维护。
②前后端混合开发
指将前端和后端的代码集成在同一个项目中,共享相同的技术栈和框架。这种模式在小型项目中比较常见,可以减少学习成本和部署难度。但是,在大型项目中,这种模式会导致代码耦合性很高,维护和升级难度较大。
对于混合开发,我们就需要使用动态页面技术,动态展示Java的共享域数据!!
JSP(JavaServer Pages)是一种动态网页开发技术,它是由 Sun 公司提出的一种基于 Java 技术的 Web 页面制作技术,可以在 HTML 文件中嵌入 Java 代码,使得生成动态内容的编写更加简单。
JSP 最主要的作用是生成动态页面。它允许将 Java 代码嵌入到 HTML 页面中,以便使用 Java 进行数据库查询、处理表单数据和生成 HTML 等动态内容。另外,JSP 还可以与 Servlet 结合使用,实现更加复杂的 Web 应用程序开发。
JSP 的主要特点包括:
总之,JSP 是一种简单高效、多样化的动态网页开发技术,它可以方便地生成动态页面和与 Servlet 结合使用,是 Java Web 开发中常用的技术之一。
在这里我们访问一个地址,其返回一个jsp页面,这也是前后端混合开发。
a 、导入jsp依赖
jakarta.servlet.jsp.jstl
jakarta.servlet.jsp.jstl-api
3.0.0
b、 准备一个jsp页面 放在web-inf 下 避免外部访问
WEB-INF/views/index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
${data}
c、准备一个handler
注意:返回页面 需要视图解析器 不能添加@ResponseBody注解
@Controller
@RequestMapping("jsp")
public class JspController {
@GetMapping("data")
public String data(HttpServletRequest request){
request.setAttribute("data","cui"); //设置一个数据共享域
System.out.println("FileController.jumpJsp");
return "index"; //这里是返回一个视图函数 会加前缀和后缀 见下方
}
d、配置jsp视图解析器(配置类)
@EnableWebMvc 注解会将handlermapping 和handleradapter 放入ioc容器中 所以我们不必再加入其第三方类,同时也会给adapter加上json转换器。
配置jsp视图解析器 我们可以实现WebMvcConfigurer 类,重载其方法,WebMvcConfigurer类有很多方法,比如configureViewResolvers,我们可以重载该方法,可以很方便的配置jsp模板语言对应的前缀和后缀。
@Configuration
@ComponentScan(basePackages = "com.cky.controller")
@EnableWebMvc
public class Myconfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//配置jsp模板语言对应的前缀和后缀 是字符串拼接 注意/
registry.jsp("/WEB-INF/jsp/",".jsp");
}
}
e、初始化配置 (比如配置类 等)
package com.cky.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ConfigInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class>[] getServletConfigClasses() {
return new Class[]{Myconfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
/**
* 1、转发
* ① 方法返回值要写成字符串
* ②不能加@RequestBody方法
* ③返回字符串前面加上 forward:
*/
@GetMapping("forward")
public String forward(){
System.out.println("JspController.forward");
return "forward:/jsp/data";
}
/**
* 1、重定向
* ① 方法返回值要写成字符串
* ②不能加@RequestBody方法
* ③返回字符串前面加上 redirect:
*/
@GetMapping("redirect")
public String redirect(){
System.out.println("JspController.redirect");
return "redirect:/jsp/data";
}
/**
* 路径细节:不使用springmvc时 request,respond
* ① 转发:都是自己项目路径下的项目:不用加Application Context
* ②重定向:二次转发 可以是外部资源项目地址 要加上Application Context
* 使用Springmvc时 springmvc会为我们做一个优化
* ①转发 和上边相同
* ②重定向:会自动为我们加上自己项目路径 也不用加Application Context
* 如果要重定向到外部资源的话,就直接在redirect后 写要重定向的地址即可
*/
@GetMapping("redirectbaidu")
public String baidu(){
System.out.println("JspController.baidu");
return "redirect:https://www.baidu.com/";
}
转发时页面地址仍然是第一个 不会改变。
重定向会改变页面地址。
对于json数据,我们可以直接返回一个实体类 或者实体类集合。
handleradapter会自动帮我们解析该实体类,返回成一个可以接收的json数据。
比如我们有一个User类
package com.cky.pojo;
import lombok.Data;
@Data
public class User {
private String name;
private int age;
}
我们的handler方法
/**
* 返回json数据
* 注意要加上@ResponseBody 注解 证明我们返回的是信息体 可以让Handleradapter 帮我们转化为json数据
*/
@ResponseBody
@GetMapping("user")
public User user(){
User user = new User();
user.setAge(18);
user.setName("cui");
return user;
}
@GetMapping("userlist")
@ResponseBody
public List users(){
List userList=new ArrayList<>();
User user = new User();
user.setName("jiang");
user.setAge(23);
userList.add(user);
return userList;
}
}
可以在方法上使用 @ResponseBody
注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端。在前后端分离的项目中使用!
如果每个方法上都加了该注解,我们可以直接在类上使用。
具体来说,@ResponseBody 注解可以用来标识方法或者方法返回值,表示方法的返回值是要直接返回给客户端的数据,而不是由视图解析器来解析并渲染生成响应体(viewResolver没用)。
该注解 等同于@Controller+@ResponseBody注解
直接在类上使用
资源本身已经是可以直接拿到浏览器上使用的程度了,不需要在服务器端做任何运算、处理。典型的静态资源包括:
打包和编译部署后 图片都存在
但是在访问时 却显示404错误
解决方法:
在配置文件中添加上
@Override
//开启静态资源处理
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
为什么会报404 错误呢?
这是因为在使用springmvc时,dispatcherhandler会首先找handlermapping这个秘书,看是否有该地址对应的handler,如果有在访问其handler,如果没有会报404错误。
当我们开启了该配置之后。相当于给ceo配置了一个二秘书,首先找handlermapping这个秘书,如果没有对应handler,就会去找该项目下的静态资源。
再添加上该类之后,我们就可以访问静态资源了。
RESTful(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序和服务之间的通信。它是一种基于标准 HTTP 方法的简单和轻量级的通信协议,广泛应用于现代的Web服务开发。
通过遵循 RESTful 架构的设计原则,可以构建出易于理解、可扩展、松耦合和可重用的 Web 服务。RESTful API 的特点是简单、清晰,并且易于使用和理解,它们使用标准的 HTTP 方法和状态码进行通信,不需要额外的协议和中间件。
总而言之,RESTful 是一种基于 HTTP 和标准化的设计原则的软件架构风格,用于设计和实现可靠、可扩展和易于集成的 Web 服务和应用程序!
学习RESTful设计原则可以帮助我们更好去设计HTTP协议的API接口!!
主要解决了
1、URL
2、哪种请求方式
3、如何传递参数
这也是http请求的必要项
HTTP协议请求方式要求
REST 风格主张在项目设计、开发过程中,具体的操作符合HTTP协议定义的请求方式的语义。
操作 | 请求方式 |
---|---|
查询操作 | GET |
保存操作 | POST |
删除操作 | DELETE |
更新操作 | PUT |
URL路径风格要求
REST风格下每个资源都应该有一个唯一的标识符,例如一个 URI(统一资源标识符)或者一个 URL(统一资源定位符)。资源的标识符应该能明确地说明该资源的信息,同时也应该是可被理解和解释的!
使用URL+请求方式确定具体的动作,他也是一种标准的HTTP协议请求!
操作 | 传统风格 | REST 风格 |
---|---|---|
保存 | /CRUD/saveEmp | URL 地址:/CRUD/emp 请求方式:POST |
删除 | /CRUD/removeEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:DELETE |
更新 | /CRUD/updateEmp | URL 地址:/CRUD/emp 请求方式:PUT |
查询 | /CRUD/editEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:GET |
总结
根据接口的具体动作,选择具体的HTTP协议请求方式
路径设计从原来携带动标识,改成名词,对应资源的唯一标识即可!
含蓄,安全
使用问号键值对的方式给服务器传递数据太明显,容易被人利用来对系统进行破坏。使用 REST 风格携带数据不再需要明显的暴露数据的名称。
风格统一
URL 地址整体格式统一,从前到后始终都使用斜杠划分各个单词,用简单一致的格式表达语义。
无状态
在调用一个接口(访问、操作资源)的时候,可以不用考虑上下文,不用考虑当前状态,极大的降低了系统设计的复杂度。
严谨,规范
严格按照 HTTP1.1 协议中定义的请求方式本身的语义进行操作。
简洁,优雅
过去做增删改查操作需要设计4个不同的URL,现在一个就够了。
操作 | 传统风格 | REST 风格 |
---|---|---|
保存 | /CRUD/saveEmp | URL 地址:/CRUD/emp 请求方式:POST |
删除 | /CRUD/removeEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:DELETE |
更新 | /CRUD/updateEmp | URL 地址:/CRUD/emp 请求方式:PUT |
查询 | /CRUD/editEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:GET |
功能 | 接口和请求方式 | 请求参数 | 返回值 |
分页查询 | GET /user | page=1&size=10 | { 响应数据 } |
用户添加 | POST /user | { user 数据 } | {响应数据} |
用户详情 | GET /user/1 | 路径参数 | {响应数据} |
用户更新 | PUT /user | { user 更新数据} | {响应数据} |
用户删除 | DELETE /user/1 | 路径参数 | {响应数据} |
条件模糊 | GET /user/search | page=1&size=10&keywork=关键字 | {响应数据} |
为什么查询用户详情,就使用路径传递参数,多条件模糊查询,就使用请求参数传递?
误区:restful风格下,不是所有请求参数都是路径传递!可以使用其他方式传递!
在 RESTful API 的设计中,路径和请求参数和请求体都是用来向服务器传递信息的方式。
此外,还有一些通用的原则可以遵循:
a.准备用户实体类
package com.atguigu.pojo;
/**
* projectName: com.atguigu.pojo
* 用户实体类
*/
public class User {
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 "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
b.准备用户Controller
/**
* projectName: com.atguigu.controller
*
* description: 用户模块的控制器
*/
@RequestMapping("user")
@RestController
public class UserController {
/**
* 模拟分页查询业务接口
*/
@GetMapping
public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
@RequestParam(name = "size",required = false,defaultValue = "10")int size){
System.out.println("page = " + page + ", size = " + size);
System.out.println("分页查询业务!");
return "{'status':'ok'}";
}
/**
* 模拟用户保存业务接口
*/
@PostMapping
public Object saveUser(@RequestBody User user){
System.out.println("user = " + user);
System.out.println("用户保存业务!");
return "{'status':'ok'}";
}
/**
* 模拟用户详情业务接口
*/
@PostMapping("/{id}")
public Object detailUser(@PathVariable Integer id){
System.out.println("id = " + id);
System.out.println("用户详情业务!");
return "{'status':'ok'}";
}
/**
* 模拟用户更新业务接口
*/
@PutMapping
public Object updateUser(@RequestBody User user){
System.out.println("user = " + user);
System.out.println("用户更新业务!");
return "{'status':'ok'}";
}
/**
* 模拟条件分页查询业务接口
*/
@GetMapping("search")
public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,
@RequestParam(name = "size",required = false,defaultValue = "10")int size,
@RequestParam(name = "keyword",required= false)String keyword){
System.out.println("page = " + page + ", size = " + size + ", keyword = " + keyword);
System.out.println("条件分页查询业务!");
return "{'status':'ok'}";
}
}
开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中,合理处理异常、避免异常产生、以及对异常进行有效的调试是非常重要的。
对于异常的处理,一般分为两种方式:
@Throws
或 @ExceptionHandler
),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。站在宏观角度来看待声明式事务处理:
整个项目从架构这个层面设计的异常处理的统一机制和规范。
一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。
使用声明式异常处理,可以统一项目处理异常思路,项目更加清晰明了!
写一个类 专门用于配置全局异常
会找到对应的异常类 然后执行handler方法
有异常==》全局配置类==》找对应的精准异常handler方法
//全局配置类
@RestControllerAdvice//== @ResponseBody+@ControllerAdvice
@ExceptionHandler(ArithmeticException.class)
记得将全局异常配置类加到配置类的扫描文件中
package com.cky.controller;
import org.springframework.web.bind.annotation.*;
//全局配置类
@RestControllerAdvice//== @ResponseBody+@ControllerAdvice
public class errorController {
@ExceptionHandler(ArithmeticException.class)
public Object ArithmeticExceptionhandler(Exception e){
System.out.println("errorController.ArithmeticExceptionhandler");
return "errorController.ArithmeticExceptionhandler";
}
//如果有精准异常 则会执行精准异常
//如果没有精准异常,则会执行父异常
@ExceptionHandler(Exception.class)
public Object Exception(Exception e){
System.out.println("errorController.Exception");
return "\"errorController.Exception\"";
}
}
相似点:
不同点:
工作平台不同
拦截的范围
IOC 容器支持
1、创建拦截类
public class Process01Interceptor implements HandlerInterceptor {
// if( ! preHandler()){return;}
// 在处理请求的目标 handler 方法前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
System.out.println("Process01Interceptor.preHandle");
// 返回true:放行
// 返回false:不放行
return true;
}
// 在目标 handler 方法之后,handler报错不执行!
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);
System.out.println("Process01Interceptor.postHandle");
}
// 渲染视图之后执行(最后),一定执行!
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);
System.out.println("Process01Interceptor.afterCompletion");
}
}
2、修改配置类添加拦截器
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = {"com.atguigu.controller","com.atguigu.exceptionhandler"}) //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {
//配置jsp对应的视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//快速配置jsp模板语言对应的
registry.jsp("/WEB-INF/views/",".jsp");
}
//开启静态资源处理 <mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new Process01Interceptor());
}
}
3、配置详解:
拦截全部
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new Process01Interceptor());
}
精准配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new Process01Interceptor());
//精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
//addPathPatterns("/common/request/one") 添加拦截路径
//也支持 /* 和 /** 模糊路径。 * 任意一层字符串 ** 任意层 任意字符串
registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow");
}
排除配置
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦4、截所有Springmvc分发的请求
registry.addInterceptor(new Process01Interceptor());
//精准匹配,设置拦截器处理指定请求 路径可以设置一个或者多个,为项目下路径即可
//addPathPatterns("/common/request/one") 添加拦截路径
registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow");
//排除匹配,排除应该在匹配的范围内排除
//addPathPatterns("/common/request/one") 添加拦截路径
//excludePathPatterns("/common/request/tow"); 排除路径,排除应该在拦截的范围内
registry.addInterceptor(new Process01Interceptor())
.addPathPatterns("/common/request/one","/common/request/tow")
.excludePathPatterns("/common/request/tow");
}
4、多个拦截器执行顺序
在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。
jsr303参数校验注解 可以直接使用在我们的实体类上。它是由hibernate实现的,所以我们需要导入其对应的依赖包。
注解 | 规则 |
---|---|
@Null | 标注值必须为 null |
@NotNull | 标注值不可为 null |
@AssertTrue | 标注值必须为 true |
@AssertFalse | 标注值必须为 false |
@Min(value) | 标注值必须大于或等于 value |
@Max(value) | 标注值必须小于或等于 value |
@DecimalMin(value) | 标注值必须大于或等于 value |
@DecimalMax(value) | 标注值必须小于或等于 value |
@Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
@Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
@Past | 标注值只能用于日期型,且必须是过去的日期 |
@Future | 标注值只能用于日期型,且必须是将来的日期 |
@Pattern(value) | 标注值必须符合指定的正则表达式 |
标注值必须是格式正确的 Email 地址 | |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
@Range | 标注值必须在指定的范围内 |
<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>
package com.cky.pojo;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class User {
@NotBlank
private String name;
@Min(1)
private int age;
@Email
private String email;
}
package com.cky.controller;
@RestController
@RequestMapping("user")
public class UserController {
@RequestMapping("errorshow")
public Object errorshow(@Validated @RequestBody User user, BindingResult bindingResult){
if(bindingResult.hasErrors()){
Map map=new HashMap();
map.put("code","404");
map.put("msg","参数校验错误");
return map;
}
System.out.println("UserController.errorshow");
return user;
}
}
在这里我们使用json进行接收
如果都验证正确:
如果验证失败:
①我们在hanler 方法参数中验证的实体类必须加上@validated 注解,证明该实体类接收时需要校验,如果我们接收的是param参数,就不用加@ResquestBody了,默认就是接收param参数,如果接收的是json,就需要添加。
②关于校验失败返回信息,如果我们不接受错误信息,则会直接抛出异常,在这里我们绑定错误信息,自定义返回信息。在参数中添加BindingResult参数,注意该参数要紧挨着校验参数,否则自定义信息不起效果。
@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。
@NotNull (包装类型不为null)
@NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。
@NotEmpty (集合类型长度大于0)
@NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。
@NotBlank (字符串,不为null,切不为" "字符串)
@NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。
总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验。
核心点 | 掌握目标 |
springmvc框架 | 主要作用、核心组件、调用流程 |
简化参数接收 | 路径设计、参数接收、请求头接收、cookie接收 |
简化数据响应 | 模板页面、转发和重定向、JSON数据、静态资源 |
restful风格设计 | 主要作用、具体规范、请求方式和请求参数选择 |
功能扩展 | 全局异常处理、拦截器、参数校验注解 |