spring高级篇(四)

news2024/11/18 11:26:21

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容器
     */
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }

    /**
     * 创建DispatcherServlet
     * @return
     */
    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    /**
     * 注册DispatcherServlet springmvc入口
     * @param dispatcherServlet
     * @return
     */
    @Bean
    public 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
     */
    @Bean
    public 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
     */
    @Bean
    public 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 无法达到演示的效果,在配置类中加入:

     @Bean
    public 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 interceptors
        HandlerExecutionChain 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 {

    @Override
    public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        return super.invokeHandlerMethod(request, response, handlerMethod);
    }
}
    /**
     * 向容器中放入RequestMappingHandlerAdapterSub 替换掉默认的
     * @return RequestMappingHandlerAdapterSub
     */
    @Bean
    public 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
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        Token token = methodParameter.getParameterAnnotation(Token.class);
        return token!=null;
    }

    /**
     * 对加了@Token注解的方法执行操作
     * parameter – 要解析的方法参数。。
     * mavContainer – 当前请求的 ModelAndViewContainer
     * webRequest – 当前请求
     * binderFactory – 用于创建 WebDataBinder 实例的工厂
     */
    @Override
    public 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")
    @Yml
    public User test4() {
        log.debug("test4");
        return new User("张三", 18);
    }

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

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

/**
 * 自定义@YML返回值解析器
 */
public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        Yml yml = returnType.getMethodAnnotation(Yml.class);
        return yml!= null;
    }

    /**
     * returnValue – 处理程序方法返回的值
     * returnType – 返回值的类型。
     * mavContainer – 当前请求的 ModelAndViewContainer
     * webRequest – 当前请求
     */
    @Override
    public 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.coloradmin.cn/o/1629787.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

开发 Chrome 浏览器插件入门

前言 简介 Chrome 插件是扩展 Chrome 浏览器的功能的软件程序。它们可以执行各种任务&#xff0c;例如阻止广告、增强隐私、添加新功能等等。 要开始编写 Chrome 插件&#xff0c;你需要掌握以下&#xff1a; 1.JavaScript语言 2.html 3.css 4.会使用chrome扩展开发手册…

【Jenkins】持续集成与交付 (四):修改Jenkins插件下载地址、汉化

【Jenkins】持续集成与交付 (四):修改Jenkins插件下载地址、汉化 一、修改Jenkins插件下载地址二、汉化Jenkins三、Jenkins 版本查看💖The Begin💖点点关注,收藏不迷路💖 一、修改Jenkins插件下载地址 由于Jenkins官方插件地址下载速度较慢,我们可以通过修改下载地…

本地CPU搭建知识库大模型来体验学习Prompt Engineering/RAG/Agent/Text2sql

目录 1.环境 2.效果 3.概念解析 4.架构图 5. AI畅想 6.涉及到的技术方案 7. db-gpt的提示词 1.环境 基于一台16c 32G的纯CPU的机器来搭建 纯docker 打造 2.效果 3.概念解析 Prompt Engineering &#xff1a; 提示词工程 RAG&#xff1a; 检索增强生成&#xff1b; …

Thinkphp--in-sqlinjection

一、漏洞原理 在 Builder 类的 parseData 方法中&#xff0c;由于程序没有对数据进行很好的过滤&#xff0c;将数据拼接进 SQL 语句&#xff0c;导致 SQL注入漏洞 的产生。 影响版本 5.0.13<ThinkPHP<5.0.15 5.1.0<ThinkPHP<5.1.5 在相应的文件夹位置打开终端…

【Kafka】Windows下安装Kafka(图文记录详细步骤)

【Kafka】Windows下安装Kafka Kafka简介一、Kafka安装前提安装Kafka之前&#xff0c;需要安装JDK、Zookeeper、Scala。1.1、JDK安装&#xff08;version&#xff1a;1.8&#xff09;1.1.1、JDK官网下载1.1.2、JDK网盘下载1.1.3、JDK安装 1.2、Zookeeper安装1.2.1、Zookeeper官网…

SpringCloud(微服务介绍,远程调用RestTemplate,注册中心Nacos,负载均衡Ribbon,环境隔离,进程和线程的区别)【详解】

目录 一、微服务介绍 1. 系统架构的演变 1 单体架构 2 分布式服务 3 微服务 2. SpringCloud介绍 SpringCloud简介 SpringCloud版本 3. 小结 二、远程调用RestTemplate【理解】 1. 服务拆分 1 服务拆分原则 2 服务拆分示例 1) 创建父工程 2) 准备用户服务 1. 用户…

03 Docker入门Dockerfile详解及镜像创建

1.1 使用 Dockerfile 构建镜像 新建一个 Dockerfile 文件vi Dockerfile 将下面的内容复制粘贴进去:## Base Images ## 从天池基础镜像构建(from的base img 根据自己的需要更换,建议使用天池open list镜像链接:https://tianchi.aliyun.com/forum/postDetail?postId=67720) F…

如何利用美国站群服务器实现有效的SEO优化策略?

如何利用美国站群服务器实现有效的SEO优化策略? 在当今数字化时代&#xff0c;SEO优化对于网站的可见性和吸引力至关重要。站群服务器作为一种有效的SEO策略&#xff0c;可以通过多个相关联的网站在不同服务器上的部署&#xff0c;增强网站的权威性和链接多样性。尤其是在利用…

Matplotlib是什么?

一、Matplotlib是什么&#xff1f; Matplotlib是一个Python语言的2D绘图库&#xff0c;它非常广泛地用于数据的可视化。以下是一些主要特点&#xff1a; 多功能性&#xff1a;它允许用户创建各种静态、动态或交互式的图表&#xff0c;如线图、散点图、直方图等。跨平台性&…

2024全国大学生高新技术竞赛——算法智星挑战赛(A~J)

好多都是之前的原题&#xff0c;甚至有上次第二届全国大学生信息技术认证挑战赛的原题&#xff0c;刚打完又来一遍&#xff0c;没绷住。 A. 手机 原题之一&#xff0c;具体出处忘了 最无脑的方法直接用map记录每个按下的值就行了&#xff0c;代码仅供参考。 #include <bit…

Android 11 裁剪系统显示区域(适配异形屏)

概述 在显示技术中&#xff0c;"OverScan"&#xff08;超扫描&#xff09;是一种调整显示图像边界的技术。通常情况下&#xff0c;OverScan 会在显示屏的边缘周围裁剪一小部分图像。这种裁剪是为了确保显示内容在屏幕上的完整可见性&#xff0c;尤其是在老式电视或投…

【缓存服务】⭐️自定义实现一个简易的数据缓存

目录 &#x1f378;前言 &#x1f37b;手写缓存服务 &#xff08;1&#xff09;缓存实体类 &#xff08;2&#xff09;缓存工具类 &#xff08;3&#xff09;测试缓存服务 &#x1f377;已有的缓存工具 &#x1f379;章末 &#x1f378;前言 俗话说 有轮子不用 就是玩 开个…

【kettle003】kettle访问SQL Server数据库并处理数据至execl文件

一直以来想写下基于kettle的系列文章&#xff0c;作为较火的数据ETL工具&#xff0c;也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 熟悉、梳理、总结下Microsoft SQL Server 2022关系数据库相关知识体系 kettle访问SQL Server数…

关于远程桌面端口的优化措施的建议

在信息技术的世界中&#xff0c;远程桌面连接已成为企业、教育和个人用户之间共享信息、协作工作的重要工具。而这一切的背后&#xff0c;都离不开远程桌面端口&#xff08;RDP&#xff0c;Remote Desktop Protocol Port&#xff09;的支持。RDP端口不仅关乎到远程访问的顺畅性…

永磁同步电机SMO负载转矩观测matlab模型。

永磁同步电机SMO负载转矩观测matlab模型。 负载转矩的有效识别是提高伺服驱动系统抗负载扰动性能的关键之一。现在的传统结构的LTID滑模观测器存在频率抖动大&#xff0c;估计精度差的缺点&#xff0c;限制了其在高性能伺服系统中的应用。 本模型推导分析了传统LTID滑模观测器…

LLM大语言模型(十三):ChatGLM3-6B兼容Langchain的Function Call的一步一步的详细转换过程记录

# LangChain&#xff1a;原始prompt System: Respond to the human as helpfully and accurately as possible. You have access to the following tools: Calculator: Useful for when you need to calculate math problems, args: {\calculation\: {\description\: \calcul…

【软件安装】(十六)双系统Ubuntu22.04引导启动菜单的默认项

一个愿意伫立在巨人肩膀上的农民...... 好学的人总是喜欢在电脑上安装双系统&#xff0c;可是安装好系统之后&#xff0c;就会出现默认启动优先级的苦恼&#xff0c;如果在Bios中设置Windows引导启动为优先启动&#xff0c;那么每次想要进如Ubuntu系统就都需要重新设置Bios。如…

ubuntu的镜像源+bionic版本

首先第一步 查找和你自己ubuntu版本匹配的版本号 匹配代号如下 在终端输入lsb_release -a查看自己系统上的版本号 可以看到我这个版本号的代号是bionic。 每个版本的镜像文件都是有规律的。 bionic版本的源如下 # 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic ma…

舌头分割YOLOV8-SEG

舌头分割&#xff0c;基于YOLOV8-SEG&#xff0c;训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV的DNN调用&#xff0c;从而摆脱YOLO依赖&#xff0c;支持C,PYTHON,ANDROID开发 舌头分割YOLOV8-SEG

【小沐学Java】VSCode搭建Java开发环境

文章目录 1、简介2、安装VSCode2.1 简介2.2 安装 3、安装Java SDK3.1 简介3.2 安装3.3 配置 4、安装插件Java Extension Pack4.1 简介4.2 安装4.3 配置 结语 1、简介 2、安装VSCode 2.1 简介 Visual Studio Code 是一个轻量级但功能强大的源代码编辑器&#xff0c;可在桌面上…