Spring底层入门(七)

1、异常处理

        在DispatcherServlet中,doDispatch(HttpServletRequest request, HttpServletResponse response) 方法用于进行任务处理:

b56970446d2241ca98ce87aa46b70454.png

        在捕获到异常后没有立刻进行处理,而是先用一个局部变量dispatchException进行记录,然后统一由processDispatchResult() 方法进行处理:

0d400b87cfd34215bd9f1c25f71c41ac.png

        processDispatchResult() 方法中,首先判断异常是否为空,如果为空就不进行处理,然后判断是否是ModelAndViewDefiningException类型异常,如果不是就进入processHandlerException()

d563dcc14d42427ab05b9b072b36fbfe.png

        processHandlerException() 中,会循环遍历handlerExceptionResolvers集合去匹配并处理异常:

	@Nullable
	private List<HandlerExceptionResolver> handlerExceptionResolvers;

cdf734a47edd47e5be5c327d40e44334.png

        HandlerExceptionResolver是一个接口,我们使用ExceptionHandlerExceptionResolver的实现去模拟异常处理的过程:

92e9d9b00a274c25a0e057ebd9bd3d66.png

         ExceptionHandlerExceptionResolver专门用于解析 @ExceptionHandler注解,把标注了 @ExceptionHandler注解的方法作为处理异常的方法

//专门用于解析 @ExceptionHandler注解,把标注了 @ExceptionHandler注解的方法作为处理异常的方法
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
//设置消息转换json
resolver.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
//初始化 加入常用的参数和返回值解析器
resolver.afterPropertiesSet();

testJSON(resolver);

        在afterPropertiesSet() 初始化方法中,已经预先定义好了一些参数解析器和返回值处理器:

2b9b5645b99c46b6a45426bafe18323f.png

        定义一个控制器:

public class Controller1 {

    public void foo(){

    }

    /**
     * 处理异常的方法,并且将返回值转成JSON
     * @param e
     * @return
     */
    @ExceptionHandler
    @ResponseBody
    public Map<String,Object> handle(ArithmeticException e){
        return Collections.singletonMap("error",e.getMessage());
    }
}

         resolver.resolveException()方法会检查Controller1中是否有@ExceptionHandler注解标注的方法,如果有,并且参数的异常和实际发生的异常能对应上,就执行其中的逻辑:

  private static void testJSON(ExceptionHandlerExceptionResolver resolver) throws NoSuchMethodException {
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        //将控制器的foo方法封装成HandlerMethod对象
        HandlerMethod handlerMethod = new HandlerMethod(new Controller1(),Controller1.class.getMethod("foo"));
        //检查Controller1中是否有@ExceptionHandler注解标注的方法,如果有,并且参数的异常和实际发生的异常能对应上,就执行其中的逻辑
        resolver.resolveException(request,response,handlerMethod,new ArithmeticException("数学异常"));
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

1f5a7f336bcb48549c45ee64a5b90620.png

        此外处理异常的方法还支持ModelAndView类型的返回,与上述解析异常的过程相似。


        我们还可以转发自定义的错误处理页面:

 /**
     * 转发到自定义的错误页面
     * @return
     */
    @Bean
    public ErrorPageRegistrar errorPageRegistrar(){
        return new ErrorPageRegistrar() {
            @Override
            public void registerErrorPages(ErrorPageRegistry registry) {
                registry.addErrorPages(new ErrorPage("/error"));
            }
        };
    }

    /**
     * 注册后处理器
     * @return
     */
    @Bean
    public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor(){
        return new ErrorPageRegistrarBeanPostProcessor();
    }


    /**
     * Spring Boot中配置自定义的BasicErrorController,用于处理基本的错误页面和错误信息。
     * @return
     */
    @Bean
    public BasicErrorController basicErrorController(){
        ErrorProperties errorProperties = new ErrorProperties();
        errorProperties.setIncludeException(true);
        return new BasicErrorController(new DefaultErrorAttributes(),errorProperties);
    }

2、BeanNameUrlHandlerMapping&SimpleControllerHandlerAdapter

        BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter分别是HandlerMapping和HandlerAdapter的实现类:

c8c09bd18ce64a0a9271cb2222597776.png

91151d6750fa495a8cd08d901737ec4c.png

        在BeanNameUrlHandlerMapping中,以/开头的 bean 的名字会被当作映射路径。这些 bean 本身当作 handler,要求实现 Controller 接口。

        准备一个Config类,将BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter注册成bean:

@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})//将配置文件中的属性绑定到对象中
public class Config {

    /**
     *  注册内嵌web容器工厂 tomcat容器
     */
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }

    /**
     * 创建DispatcherServlet
     * 首次使用时,由tomcat容器初始化
     * @return
     */
    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    /**
     * 注册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;
    }

    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping(){
        return new BeanNameUrlHandlerMapping();
    }

    @Bean
    public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter(){
        return new SimpleControllerHandlerAdapter();
    }

    @Bean("/c3")
    public Controller controller3(){
        return (request, response) -> {
            response.getWriter().print("this is c3");
            return null;
        };
    }
    
}

        再准备两个实现了Controller接口的控制器类:

@Component("/c1")
public class Controller1 implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.getWriter().print("this is c1");
        return null;
    }
}
@Component("c2")
public class Controller2 implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.getWriter().print("this is c2");
        return null;
    }
}

        启动主类:

public class A31 {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(Config.class);

    }
}

ba1edf9952654e069f1601630814e5fb.png


        我们可以模拟实现这一组映射器和适配器:

        定义一个类实现BeanNameUrlHandlerMapping的顶级接口HandlerMapping:

        它的作用是在初始化时收集容器中所有以/开头的路径和类成map集合,并且在调用时会判断当前requestURI能否与map集合中的任意元素相匹配:

        (复习一下,容器初始化时会收集所有 @RequestMapping 映射信息,封装为 Map)

/**
 * 模拟处理器映射器
 * 收集请求中以/开头的bean
 */
@Component
public class MyHandlerMapping implements HandlerMapping {

    /**
     * 处理器映射器,getHandlerMethods中 和当前requestURI 匹配的路径信息
     * @param request
     * @return
     * @throws Exception
     */
    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        String requestURI = request.getRequestURI();
        Controller controller = map.get(requestURI);
        //匹配不上 404
        if (controller == null){
            return null;
        }
        return new HandlerExecutionChain(controller);
    }




    @Autowired
    private ApplicationContext applicationContext;

    private Map<String, Controller> map;

    /**
     * 初始化时收集所有容器中/开头的bean信息
     */
    @PostConstruct
    public void init() {
        Map<String, Controller> beansOfType = applicationContext.getBeansOfType(Controller.class);
        map = beansOfType.entrySet().stream().filter(e -> e.getKey().startsWith("/")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        System.out.println(map);
    }
}

        定义一个类实现HandlerAdapter作为适配器,负责将请求分派给实现了controller接口类中的方法,RequestMappingHandlerAdapter相比,不需要自定义参数和返回值处理器。

/**
 * 模拟处理器适配器
 */
@Component
public class MyHandlerAdapter implements HandlerAdapter {

    /**
     * 判断传递的handler是否是当前MyHandlerAdapt支持的
     * @param handler
     * @return
     */
    @Override
    public boolean supports(Object handler) {
        return handler instanceof Controller;
    }

    /**
     * 调用实现了Controller接口的方法
     * 无需参数解析器,返回值处理器
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof Controller){
            ((Controller) handler).handleRequest(request, response);
        }
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        return -1;
    }
}

        结论:Controller1和Controller3能匹配上,Controller2匹配不上(路径中没有/)

3、RouterFunctionMapping&HandlerFunctionAdapter

        RouterFunctionMapping和HandlerFunctionAdapter也是HandlerMapping和HandlerAdapter的实现类:

  • RouterFunctionMapping会收集所有的RouterFunction,请求到达时,根据条件找到HandlerFunction
  • HandlerFunctionAdapter会调用符合条件的HandlerFunction。
/**
     * 会收集所有的RouterFunction
     * 请求到达时,根据条件找到HandlerFunction
     * @return
     */
    @Bean
    public RouterFunctionMapping routerFunctionMapping(){
        return new RouterFunctionMapping();
    }

    /**
     * 调用符合条件的HandlerFunction
     * @return
     */
    @Bean
    public HandlerFunctionAdapter handlerFunctionAdapter(){
        return new HandlerFunctionAdapter();
    }

        RouterFunction分为两部分:匹配规则和具体的执行逻辑(请求是GET类型,并且路径是/r1,就执行new HandlerFunction<ServerResponse>()中的逻辑)

   @Bean
    public RouterFunction<ServerResponse> r1(){
        //参数一 匹配规则 参数二 具体的执行逻辑
        return RouterFunctions.route(RequestPredicates.GET("/r1"), new HandlerFunction<ServerResponse>() {
            @Override
            public ServerResponse handle(ServerRequest request) throws Exception {
                return ServerResponse.ok().body("this is r1");
            }
        });
    }

下一篇对Spring MVC 的执行流程做一个总结。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Cpp]类和对象 | 实现日期类

标题&#xff1a;[Cpp]类和对象 | 实现日期类 水墨不写bug 正文开始&#xff1a; 类和对象是Cpp面向对象编程区别于C的面向过程编程的重要的一部分&#xff0c;因此打好坚实的类和对象的基础对于深入学习Cpp语言是比较明智的。 本文通过实现简单的日期类来加深对类和对象的理解…

怎么用git在暂存区(stage)中移除不需要提交(commit)的文件?

2024年5月9日&#xff0c;周四上午 非常简单&#xff0c;用下面这条命令就可以了 git rm --cached <file>注&#xff1a;这条命令不会把文件从文件夹中删除&#xff0c;只会把文件从暂存区中移除出去 实战

Isaac Sim 5 Ros相关(学习笔记5.8.3)

一.RGB、Depth、bbox话题发送 1.新建一个二驱示例小车 路径为Robot-Jetbot&#xff08;如果找不到也可以直接搜索Jetbot&#xff09; 2.添加Action Graph 导航栏中&#xff1a;Window - Visual Scripting - Action Graph&#xff0c;建立一个工作区&#xff0c;这个工作区中…

【高阶数据结构】并查集

并查集 并查集1、概念2、根据人找编号 / 根据编号找人&#xff08;简单介绍一下并查集&#xff09;&#xff08;1&#xff09;代码展示&#xff08;2&#xff09;调试结果&#xff08;3&#xff09;优化1&#xff1a;小的往大的合并&#xff08;4&#xff09;优化2&#xff1a;…

docker-compose安装es+kibana 8.12.2

小伙伴们&#xff0c;你们好&#xff0c;我是老寇&#xff0c;我又回来辣&#xff0c;几个月不见甚是想念啊&#xff01;&#xff01;&#xff01; 因云平台需要改造&#xff0c;es7升级为es8&#xff0c;所以记录一下&#xff0c;es8需要开启ssl认证&#xff0c;需要配置证书…

AC/DC电源模块在通信与网络设备中的应用研究

BOSHIDA AC/DC电源模块在通信与网络设备中的应用研究 随着通信与网络技术的不断发展&#xff0c;通信与网络设备的使用不断增加。电源作为通信与网络设备的重要组成部分之一&#xff0c;在其稳定工作中起到至关重要的作用。AC/DC电源模块作为一种常用的电源转换器&#xff0c;…

探索设计模式的魅力:权力集中,效率提升,中心化模式的优势与挑战

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索中心化模式之旅✨ 大家好啊&#xff01;&#x1f44b; 这次我们要聊的是IT界一…

使用js/java合并3dtiles

目录 前言&#xff1a; 需合并的json目录 aa/tileset.json bb/tileset.json cc/tileset.json dd/tileset.json ee/tileset.json js源码&#xff1a; 运行命令&#xff1a; 生成结果&#xff1a; java源码&#xff1a; Matrix.java ThreeDTilesJoin2.java pom文件…

【中级软件设计师】上午题15-计算机网络

上午题15-计算机网络 1 网络设备2 协议簇3 TCP和UDP4 SMTP和POP35 ARP和RARP6 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff09;7 URL8 浏览器9 IP地址和子网划分10 IPv611 Windows命令12 路由器 1 网络设备 物理层设备&#xff1a;中继器、集线器&#xff0…

目标检测正负样本区分和平衡

1、正负样本定义 rpn和rcnn的正负样本定义都是基于MaxIoUAssigner&#xff0c;只不过定义阈值不一样而已。 MaxIoUAssigner的操作包括4个步骤&#xff1a; 首先初始化时候假设每个anchor的mask都是-1&#xff0c;表示都是忽略anchor 将每个anchor和所有gt的iou的最大Iou小于…

C# SolidWorks 二次开发 -从零开始创建一个插件(3) 发布插件

五一节过完了吧&#xff0c;该上班学习了吧&#xff1f; 如何把自己开发好的程序优雅的给别人使用。 今天我们来简单讲解一下&#xff0c;这个之前不少粉丝咨询过相关问题&#xff0c;自己开发好的东西&#xff0c;如何给同事或者其它人使用。 先列一下使用到的主要工具&am…

什么是存量与流量?

存量与流量是反映经济状况的两类指标&#xff0c;在统计和国民经济核算中得到广泛运用。存量与流量之间既有密切的联系&#xff0c;又有一定区别。 一、存量与流量的基本概念 存量是某一时点结存的量&#xff0c;体现了某一时点上持有的经济价值或物量&#xff1b;流量是一段…

基于YOLO的车牌与车型识别系统

一、项目背景与意义 随着智能交通系统的快速发展&#xff0c;车辆识别技术在交通管理、安防监控、自动收费、停车管理等领域发挥着至关重要的作用。车牌识别和车型识别作为车辆识别技术的核心组成部分&#xff0c;能够有效提升交通运营效率&#xff0c;加强公共安全监控&#…

阿里云发布通义千问2.5,OpenCompass上得分追平GPT-4 Turbo

5月9日消息&#xff0c;阿里云正式发布通义千问2.5&#xff0c;模型性能全面赶超GPT-4 Turbo&#xff0c;成为地表最强中文大模型。同时&#xff0c;通义千问最新开源的1100亿参数模型在多个基准测评收获最佳成绩&#xff0c;超越Meta的Llama-3-70B&#xff0c;成为开源领域最强…

Davinci工程CANTP模块讲解

配置CAN的TP模式&#xff0c;涉及BSW\CanTp\CanTp.c和CanTp.h CanTpChannels 他有两组收发&#xff0c;功能诊断和物理诊断。 功能诊断有自己的参数要求 物理诊断的接收要求相对多一些 由于发送只有一个&#xff0c;所以我们把它放在物理诊断接收那组里面。 CanTpGeneral 也…

关于在阿拉伯语中占位符出现的问题

项目中用到了阿语的翻译&#xff0c;本来是直接复制过来就行&#xff0c;但是在一个使用到占位符的地方出现了问题 这是正常的内容但是粘贴到studio后却不是这样的 变成这样了那个逗号一样的文字的位置变了&#xff0c;这样一来占位符彻底无法用了还会报错。 经过多方尝试和群…

学习Uni-app开发小程序Day3

经过五一长假&#xff0c;回过头在去看学习的东西&#xff0c;发现仍然是一筹莫展的&#xff0c;看来&#xff0c;学习是不能松懈的&#xff0c;得&#xff0c;自己在把以前的从头复习一遍&#xff0c;加深印象。今天在继续听课&#xff0c;但是出现一个问题&#xff0c;是黑码…

大家都是怎么写毕业论文的? 推荐4个AI工具

写作这件事一直让我们从小学时期就开始头痛&#xff0c;初高中时期800字的作文让我们焦头烂额&#xff0c;一篇作文里用尽了口水话&#xff0c;拼拼凑凑才勉强完成。 大学时期以为可以轻松顺利毕业&#xff0c;结果毕业前的最后一道坎拦住我们的是毕业论文&#xff0c;这玩意不…

FMEA如何在设计活动中有效应用?——FMEA软件

免费试用FMEA软件-免费版-SunFMEA 在现代产品设计和开发过程中&#xff0c;FMEA&#xff08;失效模式与影响分析&#xff09;已经成为了一种不可或缺的工具。它的核心目标是在产品或过程设计的早期阶段&#xff0c;通过识别和分析潜在的失效模式&#xff0c;预防和控制可能出现…

汽车软件研发工具链丨怿星科技新产品重磅发布

“创新引领未来”聚焦汽车软件新基建&#xff0c;4月27日下午&#xff0c;怿星科技2024新产品发布会在北京圆满举行&#xff01;智能汽车领域的企业代表、知名大企业负责人、投资机构代表、研究机构代表齐聚现场&#xff0c;线上直播同步开启&#xff0c;共同见证怿星科技从单点…