龙岩网站建设要多,什么是网络营销网络营销与电商营销有什么区别,天津住房和城乡建设厅官方网站,网页游戏网站556pk游戏福利平台三、Spring MVC控制层框架
目录
一、SpringMVC简介和体验 1. 介绍2. 主要作用3. 核心组件和调用流程理解4. 快速体验 二、SpringMVC接收数据 1. 访问路径设置2. 接收参数#xff08;重点#xff09; 2.1 param 和 json参数比较2.2 param参数接收2.3 路径 参数接收2.4 json参…三、Spring MVC控制层框架
目录
一、SpringMVC简介和体验 1. 介绍2. 主要作用3. 核心组件和调用流程理解4. 快速体验 二、SpringMVC接收数据 1. 访问路径设置2. 接收参数重点 2.1 param 和 json参数比较2.2 param参数接收2.3 路径 参数接收2.4 json参数接收 3. 接收Cookie数据4. 接收请求头数据5. 原生Api对象操作6. 共享域对象操作 6.1 属性共享域作用回顾6.2 Request级别属性共享域6.3 Session级别属性共享域6.4 Application级别属性共享域 三、SpringMVC响应数据 1. handler方法分析2. 页面跳转控制 2.1 快速返回jsp视图2.2 转发和重定向 3. 返回JSON数据重点 3.1 前置准备3.2 ResponseBody3.3 RestController 4. 返回静态资源处理 四、RESTFul风格设计 1. RESTFul风格概述 1.1 RESTFul风格简介1.2 RESTFul风格特点1.3 RESTFul风格设计规范1.4 RESTFul风格好处 2. RESTFul风格实战 2.1 需求分析2.2 RESTFul风格接口设计2.3 后台接口实现 五、基于RESTFul风格增删改查练习 1. 案例功能和接口分析 1.1 功能预览1.2 接口分析 2. 工程项目准备 2.1 前端项目搭建2.2 后端项目搭建 3. 增删改查实现 4.1 项目根路径设计4.2 SpringMVC解决跨域问题4.3 业务实现 六、SpringMVC其他扩展 1. 异常处理机制 1.1 异常处理概念1.2 声明式异常好处1.3 基于注解异常声明异常处理 2. 拦截器使用 2.1 拦截器概念2.2 拦截器使用2.3 拦截器作用位置图解2.4 拦截器案例 3. 参数校验4.文件上传和下载 4.1 文件上传4.2 文件下载 七、SpringMVC底层原理 1. 启动配置流程 1.1 Servlet 生命周期回顾1.2 初始化操作调用路线图1.3 SpringMVC IoC 容器创建1.4 将 SpringMVC IoC容器对象存入应用域1.5 请求映射初始化1.6 小结 2. 请求处理流程原理 2.1 总体阶段2.2 调用前阶段2.3调用后阶段2.4 所有断点总结 3. ContextLoaderListener 3.1 配置分离相关问题3.2 配置ContextLoaderListener3.3 探讨两个IoC容器之间关系3.4 两个IoC容器之间Bean访问3.5 有可能重复对象3.5 小结
一、SpringMVC简介和体验
1. 介绍
https://docs.spring.io/spring-framework/reference/web/webmvc.html
Spring Web MVC是基于Servlet API构建的原始Web框架从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称 spring-webmvc 但它通常被称为“Spring MVC”。
在控制层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后目前业界普遍选择了SpringMVC作为Java EE项目表述层开发的首选方案。之所以能做到这一点是因为SpringMVC具备如下显著优势
Spring 家族原生产品与IOC容器等基础设施无缝对接表述层各细分领域需要解决的问题全方位覆盖提供全面解决方案代码清新简洁大幅度提升开发效率内部组件化程度高可插拔式组件即插即用想要什么功能配置相应组件即可性能卓著尤其适合现代大型、超大型互联网项目要求
原生Servlet API开发代码片段
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userName request.getParameter(userName);System.out.println(userNameuserName);
}基于SpringMVC开发代码片段
RequestMapping(/user/login)
public String login(RequestParam(userName) String userName,Sting password){log.debug(userNameuserName);//调用业务即可return result;
}2. 主要作用 SSM框架构建起单体项目的技术栈需求其中的SpringMVC负责表述层控制层实现简化
SpringMVC的作用主要覆盖的是表述层例如
请求映射数据输入视图界面请求分发表单回显会话控制过滤拦截异步交互文件上传文件下载数据校验类型转换等等等
最终总结
简化前端参数接收( 形参列表 )简化后端数据响应( 返回值 )以及其他…
3. 核心组件和调用流程理解
Spring MVC与许多其他Web框架一样是围绕前端控制器模式设计的其中中央 Servlet DispatcherServlet 做整体请求处理调度
除了DispatcherServletSpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。
SpringMVC处理请求流程
SpringMVC涉及组件理解
DispatcherServlet : SpringMVC提供我们需要使用web.xml配置使其生效它是整个流程处理的核心所有请求都经过它的处理和分发HandlerMapping : SpringMVC提供我们需要进行IoC配置使其加入IoC容器方可生效它内部缓存handler(controller方法)和handler访问路径数据被DispatcherServlet调用用于查找路径对应的handlerHandlerAdapter : SpringMVC提供我们需要进行IoC配置使其加入IoC容器方可生效它可以处理请求参数和处理响应数据每次DispatcherServlet都是通过handlerAdapter间接调用handler他是handler和DispatcherServlet之间的适配器Handler : handler又称处理器他是Controller类内部的方法简称是由我们自己定义用来接收参数向后调用业务最终返回响应结果ViewResovler : SpringMVC提供我们需要进行IoC配置使其加入IoC容器方可生效视图解析器主要作用简化模版视图页面查找的但是需要注意前后端分离项目后端只返回JSON数据不返回页面那就不需要视图解析器所以视图解析器相对其他的组件不是必须的
4. 快速体验 体验场景需求 配置分析 DispatcherServlet在web.xml配置设置处理所有请求HandlerMapping,HandlerAdapter,Handler需要加入到IoC容器供DS调用Handler自己声明Controller需要配置到HandlerMapping中供DS查找 准备项目 创建项目 springmvc-base-quick 注意需要转成maven/web程序 导入依赖 propertiesspring.version6.0.6/spring.versionmaven.compiler.source17/maven.compiler.sourcemaven.compiler.target17/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding
/propertiesdependencies!-- springioc相关依赖 --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion${spring.version}/version/dependency!-- web相关依赖 --!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 --!--在 Spring Web MVC 6 中Servlet API 迁移到了 Jakarta EE API因此在配置 DispatcherServlet 时需要使用Jakarta EE 提供的相应类库和命名空间。错误信息 “‘org.springframework.web.servlet.DispatcherServlet’is not assignable to ‘javax.servlet.Servlet,jakarta.servlet.Servlet’” 表明你使用了旧版本的Servlet API没有更新到 Jakarta EE 规范。--dependencygroupIdjakarta.platform/groupIdartifactIdjakarta.jakartaee-web-api/artifactIdversion9.1.0/versionscopeprovided/scope/dependency!-- springwebmvc相关依赖 --dependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion${spring.version}/version/dependency/dependenciesController声明 Controller
public class HelloController {//handlers/*** handler就是controller内部的具体方法* RequestMapping(/springmvc/hello) 就是用来向handlerMapping中注册的方法注解!* ResponseBody 代表向浏览器直接返回数据!*/RequestMapping(/springmvc/hello)ResponseBodypublic String hello(){System.out.println(HelloController.hello);return hello springmvc!!;}
} SpringIoC配置 ?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd!-- 扫描controller对应的包,将handler加入到ioc--context:component-scan base-packagecom.atguigu.controller /!-- 方案1: 手动配置handlerMapping 和 handlerAdapter --!-- handlerMappingRequestMappingHandlerMapping 就是springmvc提供的组件支持RequestMapping方式注册的handler--bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping /!-- handlerAdapterRequestMappingHandlerAdapter 就是springmvc提供的组件支持RequestMapping方式注册的handler--bean classorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter /!-- 方案2: 不配置解释: springmvc会在启动的时候,如果发现我们没有配置handlerMapping,handlerAdapter,viewResolver就会加载spring-webmvc包下的配置DispatcherServlet.properties下的组件!DispatcherServlet.properties:org.springframework.web.servlet.HandlerMappingorg.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapterorg.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapter........所以,我们可以不同写,也会默认加载! 注意: 一旦你配置了,就不会加载对应的默认组件切记!!!--!-- viewResolver 不需要配置,因为我们不需要查找逻辑视图!!! --/beans web.xml配置 ?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0!-- 配置SpringMVC中负责处理请求的核心Servlet也被称为SpringMVC的前端控制器 --servletservlet-nameDispatcherServlet/servlet-name!-- DispatcherServlet的全类名 --servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class!-- 通过初始化参数指定SpringMVC配置文件位置 --init-param!-- 如果不记得contextConfigLocation配置项的名称可以到DispatcherServlet的父类FrameworkServlet中查找 --param-namecontextConfigLocation/param-name!-- 使用classpath:说明这个路径从类路径的根目录开始才查找 --param-valueclasspath:spring-mvc.xml/param-value/init-param!-- 作为框架的核心组件在启动过程中有大量的初始化操作要做这些操作放在第一次请求时才执行非常不恰当 --!-- 我们应该将DispatcherServlet设置为随Web应用一起启动 --load-on-startup100/load-on-startup/servletservlet-mappingservlet-nameDispatcherServlet/servlet-name!-- 对DispatcherServlet来说url-pattern有两种方式配置 --!-- 配置“/”表示匹配整个Web应用范围内所有请求。这里有一个硬性规定不能写成“/*”。只有这一个地方有这个特殊要求以后我们再配置Filter还是可以正常写“/*”。 --url-pattern//url-pattern/servlet-mapping/web-app启动测试 注意 tomcat应该是10版本方可支持 Jakarta EE API!
二、SpringMVC接收数据
1. 访问路径设置
RequestMapping注解的作用就是将请求的 URL 地址和处理请求的方式handler方法关联起来建立映射关系。
SpringMVC 接收到指定的请求就会来找到在映射关系中对应的方法来处理这个请求。 精准路径匹配 在RequestMapping注解指定 URL 地址时不使用任何通配符按照请求地址进行精确匹配。 Controller
public class UserController {/*** 精准设置访问地址 /user/login*/RequestMapping(value {/user/login})ResponseBodypublic String login(){System.out.println(UserController.login);return login success!!;}/*** 精准设置访问地址 /user/register*/RequestMapping(value {/user/register})ResponseBodypublic String register(){System.out.println(UserController.register);return register success!!;}} 模糊路径匹配 在RequestMapping注解指定 URL 地址时通过使用通配符匹配多个类似的地址。 Controller
public class ProductController {/*** 路径设置为 /product/* * /* 为单层任意字符串 /product/a /product/aaa 可以访问此handler * /product/a/a 不可以* 路径设置为 /product/** * /** 为任意层任意字符串 /product/a /product/aaa 可以访问此handler * /product/a/a 也可以访问*/RequestMapping(/product/*)ResponseBodypublic String show(){System.out.println(ProductController.show);return product show!;}
} 单层匹配和多层匹配/*只能匹配URL地址中的一层如果想准确匹配两层那么就写“/*/*”以此类推。/**可以匹配URL地址中的多层。
其中所谓的一层或多层是指一个URL地址字符串被“/”划分出来的各个层次
这个知识点虽然对于RequestMapping注解来说实用性不大但是将来配置拦截器的时候也遵循这个规则。类和方法级别区别 RequestMapping 注解可以用于类级别和方法级别它们之间的区别如下 设置到类级别RequestMapping 注解可以设置在控制器类上用于映射整个控制器的通用请求路径。这样如果控制器中的多个方法都需要映射同一请求路径就不需要在每个方法上都添加映射路径。设置到方法级别RequestMapping 注解也可以单独设置在控制器方法上用于更细粒度地映射请求路径和处理方法。当多个方法处理同一个路径的不同操作时可以使用方法级别的 RequestMapping 注解进行更精细的映射。 //1.标记到handler方法
RequestMapping(/user/login)
RequestMapping(/user/register)
RequestMapping(/user/logout)//2.优化标记类handler方法
//类上
RequestMapping(/user)
//handler方法上
RequestMapping(/login)
RequestMapping(/register)
RequestMapping(/logout) 附带请求方式限制 HTTP 协议定义了八种请求方式在 SpringMVC 中封装到了下面这个枚举类 public enum RequestMethod {GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}默认情况下RequestMapping(“/logout”) 任何请求方式都可以访问 如果需要特定指定 Controller
public class UserController {/*** 精准设置访问地址 /user/login* method RequestMethod.POST 可以指定单个或者多个请求方式!* 注意:违背请求方式会出现405异常!*/RequestMapping(value {/user/login} , method RequestMethod.POST)ResponseBodypublic String login(){System.out.println(UserController.login);return login success!!;}/*** 精准设置访问地址 /user/register*/RequestMapping(value {/user/register},method {RequestMethod.POST,RequestMethod.GET})ResponseBodypublic String register(){System.out.println(UserController.register);return register success!!;}}注意违背请求方式会出现405异常 进阶注解 还有 RequestMapping 的 HTTP 方法特定快捷方式变体 GetMappingPostMappingPutMappingDeleteMappingPatchMapping RequestMapping(value/login,methodRequestMethod.GET)
||
GetMapping(value/login)注意进阶注解只能添加到handler方法上无法添加到类上 常见配置问题 出现原因多个 handler 方法映射了同一个地址导致 SpringMVC 在接收到这个地址的请求时该找哪个 handler 方法处理。 There is already ‘demo03MappingMethodHandler’ bean method com.atguigu.mvc.handler.Demo03MappingMethodHandler#empGet() mapped.
2. 接收参数重点
2.1 param 和 json参数比较
在 HTTP 请求中我们可以选择不同的参数类型如 param 类型和 JSON 类型。下面对这两种参数类型进行区别和对比 参数编码 param 类型的参数会被编码为 ASCII 码。例如假设 namejohn doe则会被编码为 namejohn%20doe。而 JSON 类型的参数会被编码为 UTF-8。 参数顺序 param 类型的参数没有顺序限制。但是JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递其中键值对是有序排列的。 数据类型 param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 JSON 类型的参数则支持更复杂的数据类型如数组、对象等。 嵌套性 param 类型的参数不支持嵌套。但是JSON 类型的参数支持嵌套可以传递更为复杂的数据结构。 可读性 param 类型的参数格式比 JSON 类型的参数更加简单、易读。但是JSON 格式在传递嵌套数据结构时更加清晰易懂。
总的来说param 类型的参数适用于单一的数据传递而 JSON 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求需要选择合适的参数类型。在实际开发中常见的做法是在 GET 请求中采用 param 类型的参数而在 POST 请求中采用 JSON 类型的参数传递。
2.2 param参数接收 直接接值 客户端请求 handler接收参数 只要形参数名和类型与传递参数相同即可自动接收! Controller
RequestMapping(param)
public class ParamController {/*** 前端请求: http://localhost:8080/param/value?namexxage18** 可以利用形参列表,直接接收前端传递的param参数!* 要求: 参数名 形参名* 类型相同* 出现乱码正常json接收具体解决* return 返回前端数据*/GetMapping(value/value)ResponseBodypublic String setupForm(String name,int age){System.out.println(name name , age age);return name age;}
}RequestParam注解 可以使用 RequestParam 注释将 Servlet 请求参数即查询参数或表单数据绑定到控制器中的方法参数。 RequestParam使用场景 指定绑定的请求参数名要求请求参数必须传递为请求参数提供默认值 基本用法 /*** 前端请求: http://localhost:8080/param/data?namexxstuAge18* * 使用RequestParam注解标记handler方法的形参* 指定形参对应的请求参数RequestParam(请求参数名称)*/
GetMapping(value/data)
ResponseBody
public Object paramForm(RequestParam(name) String name, RequestParam(stuAge) int age){System.out.println(name name , age age);return nameage;
}默认情况下使用此批注的方法参数是必需的但您可以通过将 RequestParam 批注的 required 标志设置为 false 如果没有没有设置非必须也没有传递参数会出现 将参数设置非必须并且设置默认值 GetMapping(value/data)
ResponseBody
public Object paramForm(RequestParam(name) String name, RequestParam(value stuAge,required false,defaultValue 18) int age){System.out.println(name name , age age);return nameage;
} 特殊场景接值 一名多值 多选框提交的数据的时候一个key对应多个值我们可以使用集合进行接收 /*** 前端请求: http://localhost:8080/param/mul?hbs吃hbs喝** 一名多值,可以使用集合接收即可!但是需要使用RequestParam注解指定*/GetMapping(value/mul)ResponseBodypublic Object mulForm(RequestParam ListString hbs){System.out.println(hbs hbs);return hbs;}实体接收 Spring MVC 是 Spring 框架提供的 Web 框架它允许开发者使用实体对象来接收 HTTP 请求中的参数。通过这种方式可以在方法内部直接使用对象的属性来访问请求参数而不需要每个参数都写一遍。下面是一个使用实体对象接收参数的示例 定义一个用于接收参数的实体类 public class User {private String name;private int age 18;// getter 和 setter 略
}在控制器中使用实体对象接收示例代码如下 Controller
RequestMapping(param)
public class ParamController {RequestMapping(value /user, method RequestMethod.POST)ResponseBodypublic String addUser(User user) {// 在这里可以使用 user 对象的属性来接收请求参数System.out.println(user user);return success;}
}在上述代码中将请求参数name和age映射到实体类属性上要求属性名必须等于参数名否则无法映射 使用postman传递参数测试 2.3 路径 参数接收
路径传递参数是一种在 URL 路径中传递参数的方式。在 RESTful 的 Web 应用程序中经常使用路径传递参数来表示资源的唯一标识符或更复杂的表示方式。而 Spring MVC 框架提供了 PathVariable 注解来处理路径传递参数。
PathVariable 注解允许将 URL 中的占位符映射到控制器方法中的参数。
例如如果我们想将 /user/{id} 路径下的 {id} 映射到控制器方法的一个参数中则可以使用 PathVariable 注解来实现。
下面是一个使用 PathVariable 注解处理路径传递参数的示例 /*** 动态路径设计: /user/{动态部分}/{动态部分} 动态部分使用{}包含即可! {}内部动态标识!* 形参列表取值: PathVariable Long id 如果形参名 {动态标识} 自动赋值!* PathVariable(动态标识) Long id 如果形参名 ! {动态标识} 可以通过指定动态标识赋值!** 访问测试: /param/user/1/root - id 1 uname root*/
GetMapping(/user/{id}/{name})
ResponseBody
public String getUser(PathVariable Long id, PathVariable(name) String uname) {System.out.println(id id , uname uname);return user_detail;
}2.4 json参数接收
前端传递 JSON 数据时Spring MVC 框架可以使用 RequestBody 注解来将 JSON 数据转换为 Java 对象。RequestBody 注解表示当前方法参数的值应该从请求体中获取并且需要指定 value 属性来指示请求体应该映射到哪个参数上。其使用方式和示例代码如下 前端发送 JSON 数据的示例使用postman测试 {name: 张三,age: 18,gender: 男
}定义一个用于接收 JSON 数据的 Java 类例如 public class Person {private String name;private int age;private String gender;// getter 和 setter 略
}在控制器中使用 RequestBody 注解来接收 JSON 数据并将其转换为 Java 对象例如 PostMapping(/person)
ResponseBody
public String addPerson(RequestBody Person person) {// 在这里可以使用 person 对象来操作 JSON 数据中包含的属性return success;
}在上述代码中RequestBody 注解将请求体中的 JSON 数据映射到 Person 类型的 person 参数上并将其作为一个对象来传递给 addPerson() 方法进行处理。 完善配置 测试 问题 org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type ‘application/json;charsetUTF-8’ is not supported] 原因 不支持json数据类型处理没有json类型处理的工具jackson 解决 spring-mvc.xml 配置json转化器 ?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:mvchttp://www.springframework.org/schema/mvcxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd!-- 扫描controller对应的包,将handler加入到ioc--context:component-scan base-packagecom.atguigu.controller /!-- 注意: 导入mvc命名空间!mvc:annotation-driven 是一个整合标签他会导入handlerMapping和handlerAdapter他会导入json数据格式转化器等等!--mvc:annotation-driven /!-- viewResolver 不需要配置,因为我们不需要查找逻辑视图!!! --/beanspom.xml 加入jackson依赖 dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.15.0/version
/dependencymvc:annotation说明 mvc:annotation是一个整合配置标签一个标签将springmvc必备组件都加入到ioc容器中其中包含handlerMapping和handlerAdapter以及json转化器等等 让我们来查看下mvc:annotation具体的动作 先查看mvc:annotation标签最终对应解析的Java类 查看解析类中具体的动作即可 打开源码org.springframework.web.servlet.config.MvcNamespaceHandler 打开源码org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {public static final String HANDLER_MAPPING_BEAN_NAME RequestMappingHandlerMapping.class.getName();public static final String HANDLER_ADAPTER_BEAN_NAME RequestMappingHandlerAdapter.class.getName();static {ClassLoader classLoader AnnotationDrivenBeanDefinitionParser.class.getClassLoader();javaxValidationPresent ClassUtils.isPresent(jakarta.validation.Validator, classLoader);romePresent ClassUtils.isPresent(com.rometools.rome.feed.WireFeed, classLoader);jaxb2Present ClassUtils.isPresent(jakarta.xml.bind.Binder, classLoader);jackson2Present ClassUtils.isPresent(com.fasterxml.jackson.databind.ObjectMapper, classLoader) ClassUtils.isPresent(com.fasterxml.jackson.core.JsonGenerator, classLoader);jackson2XmlPresent ClassUtils.isPresent(com.fasterxml.jackson.dataformat.xml.XmlMapper, classLoader);jackson2SmilePresent ClassUtils.isPresent(com.fasterxml.jackson.dataformat.smile.SmileFactory, classLoader);jackson2CborPresent ClassUtils.isPresent(com.fasterxml.jackson.dataformat.cbor.CBORFactory, classLoader);gsonPresent ClassUtils.isPresent(com.google.gson.Gson, classLoader);}OverrideNullablepublic BeanDefinition parse(Element element, ParserContext context) {//handlerMapping加入到ioc容器readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);//添加jackson转化器addRequestBodyAdvice(handlerAdapterDef);addResponseBodyAdvice(handlerAdapterDef);//handlerAdapter加入到ioc容器readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);return null;}//具体添加jackson转化对象方法protected void addRequestBodyAdvice(RootBeanDefinition beanDef) {if (jackson2Present) {beanDef.getPropertyValues().add(requestBodyAdvice,new RootBeanDefinition(JsonViewRequestBodyAdvice.class));}}protected void addResponseBodyAdvice(RootBeanDefinition beanDef) {if (jackson2Present) {beanDef.getPropertyValues().add(responseBodyAdvice,new RootBeanDefinition(JsonViewResponseBodyAdvice.class));}}3. 接收Cookie数据
可以使用 CookieValue 注释将 HTTP Cookie 的值绑定到控制器中的方法参数。
考虑使用以下 cookie 的请求
JSESSIONID415A4AC178C59DACE0B2C9CA727CDD84下面的示例演示如何获取 cookie 值
GetMapping(/demo)
public void handle(CookieValue(JSESSIONID) String cookie) { //...
}4. 接收请求头数据
可以使用 RequestHeader 批注将请求标头绑定到控制器中的方法参数。
请考虑以下带有标头的请求
Host localhost:8080
Accept text/html,application/xhtmlxml,application/xml;q0.9
Accept-Language fr,en-gb;q0.7,en;q0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q0.7,*;q0.7
Keep-Alive 300下面的示例获取 Accept-Encoding 和 Keep-Alive 标头的值
GetMapping(/demo)
public void handle(RequestHeader(Accept-Encoding) String encoding, RequestHeader(Keep-Alive) long keepAlive) { //...
}5. 原生Api对象操作
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/arguments.html
下表描述了支持的控制器方法参数
Controller method argument 控制器方法参数Descriptionjakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse请求/响应对象jakarta.servlet.http.HttpSession强制存在会话。因此这样的参数永远不会为 null 。java.io.InputStream, java.io.Reader用于访问由 Servlet API 公开的原始请求正文。java.io.OutputStream, java.io.Writer用于访问由 Servlet API 公开的原始响应正文。PathVariable接收路径参数注解RequestParam用于访问 Servlet 请求参数包括多部分文件。参数值将转换为声明的方法参数类型。RequestHeader用于访问请求标头。标头值将转换为声明的方法参数类型。CookieValue用于访问Cookie。Cookie 值将转换为声明的方法参数类型。RequestBody用于访问 HTTP 请求正文。正文内容通过使用 HttpMessageConverter 实现转换为声明的方法参数类型。java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap共享域对象并在视图呈现过程中向模板公开。Errors, BindingResult验证和数据绑定中的错误信息获取对象
获取原生对象示例
/*** 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!* 注意: 接收原生对象,并不影响参数接收!*/
GetMapping(api)
ResponseBody
public String api(HttpSession session , HttpServletRequest request,HttpServletResponse response){String method request.getMethod();System.out.println(method method);return api;
}6. 共享域对象操作
6.1 属性共享域作用回顾
在 JavaWeb 中共享域指的是在 Servlet 中存储数据以便在同一 Web 应用程序的多个组件中进行共享和访问。常见的共享域有四种ServletContext、HttpSession、HttpServletRequest、PageContext。
ServletContext 共享域ServletContext 对象可以在整个 Web 应用程序中共享数据是最大的共享域。一般可以用于保存整个 Web 应用程序的全局配置信息以及所有用户都共享的数据。在 ServletContext 中保存的数据是线程安全的。HttpSession 共享域HttpSession 对象可以在同一用户发出的多个请求之间共享数据但只能在同一个会话中使用。比如可以将用户登录状态保存在 HttpSession 中让用户在多个页面间保持登录状态。HttpServletRequest 共享域HttpServletRequest 对象可以在同一个请求的多个处理器方法之间共享数据。比如可以将请求的参数和属性存储在 HttpServletRequest 中让处理器方法之间可以访问这些数据。PageContext 共享域PageContext 对象是在 JSP 页面Servlet 创建时自动创建的。它可以在 JSP 的各个作用域中共享数据包括pageScope、requestScope、sessionScope、applicationScope 等作用域。
共享域的作用是提供了方便实用的方式在同一 Web 应用程序的多个组件之间传递数据并且可以将数据保存在不同的共享域中根据需要进行选择和使用。 6.2 Request级别属性共享域
使用 Model 类型的形参RequestMapping(/attr/request/model)
ResponseBody
public String testAttrRequestModel(// 在形参位置声明Model类型变量用于存储模型数据Model model) {// 我们将数据存入模型SpringMVC 会帮我们把模型数据存入请求域// 存入请求域这个动作也被称为暴露到请求域model.addAttribute(requestScopeMessageModel,i am very happy[model]);return target;
}使用 ModelMap 类型的形参RequestMapping(/attr/request/model/map)
ResponseBody
public String testAttrRequestModelMap(// 在形参位置声明ModelMap类型变量用于存储模型数据ModelMap modelMap) {// 我们将数据存入模型SpringMVC 会帮我们把模型数据存入请求域// 存入请求域这个动作也被称为暴露到请求域modelMap.addAttribute(requestScopeMessageModelMap,i am very happy[model map]);return target;
}使用 Map 类型的形参RequestMapping(/attr/request/map)
ResponseBody
public String testAttrRequestMap(// 在形参位置声明Map类型变量用于存储模型数据MapString, Object map) {// 我们将数据存入模型SpringMVC 会帮我们把模型数据存入请求域// 存入请求域这个动作也被称为暴露到请求域map.put(requestScopeMessageMap, i am very happy[map]);return target;
}使用原生 request 对象RequestMapping(/attr/request/original)
ResponseBody
public String testAttrOriginalRequest(// 拿到原生对象就可以调用原生方法执行各种操作HttpServletRequest request) {request.setAttribute(requestScopeMessageOriginal, i am very happy[original]);return target;
}使用 ModelAndView 对象RequestMapping(/attr/request/mav)
public ModelAndView testAttrByModelAndView() {// 1.创建ModelAndView对象ModelAndView modelAndView new ModelAndView();// 2.存入模型数据modelAndView.addObject(requestScopeMessageMAV, i am very happy[mav]);// 3.设置视图名称modelAndView.setViewName(target);return modelAndView;
}6.3 Session级别属性共享域
RequestMapping(/attr/session)
ResponseBody
public String testAttrSession(HttpSession session) {//直接对session对象操作,即对会话范围操作!return target;
}6.4 Application级别属性共享域
解释springmvc会在初始化容器的时候讲servletContext对象存储到ioc容器中
Autowired
private ServletContext servletContext;RequestMapping(/attr/application)
ResponseBody
public String attrApplication() {servletContext.setAttribute(appScopeMsg, i am hungry...);return target;
}三、SpringMVC响应数据
1. handler方法分析
理解handler方法的作用和组成
/*** TODO: 一个controller的方法是控制层的一个处理器,我们称为handler* TODO: handler需要使用RequestMapping/GetMapping系列,声明路径,在HandlerMapping中注册,供DS查找!* TODO: handler作用总结:* 1.接收请求参数(param,json,pathVariable,共享域等) * 2.调用业务逻辑 * 3.响应前端数据(页面不讲解模版页面跳转,json,转发和重定向等)* TODO: handler如何处理呢* 1.接收参数: handler(形参列表: 主要的作用就是用来接收参数)* 2.调用业务: { 方法体 可以向后调用业务方法 service.xx() }* 3.响应数据: return 返回结果,可以快速响应前端数据*/
GetMapping
public Object handler(简化请求参数接收){调用业务方法return 简化响应前端数据;
}总结 请求数据接收我们都是通过handler的形参列表 前端数据响应我们都是通过handler的return关键字快速处理 springmvc简化了参数接收和响应
2. 页面跳转控制
2.1 快速返回jsp视图 开发模式回顾 在 Web 开发中有两种主要的开发模式前后端分离和混合开发。 前后端分离模式 指将前端的界面和后端的业务逻辑通过接口分离开发的一种方式。开发人员使用不同的技术栈和框架前端开发人员主要负责页面的呈现和用户交互后端开发人员主要负责业务逻辑和数据存储。前后端通信通过 API 接口完成数据格式一般使用 JSON 或 XML。前后端分离模式可以提高开发效率同时也有助于代码重用和维护。 混合开发模式 指将前端和后端的代码集成在同一个项目中共享相同的技术栈和框架。这种模式在小型项目中比较常见可以减少学习成本和部署难度。但是在大型项目中这种模式会导致代码耦合性很高维护和升级难度较大。 对于混合开发我们就需要使用动态页面技术动态展示Java的共享域数据 jsp技术了解 JSPJavaServer Pages是一种动态网页开发技术它是由 Sun 公司提出的一种基于 Java 技术的 Web 页面制作技术可以在 HTML 文件中嵌入 Java 代码使得生成动态内容的编写更加简单。 JSP 最主要的作用是生成动态页面。它允许将 Java 代码嵌入到 HTML 页面中以便使用 Java 进行数据库查询、处理表单数据和生成 HTML 等动态内容。另外JSP 还可以与 Servlet 结合使用实现更加复杂的 Web 应用程序开发。 JSP 的主要特点包括 简单JSP 通过将 Java 代码嵌入到 HTML 页面中使得生成动态内容的编写更加简单。高效JSP 首次运行时会被转换为 Servlet然后编译为字节码从而可以启用 Just-in-TimeJIT编译器实现更高效的运行。多样化JSP 支持多种标准标签库包括 JSTLJavaServer Pages 标准标签库、EL表达式语言等可以帮助开发人员更加方便的处理常见的 Web 开发需求。 总之JSP 是一种简单高效、多样化的动态网页开发技术它可以方便地生成动态页面和与 Servlet 结合使用是 Java Web 开发中常用的技术之一。 准备jsp页面和依赖 pom.xml依赖 !-- jsp需要依赖! jstl--
dependencygroupIdjakarta.servlet.jsp.jstl/groupIdartifactIdjakarta.servlet.jsp.jstl-api/artifactIdversion3.0.0/version
/dependencyjsp页面创建 建议位置/WEB-INF/下避免外部直接访问 位置/WEB-INF/views/home.jsp % page contentTypetext/html;charsetUTF-8 languagejava %
htmlheadtitleTitle/title/headbody!-- 可以获取共享域的数据,动态展示! jsp 后台vue --${msg}/body
/html 快速响应模版页面 配置jsp视图解析器 springmvc.xml !-- 配置动态页面语言jsp的视图解析器,快速查找jsp--
bean classorg.springframework.web.servlet.view.InternalResourceViewResolverproperty nameviewClass valueorg.springframework.web.servlet.view.JstlView/property nameprefix value/WEB-INF/views//property namesuffix value.jsp/
/beanhandler返回视图 /*** 跳转到提交文件页面 /save/jump* * 如果要返回jsp页面!* 1.方法返回值改成字符串类型* 2.返回逻辑视图名即可 * property nameprefix value/WEB-INF/views//* 逻辑视图名 * property namesuffix value.jsp/*/
GetMapping(jump)
public String jumpJsp(Model model){System.out.println(FileController.jumpJsp);model.addAttribute(msg,request data!!);return home;
}2.2 转发和重定向
在 Spring MVC 中Handler 方法返回值来实现快速转发可以使用 redirect 或者 forward 关键字来实现重定向。
RequestMapping(/redirect-demo)
public String redirectDemo() {// 重定向到 /demo 路径 return redirect:/demo;
}RequestMapping(/forward-demo)
public String forwardDemo() {// 转发到 /demo 路径return forward:/demo;
}//注意 转发和重定向到项目下资源路径都是相同都不需要添加项目根路径填写项目下路径即可总结
将方法的返回值设置String类型转发使用forward关键字重定向使用redirect关键字关键字: /路径注意如果是项目下的资源转发和重定向都一样都是项目下路径都不需要添加项目根路径
3. 返回JSON数据重点
3.1 前置准备
导入jackson依赖
dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.15.0/version
/dependency添加json数据转化器 !-- 注意: 导入mvc命名空间!mvc:annotation-driven 是一个整合标签他会导入handlerMapping和handlerAdapter他会导入json数据格式转化器等等!
--
mvc:annotation-driven /3.2 ResponseBody 方法上使用ResponseBody 可以在方法上使用 ResponseBody注解用于将方法返回的对象序列化为 JSON 或 XML 格式的数据并发送给客户端。在前后端分离的项目中使用 测试方法 GetMapping(/accounts/{id})
ResponseBody
public Object handle() {// ...return obj;
}具体来说ResponseBody 注解可以用来标识方法或者方法返回值表示方法的返回值是要直接返回给客户端的数据而不是由视图解析器来解析并渲染生成响应体viewResolver没用。 测试方法 RequestMapping(value /user/detail, method RequestMethod.POST)
ResponseBody
public User getUser(RequestBody User userParam) {System.out.println(userParam userParam);User user new User();user.setAge(18);user.setName(John);//返回的对象,会使用jackson的序列化工具,转成json返回给前端!return user;
}返回结果 类上使用ResponseBody 如果类中每个方法上都标记了 ResponseBody 注解那么这些注解就可以提取到类上。 ResponseBody //responseBody可以添加到类上,代表默认类中的所有方法都生效!
Controller
RequestMapping(param)
public class ParamController {3.3 RestController
类上的 ResponseBody 注解可以和 Controller 注解合并为 RestController 注解。所以使用了 RestController 注解就相当于给类中的每个方法都加了 ResponseBody 注解。
RestController源码:
Target(ElementType.TYPE)
Retention(RetentionPolicy.RUNTIME)
Documented
Controller
ResponseBody
public interface RestController {/*** The value may indicate a suggestion for a logical component name,* to be turned into a Spring bean in case of an autodetected component.* return the suggested component name, if any (or empty String otherwise)* since 4.0.1*/AliasFor(annotation Controller.class)String value() default ;}4. 返回静态资源处理 静态资源概念 资源本身已经是可以直接拿到浏览器上使用的程度了不需要在服务器端做任何运算、处理。典型的静态资源包括 纯HTML文件图片CSS文件JavaScript文件…… 静态资源访问和问题解决 web应用加入静态资源 手动构建确保编译 访问静态资源 问题分析 DispatcherServlet 的 url-pattern 配置的是“/”url-pattern 配置“/”表示整个 Web 应用范围内所有请求都由 SpringMVC 来处理 对 SpringMVC 来说必须有对应的 RequestMapping 才能找到处理请求的方法 现在 images/mi.jpg 请求没有对应的 RequestMapping 所以返回 404 问题解决 在 SpringMVC 配置文件中增加配置 ?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:mvchttp://www.springframework.org/schema/mvcxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd!-- 扫描controller对应的包,将handler加入到ioc--context:component-scan base-packagecom.atguigu.controller /!-- 加入这个配置SpringMVC 就会在遇到没有 RequestMapping 的请求时放它过去 --!-- 所谓放它过去就是让这个请求去找它原本要访问的资源 --mvc:default-servlet-handler/
/beans再次测试访问图片 新的问题其他原本正常的handler请求访问不了了 handler无法访问 解决方案 !-- 开启 SpringMVC 的注解驱动功能。这个配置也被称为 SpringMVC 的标配。 --
!-- 标配因为 SpringMVC 环境下非常多的功能都要求必须打开注解驱动才能正常工作。 --
mvc:annotation-driven/四、RESTFul风格设计
1. RESTFul风格概述
1.1 RESTFul风格简介 RESTfulRepresentational State Transfer是一种软件架构风格用于设计网络应用程序和服务之间的通信。它是一种基于标准 HTTP 方法的简单和轻量级的通信协议广泛应用于现代的Web服务开发。
通过遵循 RESTful 架构的设计原则可以构建出易于理解、可扩展、松耦合和可重用的 Web 服务。RESTful API 的特点是简单、清晰并且易于使用和理解它们使用标准的 HTTP 方法和状态码进行通信不需要额外的协议和中间件。
RESTful 架构通常用于构建 Web API提供数据的传输和操作。它可以用于各种应用场景包括客户端-服务器应用、单页应用SPA、移动应用程序和微服务架构等。
总而言之RESTful 是一种基于 HTTP 和标准化的设计原则的软件架构风格用于设计和实现可靠、可扩展和易于集成的 Web 服务和应用程序 学习RESTful设计原则可以帮助我们更好去设计HTTP协议的API接口
1.2 RESTFul风格特点 每一个URI代表1种资源URI 是名词 客户端使用GET、POST、PUT、DELETE 4个表示操作方式的动词对服务端资源进行操作GET用来获取资源POST用来新建资源也可以用于更新资源PUT用来更新资源DELETE用来删除资源 资源的表现形式是XML或者JSON 客户端与服务端之间的交互在请求之间是无状态的从客户端到服务端的每个请求都必须包含理解请求所必需的信息。 小结rest 面向的是资源、而不是功能rest操作要求符合幂等性 幂等性执行一次操作和执行多次操作对系统的影响是一样的1.3 RESTFul风格设计规范 HTTP协议请求方式要求 REST 风格主张在项目设计、开发过程中具体的操作符合HTTP协议定义的请求方式的语义。 操作请求方式查询操作GET保存操作POST删除操作DELETE更新操作PUT URL路径风格要求 REST风格下每个资源都应该有一个唯一的标识符例如一个 URI统一资源标识符或者一个 URL统一资源定位符。资源的标识符应该能明确地说明该资源的信息同时也应该是可被理解和解释的 使用URL请求方式确定具体的动作他也是一种标准的HTTP协议请求 操作传统风格REST 风格保存/CRUD/saveEmpURL 地址/emp 请求方式POST删除/CRUD/removeEmp?empId2URL 地址/emp/2 请求方式DELETE更新/CRUD/updateEmpURL 地址/emp 请求方式PUT查询/CRUD/editEmp?empId2URL 地址/emp/2 请求方式GET 总结 根据接口的具体动作选择具体的HTTP协议请求方式 路径设计从原来携带动标识改成名词对应资源的唯一标识即可
1.4 RESTFul风格好处 含蓄安全 使用问号键值对的方式给服务器传递数据太明显容易被人利用来对系统进行破坏。使用 REST 风格携带数据不再需要明显的暴露数据的名称。 风格统一 URL 地址整体格式统一从前到后始终都使用斜杠划分各个单词用简单一致的格式表达语义。 无状态 在调用一个接口访问、操作资源的时候可以不用考虑上下文不用考虑当前状态极大的降低了系统设计的复杂度。 严谨规范 严格按照 HTTP1.1 协议中定义的请求方式本身的语义进行操作。 简洁优雅 过去做增删改查操作需要设计4个不同的URL现在一个就够了。 操作传统风格REST 风格保存/CRUD/saveEmpURL 地址/emp 请求方式POST删除/CRUD/removeEmp?empId2URL 地址/emp/2 请求方式DELETE更新/CRUD/updateEmpURL 地址/emp 请求方式PUT查询/CRUD/editEmp?empId2URL 地址/emp/2 请求方式GET 丰富的语义 通过 URL 地址就可以知道资源之间的关系。它能够把一句话中的很多单词用斜杠连起来反过来说就是可以在 URL 地址中用一句话来充分表达语义。 http://localhost:8080/shop http://localhost:8080/shop/product http://localhost:8080/shop/product/cellPhone http://localhost:8080/shop/product/cellPhone/iPhone 2. RESTFul风格实战
2.1 需求分析
数据结构 User {id 唯一标识,name 用户名age 用户年龄}功能分析 用户数据分页展示功能条件page 页数 默认1size 每页数量 默认 10保存用户功能根据用户id查询用户详情功能根据用户id更新用户数据功能根据用户id删除用户数据功能多条件模糊查询用户功能条件keyword 模糊关键字page 页数 默认1size 每页数量 默认 10
2.2 RESTFul风格接口设计 接口设计 功能接口和请求方式请求参数返回值分页查询GET /userpage1size10 param{ 响应数据 }用户添加POST /user{ user 数据 }{响应数据}用户详情GET /user/1路径参数{响应数据}用户更新PUT /user{ user 更新数据}{响应数据}用户删除DELETE /user/1路径参数{响应数据}条件模糊GET /user/searchpage1size10keywork关键字{响应数据} 问题讨论 为什么查询用户详情就使用路径传递参数多条件模糊查询就使用请求参数传递 误区restful风格下不是所有请求参数都是路径传递可以使用其他方式传递 在 RESTful API 的设计中路径和请求参数和请求体都是用来向服务器传递信息的方式。 对于查询用户详情使用路径传递参数是因为这是一个单一资源的查询即查询一条用户记录。使用路径参数可以明确指定所请求的资源便于服务器定位并返回对应的资源也符合 RESTful 风格的要求。而对于多条件模糊查询使用请求参数传递参数是因为这是一个资源集合的查询即查询多条用户记录。使用请求参数可以通过组合不同参数来限制查询结果路径参数的组合和排列可能会很多不如使用请求参数更加灵活和简洁。 此外还有一些通用的原则可以遵循路径参数应该用于指定资源的唯一标识或者 ID而请求参数应该用于指定查询条件或者操作参数。请求参数应该限制在 10 个以内过多的请求参数可能导致接口难以维护和使用。对于敏感信息最好使用 POST 和请求体来传递参数。
2.3 后台接口实现
准备用户实体类
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;}Overridepublic String toString() {return User{ id id , name name \ , age age };}
}
准备用户Controller:
/*** projectName: com.atguigu.controller** description: 用户模块的控制器*/
RequestMapping(user)
RestController
public class UserController {/*** 模拟分页查询业务接口*/GetMappingpublic 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};}/*** 模拟用户保存业务接口*/PostMappingpublic 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};}/*** 模拟用户更新业务接口*/PutMappingpublic 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};}
}五、基于RESTFul风格增删改查练习
1. 案例功能和接口分析
1.1 功能预览 1.2 接口分析
学习计划查询/*
需求说明查询全部数据页数据
请求urischedule
请求方式 get
响应的json{code:200,flag:true,data:[{id:1,title:学习java,completed:true},{id:2,title:学习html,completed:true},{id:3,title:学习css,completed:true},{id:4,title:学习js,completed:true},{id:5,title:学习vue,completed:true}]}
*/学习计划删除/*
需求说明根据id删除日程
请求urischedule/{id}
请求方式 delete
响应的json{code:200,flag:true,data:null}
*/学习计划保存/*
需求说明增加日程
请求urischedule
请求方式 post
请求体中的JSON{title: ,completed: false}
响应的json{code:200,flag:true,data:null}
*/学习计划修改/*
需求说明根据id修改数据
请求urischedule
请求方式 put
请求体中的JSON{id: 1,title: ,completed: false}
响应的json{code:200,flag:true,data:null}
*/2. 工程项目准备
2.1 前端项目搭建 导入前端工程 安装npm依赖 进入前端工程项目文件夹下 npm i启动前端程序 npm run dev2.2 后端项目搭建 数据库怎么办使用HashMap模拟所以不涉及和MyBatis、Spring的整合! 搭建后台项目 pom.xml propertiesspring.version6.0.6/spring.versionmaven.compiler.source17/maven.compiler.sourcemaven.compiler.target17/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding
/propertiesdependencies!-- springioc相关依赖 --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion${spring.version}/version/dependency!-- web相关依赖 --!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 --!--在 Spring Web MVC 6 中Servlet API 迁移到了 Jakarta EE API因此在配置 DispatcherServlet 时需要使用Jakarta EE 提供的相应类库和命名空间。错误信息 “‘org.springframework.web.servlet.DispatcherServlet’is not assignable to ‘javax.servlet.Servlet,jakarta.servlet.Servlet’” 表明你使用了旧版本的Servlet API没有更新到 Jakarta EE 规范。--dependencygroupIdjakarta.platform/groupIdartifactIdjakarta.jakartaee-web-api/artifactIdversion9.1.0/versionscopeprovided/scope/dependencydependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactIdversion2.15.0/version/dependency!-- springwebmvc相关依赖 --dependencygroupIdorg.springframework/groupIdartifactIdspring-webmvc/artifactIdversion${spring.version}/version/dependency/dependencies准备实体类 包com.atguigu.pojo /*** projectName: com.atguigu.pojo** description: 任务实体类*/
public class Schedule {private Integer id;private String title;private Boolean completed;public Schedule() {}public Schedule(Integer id, String title, Boolean completed) {this.id id;this.title title;this.completed completed;}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String getTitle() {return title;}public void setTitle(String title) {this.title title;}public Boolean getCompleted() {return completed;}public void setCompleted(Boolean completed) {this.completed completed;}Overridepublic String toString() {return Schedule{ id id , title title \ , completed completed };}
}准备R结果包装类 包com.atguigu.utils /*** projectName: com.atguigu.utils** description: 返回结果类*/
public class R {private int code 200; //200成功状态码private boolean flag true; //返回状态private Object data; //返回具体数据public static R ok(Object data){R r new R();r.data data;return r;}public static R fail(Object data){R r new R();r.code 500; //错误码r.flag false; //错误状态r.data data;return r;}public int getCode() {return code;}public void setCode(int code) {this.code code;}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag flag;}public Object getData() {return data;}public void setData(Object data) {this.data data;}
}准备业务类 包com.atguigu.service 业务接口/*** projectName: com.atguigu.service** description: schedule业务接口*/public interface ScheduleService {/*** 返回全部学习计划* return*/ListSchedule getAll();/*** 保存学习计划* param schedule*/void saveSchedule(Schedule schedule);/*** 更新学习计划* param schedule*/void updateSchedule(Schedule schedule);/*** 移除学习计划* param*/void removeById(Integer id);}业务实现/*** projectName: com.atguigu.service.impl** description:*/
Service
public class ScheduleServiceImpl implements ScheduleService {//准备假数据private static MapInteger,Schedule scheduleMap;private static int maxId 5;static {scheduleMap new HashMap();Schedule schedule null;schedule new Schedule(1, 学习Java, false);scheduleMap.put(1, schedule);schedule new Schedule(2, 学习H5, true);scheduleMap.put(2, schedule);schedule new Schedule(3, 学习Css, false);scheduleMap.put(3, schedule);schedule new Schedule(4, 学习JavaScript, false);scheduleMap.put(4, schedule);schedule new Schedule(5, 学习Spring, true);scheduleMap.put(5, schedule);} /*** 返回全部学习计划** return*/Overridepublic ListSchedule getAll() {return new ArrayList(scheduleMap.values());}/*** 保存学习计划** param schedule*/Overridepublic void saveSchedule(Schedule schedule) {maxId;schedule.setId(maxId);scheduleMap.put(maxId,schedule);}/*** 更新学习计划** param schedule*/Overridepublic void updateSchedule(Schedule schedule) {scheduleMap.put(schedule.getId(),schedule);}/*** 移除学习计划** param id*/Overridepublic void removeById(Integer id) {scheduleMap.remove(id);}}准备spring-mvc.配置文件 位置resources/spring-mvc.xml ?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:mvchttp://www.springframework.org/schema/mvcxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd!-- 扫描controller对应的包,将handler加入到ioc--context:component-scan base-packagecom.atguigu.controller,com.atguigu.service /!--注意: 导入mvc命名空间!mvc:annotation-driven 是一个整合标签他会导入handlerMapping和handlerAdapter他会导入json数据格式转化器等等!--mvc:annotation-driven /!-- viewResolver 不需要配置,因为我们不需要查找逻辑视图!!! --!-- 加入这个配置SpringMVC 就会在遇到没有 RequestMapping 的请求时放它过去 --!-- 所谓放它过去就是让这个请求去找它原本要访问的资源 --mvc:default-servlet-handler/
/beans准备 web.xml配置文件 ?xml version1.0 encodingUTF-8?
web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0!-- 配置SpringMVC中负责处理请求的核心Servlet也被称为SpringMVC的前端控制器 --servletservlet-nameDispatcherServlet/servlet-name!-- DispatcherServlet的全类名 --servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class!-- 通过初始化参数指定SpringMVC配置文件位置 --init-param!-- 如果不记得contextConfigLocation配置项的名称可以到DispatcherServlet的父类FrameworkServlet中查找 --param-namecontextConfigLocation/param-name!-- 使用classpath:说明这个路径从类路径的根目录开始才查找 --param-valueclasspath:spring-mvc.xml/param-value/init-param!-- 作为框架的核心组件在启动过程中有大量的初始化操作要做这些操作放在第一次请求时才执行非常不恰当 --!-- 我们应该将DispatcherServlet设置为随Web应用一起启动 --load-on-startup1/load-on-startup/servletservlet-mappingservlet-nameDispatcherServlet/servlet-name!-- 对DispatcherServlet来说url-pattern有两种方式配置 --!-- 方式一配置“/”表示匹配整个Web应用范围内所有请求。这里有一个硬性规定不能写成“/*”。只有这一个地方有这个特殊要求以后我们再配置Filter还是可以正常写“/*”。 --!-- 方式二配置“*.扩展名”表示匹配整个Web应用范围内部分请求 --url-pattern//url-pattern/servlet-mapping/web-app3. 增删改查实现
4.1 项目根路径设计
因为前端项目设置了后台访问的项目根路径为 /rest
我们后台项目也对应的设置 4.2 SpringMVC解决跨域问题
假设我们有一个网站 http://example.com现在需要跨域请求另外一个网站 http://api.example.com 中的数据。浏览器就会因为安全问题拒绝客户端访问请求
跨域问题是指在浏览器中发起跨域请求被浏览器拦截的问题。在同一个源域同一协议、主机、端口浏览器允许 JavaScript 发起跨域请求在不同的源域下浏览器对发起的异域请求会做出不同的限制。
常见的跨域问题的场景有
访问不同的子域名访问不同的端口号访问不同的协议http、https访问不同的域名
基于CORS方式解决跨域思路
CORSCross-Origin Resource Sharing是 W3C 制定的一种跨域解决方案它给出了跨域请求和响应的标准。服务器端代码需要在响应头中设置 Access-Control-Allow-Origin并指定访问来源域名名或 * 通配符表示允许的跨域请求。浏览器可以根据响应头信息判断是否允许该请求。
SpringMVC基于CORS思路解决跨域方案 CrossOrigin注解 CrossOrigin 注释在带注释的【控制器方法】 / 【控制器类】上启用跨源请求 RestController
RequestMapping(/account)
public class AccountController {CrossOriginGetMapping(/{id})public Account retrieve(PathVariable Long id) {// ...}DeleteMapping(/{id})public void remove(PathVariable Long id) {// ...}
} 默认情况下 CrossOrigin 允许 All origins.All headers.All HTTP methods to which the controller method is mapped. 注解核心设置属性讲解 Target({ElementType.TYPE, ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
Documented
public interface CrossOrigin {/*** 设置哪些客户端地址可以跨域访问! 格式为: 协议://主机地址:端口号* return*/AliasFor(origins)String[] value() default {};AliasFor(value)String[] origins() default {};/*** 设置哪些客户端的[自定义请求头]可以跨域访问!*/String[] allowedHeaders() default {};/*** 设置哪些服务端的自定义响应头,可以被客户端读取!*/String[] exposedHeaders() default {};/***设置哪些请求方法,可以跨域方式! */RequestMethod[] methods() default {};/*** 值为 true 或者 false* 客户端是否可以携带cookie!*/String allowCredentials() default ;
}xml全局跨域配置 mvc:corsmvc:mapping path/**allowed-origins*allowed-methodsGET, PUTallowed-headersheader1, header2, header3exposed-headersheader1, header2 allow-credentialstrue/mvc:mapping path/**allowed-originshttps://domain1.com //mvc:cors4.3 业务实现
查询业务/*** projectName: com.atguigu.controller** description: 学习计划controller*/
CrossOrigin
/*CrossOrigin 注释在带注释的控制器方法上启用跨源请求默认情况下 CrossOrigin 允许All origins 任何请求主机地址All headers 任何请求头All HTTP methods to which the controller method is mapped. 任何请求方式!可以设置:CrossOrigin(origins https://domain2.com) 指定允许跨域请求的主机地址*/
RequestMapping(schedule)
RestController
public class ScheduleController
{Autowiredprivate ScheduleService scheduleService;GetMappingpublic R showList(){ListSchedule list scheduleService.getAll();return R.ok(list);}
} 修改业务PutMapping
public R changeSchedule(RequestBody Schedule schedule){scheduleService.updateSchedule(schedule);return R.ok(null);
}删除业务DeleteMapping(/{id})
public R removeSchedule(PathVariable Integer id){scheduleService.removeById(id);return R.ok(null);
}保存业务PostMapping
public R saveSchedule(RequestBody Schedule schedule){scheduleService.saveSchedule(schedule);return R.ok(null);
}六、SpringMVC其他扩展
1. 异常处理机制
1.1 异常处理概念
开发过程中是不可避免地会出现各种异常情况的例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题甚至直接导致程序崩溃。因此在开发过程中合理处理异常、避免异常产生、以及对异常进行有效的调试是非常重要的。
对于异常的处理一般分为两种方式
编程式异常处理是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理例如使用 try-catch 块来捕获异常然后在 catch 块中编写特定的处理代码或者在 finally 块中执行一些清理操作。在编程式异常处理中开发人员需要显式地进行异常处理异常处理代码混杂在业务代码中导致代码可读性较差。声明式异常处理则是将异常处理的逻辑从具体的业务逻辑中分离出来通过配置等方式进行统一的管理和处理。在声明式异常处理中开发人员只需要为方法或类标注相应的注解如 Throws 或 ExceptionHandler就可以处理特定类型的异常。相较于编程式异常处理声明式异常处理可以使代码更加简洁、易于维护和扩展。
站在宏观角度来看待声明式事务处理
整个项目从架构这个层面设计的异常处理的统一机制和规范。
一个项目中会包含很多个模块各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常李四负责的模块按照 B 方案处理异常……各个模块处理异常的思路、代码、命名细节都不一样那么就会让整个项目非常混乱。
使用声明式异常处理可以统一项目处理异常思路项目更加清晰明了
1.2 声明式异常好处
使用声明式代替编程式来实现异常管理 让异常控制和核心业务解耦二者各自维护结构性更好 整个项目层面使用同一套规则来管理异常 整个项目代码风格更加统一、简洁便于团队成员之间的彼此协作
1.3 基于注解异常声明异常处理 声明异常处理控制器类 异常处理控制类统一定义异常处理handler方法 /*** projectName: com.atguigu.execptionhandler* * description: 全局异常处理器,内部可以定义异常处理Handler!*//*** RestControllerAdvice ControllerAdvice ResponseBody* ControllerAdvice 代表当前类的异常处理controller! */
RestControllerAdvice
public class GlobalExceptionHandler {}声明异常处理hander方法 异常处理handler方法和普通的handler方法参数接收和响应都一致 只不过异常处理handler方法要映射异常发生对应的异常会调用 普通的handler方法要使用RequestMapping注解映射路径发生对应的路径调用 /*** 异常处理handler * ExceptionHandler(HttpMessageNotReadableException.class) * 该注解标记异常处理Handler,并且指定发生异常调用该方法!* * * param e 获取异常对象!* return 返回handler处理结果!*/
ExceptionHandler(HttpMessageNotReadableException.class)
public Object handlerJsonDateException(HttpMessageNotReadableException e){return null;
}/*** 当发生空指针异常会触发此方法!* param e* return*/
ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){return null;
}/*** 所有异常都会触发此方法!但是如果有具体的异常处理Handler! * 具体异常处理Handler优先级更高!* 例如: 发生NullPointerException异常!* 会触发handlerNullException方法,不会触发handlerException方法!* param e* return*/
ExceptionHandler(Exception.class)
public Object handlerException(Exception e){return null;
}配置文件扫描控制器类配置 确保异常处理控制类被扫描 !-- 扫描controller对应的包,将handler加入到ioc--context:component-scan base-packagecom.atguigu.controller,com.atguigu.exceptionhandler /2. 拦截器使用
2.1 拦截器概念
拦截器和过滤器解决问题 生活中 为了提高乘车效率在乘客进入站台前统一检票 程序中 在程序中使用拦截器在请求到达具体 handler 方法前统一执行检测
拦截器 VS 过滤器
相似点 拦截必须先把请求拦住才能执行后续操作过滤拦截器或过滤器存在的意义就是对请求进行统一处理放行对请求执行了必要操作后放请求过去让它访问原本想要访问的资源 不同点 工作平台不同 过滤器工作在 Servlet 容器中拦截器工作在 SpringMVC 的基础上 拦截的范围 过滤器能够拦截到的最大范围是整个 Web 应用拦截器能够拦截到的最大范围是整个 SpringMVC 负责的请求 IOC 容器支持 过滤器想得到 IOC 容器需要调用专门的工具方法是间接的拦截器它自己就在 IOC 容器中所以可以直接从 IOC 容器中装配组件也就是可以直接得到 IOC 容器的支持
选择
功能需要如果用 SpringMVC 的拦截器能够实现就不使用过滤器。 2.2 拦截器使用 创建拦截器类 public class Process01Interceptor implements HandlerInterceptor {// 在处理请求的目标 handler 方法前执行Overridepublic 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报错不执行!Overridepublic 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);}// 渲染视图之后执行(最后),一定执行!Overridepublic 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);}
}单个拦截器执行顺序: preHandle() 方法目标 handler 方法postHandle() 方法渲染视图(返回json没有此步骤)afterCompletion() 方法 拦截器配置 springmvc.xml !-- 配置拦截器--
mvc:interceptors!-- 默认拦截器,拦截所有请求--bean classcom.atguigu.interceptor.Process01Interceptor /
/mvc:interceptors 配置详解 默认拦截全部!-- 拦截全部的请求地址 --
mvc:interceptorsbean classcom.atguigu.mvc.interceptor.Process03Interceptor/
/mvc:interceptors精准配置!-- 具体配置拦截器可以指定拦截的请求地址 --
mvc:interceptor!-- 精确匹配 --mvc:mapping path/common/request/one/bean classcom.atguigu.mvc.interceptor.Process03Interceptor/
/mvc:interceptormvc:interceptor
!-- /*匹配路径中的一层 --mvc:mapping path/common/request/*/bean classcom.atguigu.mvc.interceptor.Process04Interceptor/
/mvc:interceptormvc:interceptor
!-- /**匹配路径中的多层 --mvc:mapping path/common/request/**/bean classcom.atguigu.mvc.interceptor.Process05Interceptor/
/mvc:interceptor 排除配置 mvc:interceptor!-- /**匹配路径中的多层 --mvc:mapping path/common/request/**/!-- 使用 mvc:exclude-mapping 标签配置不拦截的地址 --mvc:exclude-mapping path/common/request/two/bbb/bean classcom.atguigu.mvc.interceptor.Process05Interceptor//mvc:interceptor多个拦截器执行顺序 preHandle() 方法SpringMVC 会把所有拦截器收集到一起然后按照配置顺序调用各个 preHandle() 方法。postHandle() 方法SpringMVC 会把所有拦截器收集到一起然后按照配置相反的顺序调用各个 postHandle() 方法。afterCompletion() 方法SpringMVC 会把所有拦截器收集到一起然后按照配置相反的顺序调用各个 afterCompletion() 方法。
2.3 拦截器作用位置图解 2.4 拦截器案例
一个网站有 56个资源其中一个为登陆资源两个无须登录即可访问另外三个需要登录后才能访问。如果不登录就访问那三个资源需要拦截并且提示登录后访问访问
提示登陆为模拟登陆存储一个user可以到session即可
访问资源的请求地址可参考
登陆资源/public/resource/login公共资源1/public/resource/one公共资源2/public/resouce/two私密资源1/private/resouce/one私密资源2/private/resouce/two私密资源3/private/resouce/three
案例实现
声明资源类 PublicController/*** projectName: com.atguigu.controller* description: 公有资源控制类*/
RestController
RequestMapping(public/resource)
public class PublicController {/*** 模拟登录,将假用户数据存储到session中!*/GetMapping(login)public Object login(HttpSession session){session.setAttribute(user,root);return login success!!;}GetMapping(one)public Object one(){return public one;}GetMapping(two)public Object two(){return public two;}
}PrivateControllerRestController
RequestMapping(private/resource)
public class PrivateController {GetMapping(one)public Object one(){return private one;}GetMapping(two)public Object two(){return private two;}GetMapping(three)public Object three(){return private two;}} 声明拦截器类/*** projectName: com.atguigu.interceptor** description: 登录保护拦截器*/
public class LoginProtectInterceptor implements HandlerInterceptor {/*** 登录保护方法* param request current HTTP request* param response current HTTP response* param handler chosen handler to execute, for type and/or instance evaluation* return* throws Exception*/Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Object user request.getSession().getAttribute(user);if (user null){response.setContentType(text/html;charsetutf-8);//没有登录response.getWriter().print(请先登录,再访问! a href/public/resource/login点击此处登录/a);//拦截,不到达目标地址return false;}return true;}
}配置拦截器类!-- 配置拦截器--
mvc:interceptorsmvc:interceptormvc:mapping path/private/**/bean classcom.atguigu.interceptor.LoginProtectInterceptor //mvc:interceptor
/mvc:interceptors3. 参数校验 在 Web 应用三层架构体系中表述层负责接收浏览器提交的数据业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理我们需要在表述层对数据进行检查将错误的数据隔绝在业务逻辑层之外。 校验概述 JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 NotNull、Max 等标准的注解指定校验规则并通过标准的验证接口对Bean进行验证。 注解规则Null标注值必须为 nullNotNull标注值不可为 nullAssertTrue标注值必须为 trueAssertFalse标注值必须为 falseMin(value)标注值必须大于或等于 valueMax(value)标注值必须小于或等于 valueDecimalMin(value)标注值必须大于或等于 valueDecimalMax(value)标注值必须小于或等于 valueSize(max,min)标注值大小必须在 max 和 min 限定的范围内Digits(integer,fratction)标注值值必须是一个数字且必须在可接受的范围内Past标注值只能用于日期型且必须是过去的日期Future标注值只能用于日期型且必须是将来的日期Pattern(value)标注值必须符合指定的正则表达式JSR 303 只是一套标准需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现除支持所有标准的校验注解外它还支持以下的扩展注解 注解规则Email标注值必须是格式正确的 Email 地址Length标注值字符串大小必须在指定的范围内NotEmpty标注值字符串不能是空字符串Range标注值必须在指定的范围内Spring 4.0 版本已经拥有自己独立的数据校验框架同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时可同时调用校验框架完成数据校验工作。在SpringMVC 中可直接通过注解驱动 mvc:annotation-driven 的方式进行数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现所以必须将JSR 303的实现者的jar包放到类路径下。 配置 mvc:annotation-driven 后SpringMVC 会默认装配好一个 LocalValidatorFactoryBean通过在处理方法的入参上标注 Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。 操作演示 导入依赖 !-- 校验注解 --
dependencygroupIdjakarta.platform/groupIdartifactIdjakarta.jakartaee-web-api/artifactIdversion9.1.0/versionscopeprovided/scope
/dependency!-- 校验注解实现--
!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator --
dependencygroupIdorg.hibernate.validator/groupIdartifactIdhibernate-validator/artifactIdversion8.0.0.Final/version
/dependency
!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor --
dependencygroupIdorg.hibernate.validator/groupIdartifactIdhibernate-validator-annotation-processor/artifactIdversion8.0.0.Final/version
/dependency应用校验注解 import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import org.hibernate.validator.constraints.Length;/*** projectName: com.atguigu.pojo*/
public class User {//age 1 age 150Min(10)private int age;//name 3 name.length 6Length(min 3,max 10)private String name;//email 邮箱格式Emailprivate String email;public int getAge() {return age;}public void setAge(int age) {this.age age;}public String getName() {return name;}public void setName(String name) {this.name name;}public String getEmail() {return email;}public void setEmail(String email) {this.email email;}
} handler标记和绑定错误收集 RestController
RequestMapping(user)
public class UserController {/*** Validated 代表应用校验注解! 必须添加!*/PostMapping(save)public Object save(Validated RequestBody User user,//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!BindingResult result){//判断是否有信息绑定错误! 有可以自行处理!if (result.hasErrors()){System.out.println(错误);String errorMsg result.getFieldError().toString();return errorMsg;}//没有,正常处理业务即可System.out.println(正常);return user;}
}测试效果 易混总结 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 注解只能用于字符串类型的校验。 总之这三种注解都是用于校验字段值是否为空的注解但是其校验规则和用法有所不同。在进行数据校验时需要根据具体情况选择合适的注解进行校验。
4.文件上传和下载
4.1 文件上传 文件上传表单页面 位置index.html 第一点请求方式必须是 POST第二点请求体的编码方式必须是 multipart/form-data通过 form 标签的 enctype 属性设置第三点使用 input 标签、type 属性设置为 file 来生成文件上传框 !DOCTYPE html
html langen
headmeta charsetUTF-8titleTitle/title
/head
bodyform action/save/picture methodpost enctypemultipart/form-data昵称input typetext namenickName value龙猫 /br/头像input typefile nameheadPicture /br/背景input typefile namebackgroundPicture /br/button typesubmit保存/button/form
/body
/html springmvc环境要求 pom.xml添加依赖 !-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --
dependencygroupIdcommons-fileupload/groupIdartifactIdcommons-fileupload/artifactIdversion1.3.1/version
/dependency配置文件上传处理器(springmvc配置) !-- 文件上传处理器,可处理 multipart/* 请求并将其转换为 MultipartFile 对象--
bean idmultipartResolverclassorg.springframework.web.multipart.support.StandardServletMultipartResolver
/beanCommonsMultipartResolver的bean的id必须是multipartResolver 如果不是这个值会在上传文件时报错 在 web.xml 文件中添加 Multipart 配置 servletservlet-nameyourAppServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classmultipart-config!-- 定义文件上传时所需的最大值单位为字节 --max-file-size10485760/max-file-size!-- 定义单个上传文件的最大值单位为字节 --max-request-size20971520/max-request-size!-- 定义内存中存储文件的最大值超过此大小的文件会写入到硬盘中 --file-size-threshold5242880/file-size-threshold/multipart-configload-on-startup1/load-on-startup
/servlet低版本web.xml约束文件会爆红不管担心继续启动即可 历史Spring MVC 6之前通常使用的是 CommonsMultipartResolver 来解析文件上传请求。但是在 Spring MVC 6中此类已被移除Spring 官方推荐使用 StandardServletMultipartResolver 或 MockMultipartResolver 来替代。 handler方法接收数据 /*** 上传的文件使用 MultipartFile 类型接收其相关数据* param nickName* param picture* param backgroundPicture* return* throws IOException*/
PostMapping (picture)
public String upload(String nickName, RequestPart(headPicture) MultipartFile picture, RequestPart(backgroundPicture)MultipartFile backgroundPicture) throws IOException {System.out.println(nickName);String inputName picture.getName();System.out.println(文件上传表单项的 name 属性值 inputName);// 获取这个数据通常都是为了获取文件本身的扩展名String originalFilename picture.getOriginalFilename();System.out.println(文件在用户本地原始的文件名 originalFilename);String contentType picture.getContentType();System.out.println(文件的内容类型 contentType);boolean empty picture.isEmpty();System.out.println(文件是否为空 empty);long size picture.getSize();System.out.println(文件大小 size);byte[] bytes picture.getBytes();System.out.println(文件二进制数据的字节数组 Arrays.asList(bytes));InputStream inputStream picture.getInputStream();System.out.println(读取文件数据的输入流对象 inputStream);Resource resource picture.getResource();System.out.println(代表当前 MultiPartFile 对象的资源对象 resource);return home;
} MultipartFile接口 文件转存 底层机制 本地转存 转存代码演示 ……// 1、准备好保存文件的目标目录
// ①File 对象要求目标路径是一个物理路径在硬盘空间里能够直接找到文件的路径
// ②项目在不同系统平台上运行要求能够自动兼容、适配不同系统平台的路径格式
// 例如Window系统平台的路径是 D:/aaa/bbb 格式
// 例如Linux系统平台的路径是 /ttt/uuu/vvv 格式
// 所以我们需要根据『不会变的虚拟路径』作为基准动态获取『跨平台的物理路径』
// ③虚拟路径浏览器通过 Tomcat 服务器访问 Web 应用中的资源时使用的路径
String destFileFolderVirtualPath /head-picture;// ④调用 ServletContext 对象的方法将虚拟路径转换为真实物理路径
String destFileFolderRealPath servletContext.getRealPath(destFileFolderVirtualPath);// 2、生成保存文件的文件名
// ①为了避免同名的文件覆盖已有文件不使用 originalFilename所以需要我们生成文件名
// ②我们生成文件名包含两部分文件名本身和扩展名
// ③声明变量生成文件名本身
String generatedFileName UUID.randomUUID().toString().replace(-,);// ④根据 originalFilename 获取文件的扩展名
String fileExtname originalFilename.substring(originalFilename.lastIndexOf(.));// ⑤拼装起来就是我们生成的整体文件名
String destFileName generatedFileName fileExtname;// 3、拼接保存文件的路径由两部分组成
// 第一部分文件所在目录
// 第二部分文件名
String destFilePath destFileFolderRealPath / destFileName;// 4、创建 File 对象对应文件具体保存的位置
File destFile new File(destFilePath);// 5、执行转存
picture.transferTo(destFile);……缺陷 Web 应用重新部署时通常都会清理旧的构建结果此时用户以前上传的文件会被删除导致数据丢失。项目运行很长时间后会导致上传的文件积累非常多体积非常大从而拖慢 Tomcat 运行速度。当服务器以集群模式运行时文件上传到集群中的某一个实例其他实例中没有这个文件就会造成数据不一致。不支持动态扩容一旦系统增加了新的硬盘或新的服务器实例那么上传、下载时使用的路径都需要跟着变化导致 Java 代码需要重新编写、重新编译进而导致整个项目重新部署。 文件服务器转存推荐 好处 不受 Web 应用重新部署影响在应用服务器集群环境下不会导致数据不一致针对文件读写进行专门的优化性能有保障能够实现动态扩容 文件服务器类型 第三方平台 阿里的 OSS 对象存储服务七牛云 自己搭建服务器FastDFS 等 上传到其他模块 这种情况肯定出现在分布式架构中常规业务功能不会这么做采用这个方案的一定是特殊情况这种情况极其少见。 在 MultipartFile 接口中有一个对应的方法 /*** Return a Resource representation of this MultipartFile. This can be used* as input to the {code RestTemplate} or the {code WebClient} to expose* content length and the filename along with the InputStream.* return this MultipartFile adapted to the Resource contract* since 5.1*/
default Resource getResource() {return new MultipartFileResource(this);
}注释中说这个 Resource 对象代表当前 MultipartFile 对象输入给 RestTemplate 或 WebClient。而 RestTemplate 或 WebClient 就是用来在 Java 程序中向服务器端发出请求的组件。
4.2 文件下载
在 Spring MVC 中ResponseEntity 是用于表示 HTTP 响应的一个类它既能设置响应体的内容也能设置响应头相关的信息。
ResponseEntity 可以封装一个 HTTP 响应包括响应体、响应头和响应状态码等属性并将其发送回客户端。它提供了一种灵活的方式来表示 HTTP 响应可以用于处理 RESTful API、文件下载、异常处理等应用场景。
演示json数据返回
GetMapping(/users/{age})
public ResponseEntityUser getUser(PathVariable(age) int age) {User user new User();user.setAge(age);user.setEmail(test);user.setName(二狗子);return ResponseEntity.ok(user);
}演示文件下载代码
Autowired
private ServletContext servletContext;RequestMapping(/download/file)
public ResponseEntitybyte[] downloadFile() {// 1.获取要下载的文件的输入流对象// 这里指定的路径以 Web 应用根目录为基准InputStream inputStream servletContext.getResourceAsStream(/images/mi.jpg);try {// 2.将要下载的文件读取到字节数组中// ①获取目标文件的长度int len inputStream.available();// ②根据目标文件长度创建字节数组byte[] buffer new byte[len];// ③将目标文件读取到字节数组中inputStream.read(buffer);// 3.封装响应消息头// ①创建MultiValueMap接口类型的对象实现类是HttpHeadersMultiValueMap responseHeaderMap new HttpHeaders();// ②存入下载文件所需要的响应消息头responseHeaderMap.add(Content-Disposition, attachment; filenamemi.jpg);// ③创建ResponseEntity对象ResponseEntitybyte[] responseEntity new ResponseEntity(buffer, responseHeaderMap, HttpStatus.OK);// 4.返回responseEntity对象return responseEntity;} catch (IOException e) {e.printStackTrace();} finally {if (inputStream ! null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}return null;
}七、SpringMVC底层原理
1. 启动配置流程
1.1 Servlet 生命周期回顾 生命周期环节调用的方法时机次数创建对象无参构造器默认第一次请求 修改Web应用启动时一次初始化init(ServletConfig servletConfig)创建对象后一次处理请求service(ServletRequest servletRequest, ServletResponse servletResponse)接收到请求后多次清理操作destroy()Web应用卸载之前一次
1.2 初始化操作调用路线图
类和接口之间的关系: 调用线路图:
调用线路图所示是方法调用的顺序但是实际运行的时候本质上都是调用 DispatcherServlet 对象的方法。包括这里涉及到的接口的方法也不是去调用接口中的『抽象方法』。毕竟抽象方法是没法执行的。抽象方法一定是在某个实现类中有具体实现才能被调用。
而对于最终的实现类DispatcherServlet 来说所有父类的方法最后也都是在 DispatcherServlet 对象中被调用的。 1.3 SpringMVC IoC 容器创建
所在类org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext createWebApplicationContext(Nullable ApplicationContext parent) {Class? contextClass getContextClass();if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException(Fatal initialization error in servlet with name getServletName() : custom WebApplicationContext class [ contextClass.getName() ] is not of type ConfigurableWebApplicationContext);}// 通过反射创建 IOC 容器对象ConfigurableWebApplicationContext wac (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);wac.setEnvironment(getEnvironment());// 设置父容器wac.setParent(parent);String configLocation getContextConfigLocation();if (configLocation ! null) {wac.setConfigLocation(configLocation);}// 配置并且刷新在这个过程中就会去读XML配置文件并根据配置文件创建bean、加载各种组件configureAndRefreshWebApplicationContext(wac);return wac;
}1.4 将 SpringMVC IoC容器对象存入应用域
所在类org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac null;if (this.webApplicationContext ! null) {wac this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {if (cwac.getParent() null) {//springmvc容器认另一个容器spring作为父容器cwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}if (wac null) {wac findWebApplicationContext();}if (wac null) {// 创建 IOC 容器wac createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {synchronized (this.onRefreshMonitor) {onRefresh(wac);}}if (this.publishContext) {// 获取存入应用域时专用的属性名String attrName getServletContextAttributeName();// 存入getServletContext().setAttribute(attrName, wac);}return wac;
}看到这一点的意义SpringMVC 有一个工具方法可以从应用域获取 IOC 容器对象的引用。
工具类org.springframework.web.context.support.WebApplicationContextUtils
工具方法getWebApplicationContext()
Nullable
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}作用将来假如我们自己开发时在IOC容器之外需要从IOC容器中获取bean那么就可以通过这个工具方法获取IOC容器对象的引用。IOC容器之外的场景会有很多比如在一个我们自己创建的Filter中。
1.5 请求映射初始化
FrameworkServlet.createWebApplicationContext()→configureAndRefreshWebApplicationContext()→wac.refresh()→触发刷新事件→org.springframework.web.servlet.DispatcherServlet.initStrategies()→org.springframework.web.servlet.DispatcherServlet.initHandlerMappings() 1.6 小结
整个启动过程我们关心如下要点
DispatcherServlet 本质上是一个 Servlet所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。DispatcherServlet 的父类是 FrameworkServlet。 FrameworkServlet 负责框架本身相关的创建和初始化。DispatcherServlet 负责请求处理相关的初始化。 FrameworkServlet 创建 IOC 容器对象之后会存入应用域。FrameworkServlet 完成初始化会调用 IOC 容器的刷新方法。刷新方法完成触发刷新事件在刷新事件的响应函数中调用 DispatcherServlet 的初始化方法。在 DispatcherServlet 的初始化方法中初始化了请求映射等。
2. 请求处理流程原理
2.1 总体阶段 流程描述 目标 handler 方法执行前 建立调用链确定整个执行流程拦截器的 preHandle() 方法注入请求参数准备目标 handler 方法所需所有参数 调用目标 handler 方法目标 handler 方法执行后 拦截器的 postHandle() 方法渲染视图拦截器的 afterCompletion() 方法 核心代码 整个请求处理过程都是doDispatch()方法在宏观上协调和调度把握了这个方法就理解了 SpringMVC 总体上是如何处理请求的。 所在类DispatcherServlet 所在方法doDispatch() 核心方法中的核心代码 // Actually invoke the handler.
mv ha.handle(processedRequest, response, mappedHandler.getHandler());2.2 调用前阶段 建立调用链 全类名org.springframework.web.servlet.HandlerExecutionChain 拦截器索引默认是 -1说明开始的时候它指向第一个拦截器前面的位置。每执行一个拦截器就把索引向前移动一个位置。所以这个索引每次都是指向当前拦截器。所以它相当于拦截器的指针。 对应操作 所在类org.springframework.web.servlet.handler.AbstractHandlerMapping 所在方法getHandlerExecutionChain() 关键操作 把目标handler对象存入 把当前请求要经过的拦截器存入 结论调用链是由拦截器和目标 handler 对象组成的。 调用拦截器preHandle() 所在类org.springframework.web.servlet.DispatcherServlet 所在方法doDispatch() 具体调用细节正序调用 所在类org.springframework.web.servlet.HandlerExecutionChain 所在方法applyPreHandle 从这部分代码我们也能看到为什么拦截器中的 preHandle() 方法通过返回布尔值能够控制是否放行。 每一个拦截器的 preHandle() 方法都返回 trueapplyPreHandle() 方法返回 true被取反就不执行 if 分支继续执行后续操作这就是放行。任何一个拦截器的 preHandle() 方法返回 falseapplyPreHandle() 方法返回 false被取反执行 if 分支return导致 doDispatch() 方法结束不执行后续操作就是不放行。 注入请求参数 相关组件 接口org.springframework.web.servlet.HandlerAdapter 作用字面含义是适配器的意思具体功能有三个 将请求参数绑定到实体类对象中给目标 handler 方法准备所需的其他参数例如 Model、ModelMap、Map……原生 Servlet APIrequest、response、session……BindingResultRequestParam 注解标记的零散请求参数PathVariable 注解标记的路径变量 调用目标 handler 方法 所以 HandlerAdapter 这个适配器是将底层的 HTTP 报文、原生的 request 对象进行解析和封装『适配』到我们定义的 handler 方法上。 创建并获取这个组件 所在类org.springframework.web.servlet.DispatcherServlet 所在方法doDispatch() 具体操作调用目标 handler 方法 所在类org.springframework.web.servlet.DispatcherServlet 所在方法doDispatch() 具体操作注入请求参数 通过反射给对应属性注入请求参数应该是下面的过程 获取请求参数名称将请求参数名称首字母设定为大写在首字母大写后的名称前附加 set得到目标方法名通过反射调用 setXxx() 方法
2.3调用后阶段 调用拦截器的 postHandle() 方法 所在类org.springframework.web.servlet.DispatcherServlet 所在方法doDispatch() 调用细节从拦截器集合长度 - 1 开始循环循环到 0 为止。所以是倒序执行从而让各个拦截器形成嵌套执行的效果和AOP有异曲同工之妙。 渲染视图 所有后续操作的入口: 所在类org.springframework.web.servlet.DispatcherServlet 所在方法doDispatch() 后续细节1处理异常: 所在类org.springframework.web.servlet.DispatcherServlet 所在方法processDispatchResult() 后续细节2渲染视图: 所在类org.springframework.web.servlet.DispatcherServlet 所在方法processDispatchResult() 补充细节模型数据存入请求域的具体位置 所在类org.thymeleaf.context.WebEngineContext.RequestAttributesVariablesMap 所在方法setVariable() 调用拦截器的 afterCompletion() 方法 所在类org.springframework.web.servlet.DispatcherServlet 所在方法processDispatchResult() 调用细节从拦截器索引开始循环直到循环变量 i 被减到 0 为止。这样的效果是前面执行拦截器到哪里就从哪里倒回去执行前面没有执行的拦截器现在也不执行。
2.4 所有断点总结
断点位置基准SpringMVC 版本采用 6.0.6 且源码已经下载包含注释。
所在类所在方法断点行数作用DispatcherServletdoDispatch()1057创建调用链对象DispatcherServletdoDispatch()1064创建 HandlerAdapter 对象DispatcherServletdoDispatch()1076调用拦截器 preHandle()方法DispatcherServletdoDispatch()1081执行目标 handler 方法DispatcherServletdoDispatch()1088调用拦截器 postHandle()方法DispatcherServletdoDispatch()1098执行所有后续操作DispatcherServletprocessDispatchResult()1145处理异常DispatcherServletprocessDispatchResult()1159渲染视图DispatcherServletprocessDispatchResult()1177调用拦截器 afterCompletion()方法
3. ContextLoaderListener
3.1 配置分离相关问题
目前情况DispatcherServlet 加载 spring-mvc.xml此时整个 Web 应用中只创建一个 IoC 容器。将来整合Mybatis、配置声明式事务全部在 spring-mvc.xml 配置文件中配置也是可以的。可是这样会导致配置文件太长不容易维护。
所以想到把配置文件分开
SpringMVC相关spring-mvc.xml 配置文件Spring和Mybatis相关spring-persist.xml 配置文件 (命名随意分离思维)
配置文件分开之后可以让 DispatcherServlet 加载多个配置文件。例如
servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-*.xml/param-value/init-paramload-on-startup1/load-on-startup
/servlet如果希望这两个配置文件使用不同的机制来加载
DispatcherServlet 加载 spring-mvc.xml 配置文件它们和处理浏览器请求相关ContextLoaderListener 加载 spring-persist.xml 配置文件不需要处理浏览器请求需要配置持久化层相关功能
此时会带来一个新的问题在一个 Web 应用中就会出现两个 IOC 容器
DispatcherServlet 创建一个 IOC 容器ContextLoaderListener 创建一个 IOC 容器
注意本节我们探讨的这个技术方案并不是『必须』这样做而仅仅是『可以』这样做。
3.2 配置ContextLoaderListener 创建 spring-persist.xml 配置 ContextLoaderListener !-- 通过全局初始化参数指定 Spring 配置文件的位置 --
context-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-persist.xml/param-value
/context-paramlistener!-- 指定全类名配置监听器 --listener-classorg.springframework.web.context.ContextLoaderListener/listener-class
/listenerContextLoaderListener 方法名执行时机作用contextInitialized()Web 应用启动时执行创建并初始化 IOC 容器contextDestroyed()Web 应用卸载时执行关闭 IOC 容器 ContextLoader ContextLoader 类是 ContextLoaderListener 类的父类。 指定配置文件位置的参数名: /*** Name of servlet context parameter (i.e., {value}) that can specify the* config location for the root context, falling back to the implementations* default otherwise.* see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION*/
public static final String CONFIG_LOCATION_PARAM contextConfigLocation;初始化 IOC 容器 方法名initWebApplicationContext() 创建 IOC 容器 方法名createWebApplicationContext()
3.3 探讨两个IoC容器之间关系
打印两个 IOC 容器对象的 toString() 方法
Object springIOC servletContext.getAttribute(org.springframework.web.context.WebApplicationContext.ROOT);
log.debug(springIOC.toString());Object springMVCIOC servletContext.getAttribute(org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet);
log.debug(springMVCIOC.toString());打印效果是 **Root **WebApplicationContext, started on Thu Jun 17 14:49:17 CST 2021 …… WebApplicationContext for namespace ‘dispatcherServlet-servlet’, started on Thu Jun 17 14:49:18 CST 2021, parent: Root WebApplicationContext 结论两个组件分别创建的 IOC 容器是父子关系。
父容器ContextLoaderListener 创建的 IOC 容器子容器DispatcherServlet 创建的 IOC 容器
父子关系是如何决定的
Tomcat 在读取 web.xml 之后加载组件的顺序就是监听器、过滤器、Servlet。ContextLoaderListener 初始化时如果检查到有已经存在的根级别 IOC 容器那么会抛出异常。DispatcherServlet 创建的 IOC 容器会在初始化时先检查当前环境下是否存在已经创建好的 IOC 容器。 如果有则将已存在的这个 IOC 容器设置为自己的父容器如果没有则将自己设置为 root 级别的 IOC 容器
DispatcherServlet 创建的 IOC 容器设置父容器的源码截图
所在类org.springframework.web.servlet.FrameworkServlet
所在方法createWebApplicationContext() 3.4 两个IoC容器之间Bean访问 spring-mvc.xml配置方式
context:component-scan base-packagecom.atguigu.spring.component.controller/spring-persist.xml配置方式
context:component-scan base-packagecom.atguigu.spring.component.service,com.atguigu.spring.component.dao/所以bean所属IOC容器的关系
父容器 EmpServiceEmpDao 子容器 EmpController
结论子容器中的 EmpController 装配父容器中的 EmpService 能够正常工作。说明子容器可以访问父容器中的bean。
分析“子可用父父不能用子”的根本原因是子容器中有一个属性 span style“color:blue;font-weight:bold;”getParent()/span 可以获取到父容器这个对象的引用。
源码依据
在 AbstractApplicationContext 类中有 parent 属性在 AbstractApplicationContext 类中有获取 parent 属性的 getParent() 方法子容器可以通过 getParent() 方法获取到父容器对象的引用进而调用父容器中类似 “getBean()” 这样的方法获取到需要的 bean 完成装配而父容器中并没有类似 “getChildren()“ 这样的方法所以没法拿到子容器对象的引用
3.5 有可能重复对象 查看日志确认是否重复创建了对象 Root WebApplicationContext: initialization started …… Creating shared instance of singleton bean ‘helloDao’ Creating shared instance of singleton bean ‘helloHandler’ Creating shared instance of singleton bean ‘helloService’
…… Root WebApplicationContext initialized in 1150 ms Refreshing WebApplicationContext for namespace ‘dispatcherServlet-servlet’ ……
Creating shared instance of singleton bean ‘helloDao’ Creating shared instance of singleton bean ‘helloHandler’ Creating shared instance of singleton bean ‘helloService’
重复创建对象的问题
浪费内存空间两个 IOC 容器能力是不同的 spring-mvc.xml仅配置和处理请求相关的功能。所以不能给 service 类附加声明式事务功能。 结论基于 spring-mvc.xml 配置文件创建的 EmpService 的 bean 不带有声明式事务的功能 影响DispatcherServlet 处理浏览器请求时会调用自己创建的 EmpController然后再调用自己创建的EmpService而这个 EmpService 是没有事务的所以处理请求时span style“color:blue;font-weight:bold;”没有事务功能的支持/span。 spring-persist.xml配置声明式事务。所以可以给 service 类附加声明式事务功能。 结论基于 spring-persist.xml 配置文件创建的 EmpService 有声明式事务的功能 影响由于 DispatcherServlet 的 IOC 容器会优先使用自己创建的 EmpController进而装配自己创建的EmpService所以基于 spring-persist.xml 配置文件创建的有声明式事务的 EmpService 用不上。
解决重复创建对象的问题
解决方案一[建议使用]
让两个配置文件配置自动扫描的包时各自扫描各自的组件。
SpringMVC 就扫描 XxxHandler、XXXControllerSpring 扫描 XxxService 和 XxxDao
解决方案二
如果由于某种原因必须扫描同一个包确实存在重复创建对象的问题可以采取下面的办法处理。
spring-mvc.xml 配置文件在整体扫描的基础上进一步配置仅包含被 Controller 注解标记的类。spring-persist.xml 配置在整体扫描的基础上进一步配置排除被 Controller 注解标记的类。
具体spring-mvc.xml配置文件中的配置方式如下
!-- 两个Spring的配置文件扫描相同的包 --
!-- 为了解决重复创建对象的问题需要进一步制定扫描组件时的规则 --
!-- 目标『仅』包含Controller注解标记的类 --
!-- use-default-filtersfalse表示关闭默认规则表示什么都不扫描此时不会把任何组件加入IOC容器再配合context:include-filter实现“『仅』包含”效果 --
context:component-scan base-packagecom.atguigu.spring.component use-default-filtersfalse!-- context:include-filter标签配置一个“扫描组件时要包含的类”的规则追加到默认规则中 --!-- type属性指定规则的类型根据什么找到要包含的类现在使用annotation表示基于注解来查找 --!-- expression属性规则的表达式。如果type属性选择了annotation那么expression属性配置注解的全类名 --context:include-filter typeannotation expressionorg.springframework.stereotype.Controller/
/context:component-scan具体spring-persist.xml配置文件中的配置方式如下
!-- 两个Spring的配置文件扫描相同的包 --
!-- 在默认规则的基础上排除标记了Controller注解的类 --
context:component-scan base-packagecom.atguigu.spring.component!-- 配置具体排除规则把标记了Controller注解的类排除在扫描范围之外 --context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/
/context:component-scan3.5 小结
DispatcherServlet 和 ContextLoaderListener 并存 DispatcherServlet 负责加载 SpringMVC 的配置文件例如spring-mvc.xmlContextLoaderListener 负责加载 Spring 的配置文件例如spring-persist.xml 两个 IOC 容器的关系 ContextLoaderListener 创建的容器是父容器DispatcherServlet 创建的容器是子容器 bean 的装配 子容器可以访问父容器中的 bean父容器不能访问子容器中的 bean 两个容器扫描同一个包会导致重复创建对象 解决办法一各自扫描各自的包解决办法二 DispatcherServlet 创建的容器仅扫描 handlerContextLoaderListener 创建的容器不扫描 handler
通过遵循 RESTful 架构的设计原则可以构建出易于理解、可扩展、松耦合和可重用的 Web 服务。RESTful API 的特点是简单、清晰并且易于使用和理解它们使用标准的 HTTP 方法和状态码进行通信不需要额外的协议和中间件。
RESTful 架构通常用于构建 Web API提供数据的传输和操作。它可以用于各种应用场景包括客户端-服务器应用、单页应用SPA、移动应用程序和微服务架构等。
总而言之RESTful 是一种基于 HTTP 和标准化的设计原则的软件架构风格用于设计和实现可靠、可扩展和易于集成的 Web 服务和应用程序