spring高级篇(四)

1、DispatcherServlet

        DispatcherServlet 是 Spring MVC 中的一个关键组件,用于处理 Web 请求并将其分发给相应的处理器(Controller)进行处理。它是一个 Servlet,作为前端控制器(Front Controller)的核心,负责协调整个 Spring MVC 框架的请求处理过程。

        其主要作用是将请求进行分发和转发,使得每个请求能够被正确的处理器处理,并将处理结果返回给客户端。它的配置通常在 Spring MVC 的配置文件中进行,可以配置拦截器、异常处理器、视图解析器等,以定制请求处理流程。

        DispatcherServlet 的工作流程:

  • 接收请求:客户端发送请求到 DispatcherServlet。
  • 请求处理:DispatcherServlet 根据请求的 URL 找到对应的 HandlerMapping(处理器映射器),然后将请求分发给相应的 Controller(处理器)。
  • 处理请求:Controller 处理请求,并返回一个 ModelAndView 对象,其中包含了视图名和模型数据。
  • 视图解析:DispatcherServlet 根据返回的 ModelAndView 对象,通过 ViewResolver(视图解析器)找到对应的视图。
  • 渲染视图:视图解析器将视图渲染成最终的 HTML 输出。
  • 返回响应:DispatcherServlet 将渲染好的视图作为响应返回给客户端。
1.1、DispatcherServlet的初始化时机

        通过一个案例来说明:

        创建一个配置类:

@Configuration
@ComponentScan
public class Config {/***  注册内嵌web容器工厂 tomcat容器*/@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory(){return new TomcatServletWebServerFactory();}/*** 创建DispatcherServlet* @return*/@Beanpublic DispatcherServlet dispatcherServlet(){return new DispatcherServlet();}/*** 注册DispatcherServlet springmvc入口* @param dispatcherServlet* @return*/@Beanpublic DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");return registrationBean;}}

        通常是在DispatcherServlet首次使用时,才被tomcat容器初始化:

        tomcat启动时DispatcherServlet并未初始化

        在浏览器访问8080时,DispatcherServlet才被tomcat容器初始化。

        可以在dispatcherServletRegistrationBean()方法中设置tomcat容器启动时即进行DispatcherServlet初始化:

   /*** 注册DispatcherServlet springmvc入口* @param dispatcherServlet* @return*/@Beanpublic DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");//设置tomcat容器启动时即进行DispatcherServlet初始化registrationBean.setLoadOnStartup(1);return registrationBean;}

        在设置tomcat容器启动时即进行DispatcherServlet初始化的.setLoadOnStartup();方法中,采用的是硬编码的方式。可以改进成从配置文件中读取对应的值

        在类上加入@PropertySource、@EnableConfigurationProperties注解:

@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
  • @PropertySource: 用于指定一个或多个属性源文件的位置,Spring 会在启动时加载指定的属性源文件,并将其中的属性值加载到 Spring 的环境(Environment)中。
  • @EnableConfigurationProperties:@ConfigurationProperties注解标注的类注册为Spring Bean,并将其配置属性注入到这些Bean中。

        WebMvcProperties和ServerProperties都是被@ConfigurationProperties 注解标注的类:

        Spring 在启动时会加载 application.properties文件,并将其中的属性值注入到 WebMvcProperties和 ServerProperties类的实例中,使得它们可以被其他组件注入和使用:

/*** 注册DispatcherServlet springmvc入口* @param dispatcherServlet* @return*/@Beanpublic DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet,WebMvcProperties webMvcProperties){DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");//设置tomcat容器启动时即进行DispatcherServlet初始化registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());return registrationBean;}

        补充:从配置文件读取值的优点

  1. 灵活性和可维护性:将应用程序的配置信息放在外部配置文件中,可以使配置信息与应用程序的代码分离。这样做使得修改配置信息变得更加方便,不需要修改源代码,只需要修改配置文件即可,提高了应用程序的灵活性和可维护性。

  2. 安全性:敏感信息(如数据库连接信息、密码等)通常不应该硬编码在代码中,而是应该放在配置文件中,并且限制访问这些配置文件的权限,以提高应用程序的安全性。

  3. 跨环境适应性:不同的环境(如开发环境、测试环境、生产环境)可能需要不同的配置信息。通过使用配置文件,可以很容易地在不同的环境中部署应用程序,只需要修改相应的配置文件即可。

  4. 便于集中管理:将所有配置信息放在一个或少数几个配置文件中,有助于集中管理配置,降低了维护成本。

  5. 便于扩展:当应用程序需要添加新的功能或模块时,通常需要修改或添加一些配置信息。通过使用配置文件,可以轻松地扩展应用程序的功能,而无需修改源代码。

1.2、DispatcherServlet的初始化执行的操作

        在初始化时,执行了onRefresh(ApplicationContext context) 方法:

        方法的内部又调用了initStrategies(context); 在该方法中,又初始化了不同的组件:

  • initMultipartResolver(context): 用于处理文件上传请求。
  • initLocaleResolver(context); 用于解析客户端的区域信息,以确定合适的本地化。
  • initThemeResolver(context);用于解析请求中的主题信息,以确定所使用的页面主题。
  • initHandlerMappings(context);用于将请求映射到相应的处理器(Controller)上。(重点)
  • initHandlerAdapters(context);用于将请求分派给相应的处理器方法。
  • initHandlerExceptionResolvers(context);用于处理请求过程中发生的异常。
  • initRequestToViewNameTranslator(context);用于将请求映射到视图名称上。
  • initViewResolvers(context);用于将逻辑视图名称解析为具体的视图实现。
  • initFlashMapManager(context);用于处理重定向时的 Flash 属性传递。
1.2.1、HandlerMappings        

          initHandlerMappings(context); 方法的源码:大致的意思是,首先会在自己的容器中找有误HandlerMappings,如果没有就回去父容器去找:

        父子容器中都没有,就会去初始化一个默认的HandlerMappings(在DispatcherServlet.properties中):

        如果需要演示initHandlerMappings具体的执行过程,需要手动将RequestMappingHandlerMapping作为bean放入到容器中,因为之前提到,如果父子容器中都没有HandlerMapping,就会使用默认的RequestMappingHandlerMapping。

        而默认的RequestMappingHandlerMapping 无法达到演示的效果,在配置类中加入:

     @Beanpublic RequestMappingHandlerMapping requestMappingHandlerMapping(){return new RequestMappingHandlerMapping();}

        在主类中获取RequestMappingHandlerMapping,并且通过.getHandlerMethods(); 方法获取映射结果(RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map):

public class A18 {public static void main(String[] args) throws Exception {AnnotationConfigServletWebServerApplicationContext context =new AnnotationConfigServletWebServerApplicationContext(Config.class);//解析@RequestMapping 和其派生注解 生成路径与控制器的派生关系 在控制器初始化时生成RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);//获取映射结果//RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map//k:请求方式 路径{ /test4}  v 方法信息com.itbaima.a18.Controller1#test4()Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();handlerMethods.forEach((k,v)->{System.out.println( k + "=" + v);});}
}

        Controller中有三个方法:

@Controller
public class Controller1 {private static final Logger log = LoggerFactory.getLogger(Controller1.class);@GetMapping("/test1")public ModelAndView test1() throws Exception {log.debug("test1()");return null;}@PostMapping("/test2")public ModelAndView test2(@RequestParam("name") String name) {log.debug("test2({})", name);return null;}@PutMapping("/test3")public ModelAndView test3(String token) {log.debug("test3({})", token);return null;}}

        控制台打印的结果:

{PUT /test3}=com.itbaima.a18.Controller1#test3(String)
{POST /test2}=com.itbaima.a18.Controller1#test2(String)
{GET /test1}=com.itbaima.a18.Controller1#test1()

        当浏览器发送请求时,DispatcherServlet会调用.getHandler() 方法,根据请求路径Key 获取RequestMappingHandlerMapping 封装的 Map 对应的Value(HandlerMethod)

        //发送请求了//获取的结果会包装在拦截器链中//HandlerExecutionChain with [com.itbaima.a18.Controller1#test1()] and 0 interceptorsHandlerExecutionChain chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/test1"));System.out.println(chain);

HandlerExecutionChain with [com.itbaima.a18.Controller1#test1()] and 0 interceptors

 1.2.2、HandlerAdapters

        为了演示,同样需要将RequestMappingHandlerAdapter注册成Bean。但是其核心invokeHandlerMethod() 方法的修饰符是protect,所以采用子类继承的方式,将子类注册成为Bean:

/*** 继承RequestMappingHandlerAdapter 重写invokeHandlerMethod 的修饰符为public*/
public class RequestMappingHandlerAdapterSub extends RequestMappingHandlerAdapter {@Overridepublic ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {return super.invokeHandlerMethod(request, response, handlerMethod);}
}
    /*** 向容器中放入RequestMappingHandlerAdapterSub 替换掉默认的* @return RequestMappingHandlerAdapterSub*/@Beanpublic RequestMappingHandlerAdapterSub requestMappingHandlerAdapterSub(){RequestMappingHandlerAdapterSub handlerAdapterSub = new RequestMappingHandlerAdapterSub();return handlerAdapterSub;}

        上面提到,当浏览器发送请求时,DispatcherServlet会调用.getHandler() 方法,根据请求路径Key 获取RequestMappingHandlerMapping 封装的 Map 对应的Value(HandlerMethod)。

        此时已经得到了HandlerMethod,HandlerAdapters负责将请求分派给相应的HandlerMethod方法。

  handlerAdapterSub.invokeHandlerMethod(request,response, ((HandlerMethod) chain.getHandler()));

         在HandlerAdapters中,有很多自带的解析器,可以分为两类:

  • 参数解析器:
//handlerAdapterSub的参数解析器,用于解析@RequestParam等注解信息
System.out.println("<<<<<<<<<<<<<参数解析器<<<<<<<<<<<<<");
for (HandlerMethodArgumentResolver argumentResolver : handlerAdapterSub.getArgumentResolvers()) {System.out.println(argumentResolver);
}

        其中第一个就是解析参数中@RequestParam注解的

  • 返回值解析器:
System.out.println("<<<<<<<<<<<<<返回值解析器<<<<<<<<<<<<<");
for (HandlerMethodReturnValueHandler returnValueHandler : handlerAdapterSub.getReturnValueHandlers()) {System.out.println(returnValueHandler);
}

        其中第一个是解析ModelAndView返回值类型的

       

         我们还可以自定义参数解析器和返回值解析器:

         现在有一个方法test3,参数上加了自定义的@Token注解

    @PutMapping("/test3")public ModelAndView test3(@Token String token) {log.info("test3({})", token);return null;}

        显然通过HandlerAdapters自带的参数解析器是无法解析的,需要自定义参数解析器

        在自定义参数解析器中我们进行了两步操作:

  • 判断参数上是否加上了自定义类型的注解
  • 对加上了自定义注解的方法进行操作
/*** 自定义@Token注解的参数解析器*/
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {/*** 判断是否加了@Token注解 只对有@Token注解的方法生效* @param methodParameter* @return*/@Overridepublic boolean supportsParameter(MethodParameter methodParameter) {Token token = methodParameter.getParameterAnnotation(Token.class);return token!=null;}/*** 对加了@Token注解的方法执行操作* parameter – 要解析的方法参数。。* mavContainer – 当前请求的 ModelAndViewContainer* webRequest – 当前请求* binderFactory – 用于创建 WebDataBinder 实例的工厂*/@Overridepublic Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {return nativeWebRequest.getHeader("token");}
}

        还需要在注册RequestMappingHandlerAdapter Bean的时候设置自定义的参数解析器:

//将自定义的TokenArgumentResolver加入RequestMappingHandlerAdapterSub
handlerAdapterSub.setCustomArgumentResolvers(Collections.singletonList(new TokenArgumentResolver()));

        在controller中还有一个方法,在方法上加入了自定义的@Yml注解,是希望将返回的结果转成yml格式(和将加上了@ResponseBody注解的方法的返回值转成JSON返回给前端是一个道理)

    @RequestMapping("/test4")@Ymlpublic User test4() {log.debug("test4");return new User("张三", 18);}

        需要自定义返回值处理器:

        在返回值处理器中,进行的两步操作和参数处理器中的操作类似。

/*** 自定义@YML返回值解析器*/
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {@Overridepublic boolean supportsReturnType(MethodParameter returnType) {Yml yml = returnType.getMethodAnnotation(Yml.class);return yml!= null;}/*** returnValue – 处理程序方法返回的值* returnType – 返回值的类型。* mavContainer – 当前请求的 ModelAndViewContainer* webRequest – 当前请求*/@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {String str = new Yaml().dump(returnValue);HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);response.setContentType("text/plain;charset=utf-8");response.getWriter().print(str);//设置请求已响应完成mavContainer.setRequestHandled(true);}
}

        还需要在注册RequestMappingHandlerAdapter Bean的时候设置自定义的返回值解析器:

 //将自定义的YmlReturnValueHandler加入RequestMappingHandlerAdapterSub
handlerAdapterSub.setCustomReturnValueHandlers(Collections.singletonList(new YmlReturnValueHandler()));

        下面通过debug的方式演示一下自定义参数解析器的执行过程,加深一下印象:

        启动程序,首先执行到发送模拟请求:

        将请求分派到了HandlerAdapters:

        进入自定义参数解析器,判断参数中是否加入了@Token注解:

        对加了@Token注解的方法执行操作:

        执行test3()方法:


 下一篇会重点介绍参数解析器和返回值处理器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/315095.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习神经网络的AI图片上色DDcolor系统源码

第一步&#xff1a;DDcolor介绍 DDColor 是最新的 SOTA 图像上色算法&#xff0c;能够对输入的黑白图像生成自然生动的彩色结果&#xff0c;使用 UNet 结构的骨干网络和图像解码器分别实现图像特征提取和特征图上采样&#xff0c;并利用 Transformer 结构的颜色解码器完成基于视…

以生命健康为中心的物联网旅居养老运营平台

随着科技的飞速发展和人口老龄化的日益加剧&#xff0c;养老问题逐渐成为社会关注的焦点。传统的养老模式已经难以满足现代老年人的多元化需求&#xff0c;因此&#xff0c;构建一个以生命健康为中心的物联网旅居养老运营平台显得尤为重要。 以生命健康为中心的物联网旅居养老运…

未来已来:解锁AGI的无限潜能与挑战

未来已来&#xff1a;解锁AGI的无限潜能与挑战 引言 假设你有一天醒来&#xff0c;发现你的智能手机不仅提醒你今天的日程&#xff0c;还把你昨晚做的那个奇怪的梦解释了一番&#xff0c;并建议你可能需要减少咖啡摄入量——这不是科幻电影的情节&#xff0c;而是人工通用智能…

本地认证的密码去哪了?怎么保证安全的?

1. windows登录的明文密码&#xff0c;存储过程是怎么样的&#xff1f;密文存在哪个文件下?该文件是否可以打开&#xff0c;并且查看到密文&#xff1f; 系统将输入的明文密码通过hash算法转为哈希值&#xff0c;且输入的值会在内存中立即删除无法查看。 然后将密文存放在C:…

Vue3+Vite开发的项目进行加密打包

本文主要介绍Vue3+Vite开发的项目如何进行加密打包。 目录 一、vite简介二、混淆工具三、使用方法1. 安装插件:2. 配置插件:3. 运行构建:4. 自定义混淆选项:5. 排除文件:下面是Vue 3+Vite开发的项目进行加密打包的方法。 一、vite简介 Vite 是一个由 Evan You 创造的现代…

MultiHeadAttention在Tensorflow中的实现原理

前言 通过这篇文章&#xff0c;你可以学习到Tensorflow实现MultiHeadAttention的底层原理。 一、MultiHeadAttention的本质内涵 1.Self_Atention机制 MultiHeadAttention是Self_Atention的多头堆嵌&#xff0c;有必要对Self_Atention机制进行一次深入浅出的理解&#xff0c;这…

AJAX——案例

1.商品分类 需求&#xff1a;尽可能同时展示所有商品分类到页面上 步骤&#xff1a; 获取所有的一级分类数据遍历id&#xff0c;创建获取二级分类请求合并所有二级分类Promise对象等待同时成功后&#xff0c;渲染页面 index.html代码 <!DOCTYPE html> <html lang&qu…

ssh 文件传输:你应该掌握的几种命令行工具

这篇文章主要分享一下我使用过的 ssh 传输文件的进阶路程&#xff0c;从 scp -> lrzsz -> trzsz&#xff0c;希望能给你带来一些帮助&#xff5e; scp scp 命令可以用于在 linux 系统之间复制文件&#xff0c;具体的语法可以参考下图 其实使用起来也还比较方便&#x…

【Docker】Docker 实践(三):使用 Dockerfile 文件构建镜像

Docker 实践&#xff08;三&#xff09;&#xff1a;使用 Dockerfile 文件构建镜像 1.使用 Dockerfile 文件构建镜像2.Dockerfile 文件详解 1.使用 Dockerfile 文件构建镜像 Dockerfile 是一个文本文件&#xff0c;其中包含了一条条的指令&#xff0c;每一条指令都用于构建镜像…

智慧码头港口:施工作业安全生产AI视频监管与风险预警平台方案

一、建设思路 随着全球贸易的快速发展&#xff0c;港口作为连接海洋与内陆的关键节点&#xff0c;其运营效率和安全性越来越受到人们的关注。为了提升港口的运营效率和安全性&#xff0c;智慧港口视频智能监控系统的建设显得尤为重要。 1&#xff09;系统架构设计 系统应该采…

针对icon报错

针对上篇文章生成图标链接中图标报错 C# winfrom应用程序添加图标-CSDN博客 问题&#xff1a;参数“picture”必须是可用作Icon的参数 原因&#xff1a;生成的ico图标类型不匹配 解决方法&#xff1a; 更改导出的ico类型

下载学浪视频,小浪助手一键搞定

小浪助手可以一键获取课程&#xff0c;一键根据课程获取视频列表&#xff0c;而且内置了2大下载器&#xff0c;N_m3u8和逍遥一仙下载器 小浪助手我已经打包好了&#xff0c;有需要的自己取一下 学浪下载工具链接&#xff1a;https://pan.baidu.com/s/1_Sg-EGGXKc4bMW-NPqUqvg…

第55篇:创建Nios II工程之Hello_World<一>

Q&#xff1a;本期我们开始介绍创建Platform Designer系统&#xff0c;并设计基于Nios II Processor的Hello_world工程。 A&#xff1a;设计流程和实验原理&#xff1a;需要用到的IP组件有Clock Source、Nios II Processor、On-Chip Memory、JTAG UART和System ID外设。Nios I…

Maven多模块快速升级超好用Idea插件-MPVP

功能&#xff1a;多模块maven项目快速升级指定版本插件&#xff0c;并提供预览和相关升级模块日志能力。 可快速进行版本升级&#xff0c;进行部署到Maven仓库。 安装&#xff1a; 可在idea插件中心进行安装 / 下载资源拖动安装 MPVP(Maven) - IntelliJ IDEs Plugin | Marke…

构建安全高效的前端权限控制系统

✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天开心哦&#xff01;✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; ✨✨ 帅哥美女们&#xff0c;我们共同加油&#xff01;一起进步&am…

数据库变更时,OceanBase如何自动生成回滚 SQL

背景 在开发中&#xff0c;数据的变更与维护工作一般较频繁。当我们执行数据库的DML操作时&#xff0c;必须谨慎考虑变更对数据可能产生的后果&#xff0c;以及变更是否能够顺利执行。若出现意外数据丢失、操作失误或语法错误等情况&#xff0c;我们必须迅速将数据库恢复到变更…

Bayes判别示例数据:鸢尾花数据集

使用Bayes判别的R语言实例通常涉及使用朴素贝叶斯分类器。朴素贝叶斯分类器是一种简单的概率分类器&#xff0c;基于贝叶斯定理和特征之间的独立性假设。在R中&#xff0c;我们可以使用e1071包中的naiveBayes函数来实现这一算法。下面&#xff0c;我将通过一个简单的示例展示如…

npm、yarn与pnpm详解

&#x1f525; npm、yarn与pnpm详解 &#x1f516; 一、npm &#x1f50d; 简介&#xff1a; npm是随Node.js一起安装的官方包管理工具&#xff0c;它为开发者搭建了一个庞大的资源库&#xff0c;允许他们在这个平台上搜索、安装和管理项目所必需的各种代码库或模块。 &#…

Intelij Idea Push失败,出现git Authentication failed(验证失败)

目录 1、出现问题的原因 2、解决之法 1、出现问题的原因 能出现这种问题&#xff0c;最主要的原因是链接对上了&#xff0c;但用户验证失败了&#xff0c;即登录失败。 因为服务器转移或者换了git项目链接&#xff0c;导致你忘记了用户名密码&#xff0c;随意输入之后&…

求三个字符数组最大者(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h> # include <string.h>int main() {//初始化变量值&#xff1b;int i 0;char str[3][20];char string[20];//循环输入3个字符…