spring高级篇(七)

1、异常处理

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

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

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

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

	@Nullable
	private List<HandlerExceptionResolver> handlerExceptionResolvers;

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

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

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

testJSON(resolver);

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

        定义一个控制器:

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));
    }

        此外处理异常的方法还支持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分别是HandlerMappingHandlerAdapter的实现类:

        在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);

    }
}


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

        定义一个类实现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也是HandlerMappingHandlerAdapter的实现类:

  • 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 的执行流程做一个总结。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-05-04 16:10:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-04 16:10:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-04 16:10:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-04 16:10:02       20 阅读

热门阅读

  1. dart-sdk 安装以及vscode开发工具安装dart

    2024-05-04 16:10:02       12 阅读
  2. String str = new String(“Hello, World!“);

    2024-05-04 16:10:02       11 阅读
  3. 面试经典150题——判断子序列

    2024-05-04 16:10:02       8 阅读
  4. 基于micropython和esp32cam的图像颜色识别小作品

    2024-05-04 16:10:02       11 阅读
  5. 第IV章-Ⅰ Vue3组件与组件通信

    2024-05-04 16:10:02       12 阅读
  6. drop、delete与truncate的区别

    2024-05-04 16:10:02       11 阅读
  7. FPGA学习笔记(2)——Verilog语法及ModelSim使用

    2024-05-04 16:10:02       13 阅读
  8. 安卓抓包总结

    2024-05-04 16:10:02       11 阅读
  9. 深入探索CSS伪类:解锁样式与交互的无限可能

    2024-05-04 16:10:02       12 阅读
  10. 用qml生成日志文件

    2024-05-04 16:10:02       12 阅读