SpringMVC底层(三)

笔记参考B站黑马程序员视频:spring原理

1.异常处理

在DispatcherServlet中的方法doDispatch来处理请求

doDispatch 方法的主要作用是将请求分派给相应的处理器(Handler),并最终执行处理器方法(Controller 方法),然后生成响应返回给客户端

比如这里创建HandlerAdapter方法,去执行控制器方法

 

handle方法使用参数解析器解析参数,接下来反射调用对应方法,然后返回值处理器处理。最终返回一个modelAndView(responseBody等为null)

 

对于异常

dispatchException是一个变量,用于存储请求处理过程中发生的异常。

而真正处理异常的逻辑在上面标注的processDispatchResult

而因为该方法在try-catch外,所以会判断是否发生了异常,如果有异常就处理异常,封否则处理视图。

处理异常的重要逻辑是在processHandlerException方法中

然后在这个方法里,首先去获取所有的异常处理器

也就是该类的一个属性中

比较典型的异常处理器就是ExceptionHandlerExceptionResolver

用于处理控制器方法中抛出的异常。它的作用是捕获控制器方法抛出的异常,并将其交给注册的 @ExceptionHandler 方法来处理。

下面简单展示一下它的用法

package com.dreams.demo30;

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) throws NoSuchMethodException {
        //......
    }

    static class Controller1 {
        public void foo() {

        }
        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(ArithmeticException e) {
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("error", e.getMessage());
            return errorMap;
        }
    }

    static class Controller2 {
        public void foo() {

        }
        @ExceptionHandler
        public ModelAndView handle(ArithmeticException e) {
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("error", e.getMessage());
            return new ModelAndView("test2", errorMap);
        }
    }

    static class Controller3 {
        public void foo() {

        }
        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(IOException e) {
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("error", e.getMessage());
            return errorMap;
        }
    }

    static class Controller4 {
        public void foo() {}
        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handler(Exception e, HttpServletRequest request) {
            System.out.println(request);
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("error", e.getMessage());
            return errorMap;
        }
    }
}

逻辑在main方法中

public static void main(String[] args) throws NoSuchMethodException {
    ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
    resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
    resolver.afterPropertiesSet();
}

@ResponseBody还需要消息转换器,所以需要setMessageConverters。

对于@ExceptionHandler返回值同样需要参数解析器和返回值处理器,而遇到了

调用resolver.afterPropertiesSet();就可以添加一些默认的参数解析器和返回值处理器。

 

异常返回JSON

测试@ExceptionHandler返回JSON

对应处理controller1

static class Controller1 {
    public void foo() {

    }
    @ExceptionHandler
    @ResponseBody
    public Map<String, Object> handle(ArithmeticException e) {
        Map<String, Object> errorMap = new HashMap<>();
        errorMap.put("error", e.getMessage());
        return errorMap;
    }
}

main方法完整代码

public static void main(String[] args) throws NoSuchMethodException {
    ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
    resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
    resolver.afterPropertiesSet();

    MockHttpServletRequest request = new MockHttpServletRequest();
    MockHttpServletResponse response = new MockHttpServletResponse();
    // 1.测试 json
    HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));
    Exception e = new ArithmeticException("被零除");
    resolver.resolveException(request, response, handlerMethod, e);
    System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}

代码解释:

通过 MockHttpServletRequest 和 MockHttpServletResponse,创建模拟的 HTTP 请求和响应对象,用于测试异常解析和处理。

MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();

这里创建一个 HandlerMethod 对象,代表一个处理器方法。Controller1 是一个控制器类,其中包含了一个名为 foo 的方法。该方法预期会抛出异常。

HandlerMethod handlerMethod = new HandlerMethod(new Controller1(), Controller1.class.getMethod("foo"));

创建了一个 ArithmeticException 异常实例,模拟 foo 方法中的异常情况,这里异常的消息是 “被零除”。使用 ExceptionHandlerExceptionResolver 的 resolveException 方法来处理模拟的异常情况。根据注册的 @ExceptionHandler 方法,这个方法将根据异常类型和消息转换器来生成相应的 JSON 输出。

Exception e = new ArithmeticException("被零除");
resolver.resolveException(request, response, handlerMethod, e);
System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));

输出如下:

 

异常返回model视图

测试@ExceptionHandler返回model视图

对应处理controller2

static class Controller2 {
    public void foo() {

    }
    @ExceptionHandler
    public ModelAndView handle(ArithmeticException e) {
        Map<String, Object> errorMap = new HashMap<>();
        errorMap.put("error", e.getMessage());
        return new ModelAndView("test2", errorMap);
    }
}

对应main方法逻辑

public static void main(String[] args) throws NoSuchMethodException {
    ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
    resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
    resolver.afterPropertiesSet();

    MockHttpServletRequest request = new MockHttpServletRequest();
    MockHttpServletResponse response = new MockHttpServletResponse();
    HandlerMethod handlerMethod = new HandlerMethod(new Controller2(), Controller2.class.getMethod("foo"));
    Exception e = new ArithmeticException("被零除");
    ModelAndView mav = resolver.resolveException(request, response, handlerMethod, e);
    System.out.println(mav.getModel());
    System.out.println(mav.getViewName());
}

直接resolver.resolveException返回ModelAndView即可

 

异常嵌套

如果异常出现异常嵌套问题

static class Controller3 {
    public void foo() {

    }
    @ExceptionHandler
    @ResponseBody
    public Map<String, Object> handle(IOException e) {
        Map<String, Object> errorMap = new HashMap<>();
        errorMap.put("error", e.getMessage());
        return errorMap;
    }
}

比如

Exception e = new Exception("e1", new RuntimeException("e2", new IOException("e3")));

main方法逻辑如下:

public static void main(String[] args) throws NoSuchMethodException {
    ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
    resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
    resolver.afterPropertiesSet();

    MockHttpServletRequest request = new MockHttpServletRequest();
    MockHttpServletResponse response = new MockHttpServletResponse();
    // 测试嵌套异常
    HandlerMethod handlerMethod = new HandlerMethod(new Controller3(), Controller3.class.getMethod("foo"));
    Exception e = new Exception("e1", new RuntimeException("e2", new IOException("e3")));
    resolver.resolveException(request, response, handlerMethod, e);
    System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}

可以看到能够成功获取

在源码中,同样在doResolveHandlerMethodException方法中,去获取得到的异常,添加到一个list列表exception中,然后不断getCause去获取嵌套的异常,直到为null。

最后调用exceptionHandlerMethod.invokeAndHandle去处理

 

异常参数解析器

上面提到调用resolver.afterPropertiesSet()就可以添加一些默认的参数解析器和返回值处理器。

比如,传入一个http请求

static class Controller4 {
    public void foo() {}
    @ExceptionHandler
    @ResponseBody
    public Map<String, Object> handler(Exception e, HttpServletRequest request) {
        System.out.println(request);
        Map<String, Object> errorMap = new HashMap<>();
        errorMap.put("error", e.getMessage());
        return errorMap;
    }
}

main方法逻辑如下:

public static void main(String[] args) throws NoSuchMethodException {
    ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
    resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
    resolver.afterPropertiesSet();

    MockHttpServletRequest request = new MockHttpServletRequest();
    MockHttpServletResponse response = new MockHttpServletResponse();
    // 测试异常处理方法参数解析
    HandlerMethod handlerMethod = new HandlerMethod(new Controller4(), Controller4.class.getMethod("foo"));
    Exception e = new Exception("e1");
    resolver.resolveException(request, response, handlerMethod, e);
    System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
}

可以看到http请求参数能够获取到

 

全局异常

对于@ControllerAdvice注解标注的,是全局异常,因为全局异常需要@ControllerAdvice,所以我们要通过容器读取配置。

package com.dreams.demo31;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class WebConfig {
    @ControllerAdvice
    static class MyControllerAdvice {
        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(Exception e) {
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("error", e.getMessage());
            return errorMap;
        }
    }

    @Bean
    public ExceptionHandlerExceptionResolver resolver() {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        resolver.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
        return resolver;
    }
}

main方法逻辑

package com.dreams.demo31;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import java.nio.charset.StandardCharsets;

public class Demo {
    public static void main(String[] args) throws NoSuchMethodException {
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();
        
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);

        HandlerMethod handlerMethod = new HandlerMethod(new Controller5(), Controller5.class.getMethod("foo"));
        Exception e = new Exception("e1");
        resolver.resolveException(request, response, handlerMethod, e);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    static class Controller5 {
        public void foo() {

        }
    }
}

在源码中他是如何获取呢?

同样在ExceptionHandlerExceptionResolver类的afterPropertiesSet()方法中

在initExceptionHandlerAdviceCache()方法内

initExceptionHandlerAdviceCache()方法中

findAnnotatedBeans(getApplicationContext()) 方法用于查找应用上下文中所有标注了 @ControllerAdvice 注解的 Bean,并返回它们的列表。

对于每个 ControllerAdviceBean,获取其类型(即 beanType)。

创建一个 ExceptionHandlerMethodResolver 对象,传入 beanType。

调用 resolver.hasExceptionMappings() 方法,检查该 ControllerAdviceBean 是否包含了异常处理方法(即带有 @ExceptionHandler 注解的方法)。

如果 resolver 中有异常映射方法,则将该 ControllerAdviceBean 和 resolver 对象存入 exceptionHandlerAdviceCache 中,以便后续的异常处理时使用。

 

对于我们之前学的RequestMappingHandlerAdapter也有afterPropertiesSet()方法

同样会有初始化方法initControllerAdviceCache()方法

initControllerAdviceCache()方法中也类似的将对象存入Cache 中,以便后续的处理时使用。

 

2.tomcat异常处理

Tomcat的错误页处理

如果是springMVC框架控制层抛出的异常,会由@ControllerAdvice的异常处理器。

如果其他地方出现异常,比如过滤器,那就由tomcat来处理了

下列代码注册几个Bean,这些之前学过的,就不再阐述,最后加入一个Controller 层方法,让它抛出一个异常。

package com.dreams.demo32;

import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.Arrays;

@Configuration
public class WebConfig {
    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }

    @Bean // @RequestMapping
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    @Bean // 注意默认的 RequestMappingHandlerAdapter 不会带 jackson 转换器,所以我们注解Bean注入
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
        handlerAdapter.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));
        return handlerAdapter;
    }

    @Controller
    public static class MyController {
        @RequestMapping("test")
        public ModelAndView test() {
            int i = 1 / 0;
            return null;
        }
    }
}

启动类main方法,就是简单调用

package com.dreams.demo32;

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

public class Demo {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        handlerMapping.getHandlerMethods().forEach((RequestMappingInfo k, HandlerMethod v) -> {
            System.out.println("映射路径:" + k + "\t方法信息:" + v);
        });
    }
}

可以看到映射路径是正常输出了

但是访问控制器方法可以看到出现的异常由tomcat捕获了

 

我们可以添加一个Bean到WebConfig.java

ErrorPageRegistrar用于注册错误页面。当应用程序发生错误时(如 404 Not Found 或 500 Internal Server Error),Spring Boot 将重定向到 /error 路径,显示自定义的错误页面或错误信息。

这里传入的ErrorPageRegistry webServerFactory实际就是TomcatServletWebServerFactory,他实现了ErrorPageRegistry。

ErrorPageRegistrarBeanPostProcessor 的作用是处理 ErrorPageRegistrar 类型的 Bean。

@Bean // 修改了 Tomcat 服务器默认错误地址
public ErrorPageRegistrar errorPageRegistrar() { // 出现错误,会使用请求转发 forward 跳转到 error 地址
    return new ErrorPageRegistrar() {
        @Override
        public void registerErrorPages(ErrorPageRegistry webServerFactory) {
            webServerFactory.addErrorPages(new ErrorPage("/error"));
        }
    };
}

@Bean
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
    return new ErrorPageRegistrarBeanPostProcessor();
}

上面我们要他跳转到/error路径,所以我们定义一个/error

HttpServletRequest 参数用于获取请求中的错误属性RequestDispatcher.ERROR_EXCEPTION,这是一个 Servlet API 中的常量,用于表示发生错误时的异常对象。

@Controller
public static class MyController {
    @RequestMapping("test")
    public ModelAndView test() {
        int i = 1 / 0;
        return null;
    }
    
    @RequestMapping("/error")
    @ResponseBody
    public Map<String, Object> error(HttpServletRequest request) {
        Throwable e = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
        Map<String, Object> errorMap = new HashMap<>();
        errorMap.put("error", e.getMessage());
        return errorMap;
    }
}

 

SpringBoot中BasicErrorController

BasicErrorController 是 Spring Boot 提供的一个预定义的控制器类,用于处理应用程序中的错误情况。

Spring Boot 在启动时会自动配置一个 BasicErrorController,它处理所有未显式处理的错误情况。

他也是一个Controller类,也有路径映射,@RequestMapping(“${server.error.path:${error.path:/error}}”) 默认读取使用server.error.path,没有使用error.path,再没有使用 /error 路径来处理错误请求。

同样在WebConfig.java配置加入Bean

includeException 控制是否在错误响应中包含异常信息。

@Bean
public BasicErrorController basicErrorController() {
    ErrorProperties errorProperties = new ErrorProperties();
    errorProperties.setIncludeException(true);
    return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}

 

这样就不需要我们定义controller层路径/errror了

@Controller
public static class MyController {
    @RequestMapping("test")
    public ModelAndView test() {
        int i = 1 / 0;
        return null;
    }
}

 

默认浏览器返回html

直接请求返回JSON

为什么浏览器又返回了html了呢

在源码里可以看到浏览器访问,匹配到的请求,默认返回一个视图,路径为/error

对应上面我们需要配置如下:

定义一个名为 error 的自定义视图 View bean,要使error视图生效,还需要配置视图解析器配置 (viewResolver() 方法)。

@Bean
public View error() {
    return new View() {
        @Override
        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            System.out.println(model);
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().print("<h3>服务器内部错误</h3>");
        }
    };
}

@Bean
public ViewResolver viewResolver() {
    return new BeanNameViewResolver();
}

如下:

如果想要都返回json

@Bean
public View error() {
    return new View() {
        @Override
        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            System.out.println(model);
            response.setContentType("application/json;charset=utf-8");
            Map<String, Object> errorMap = new HashMap<>();
            errorMap.put("error", "服务器出现异常");
            String quote = JSONParser.quote(errorMap.toString());
            response.getWriter().print(quote);
        }
    };
}

@Bean
public ViewResolver viewResolver() {
    return new BeanNameViewResolver();
}

 

3.HandlerMapping和HandlerAdapter

我们之前使用的是RequestMappingHandlerMapping和RequestMappingHandlerAdapter搭配使用。

而还有其他的实现

BeanNameUrlHandlerMapping和SimpleControllerHandlerAdapter

BeanNameUrlHandlerMapping 是 Spring MVC 中的一种 HandlerMapping 实现,其作用是将 URL 映射到 Spring 容器中已注册的 bean:

  • 将 HTTP 请求的 URL 映射到相应的处理器(controller bean)。
  • 使用 bean 的名称作为 URL 路径的一部分,从而实现 URL 到控制器方法的映射。
  • 也就是处理@Component(“/test1”)映射为/test1。

SimpleControllerHandlerAdapter 是 Spring MVC 中的一个 HandlerAdapter 实现,负责将特定类型的控制器适配为处理请求的适配器:

  • 将实现了 Controller 接口的 bean(例如 AbstractController 或其子类)适配为 Spring MVC 的处理器。也就是必须要求实现org.springframework.web.servlet.mvc.Controller;
  • 提供处理器适配的基本功能,使得不同类型的控制器能够统一通过 Spring MVC 进行管理和调度。
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
    return new BeanNameUrlHandlerMapping();
}

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

 

WebConfig.java代码:

可以看到每一个接口都要实现Controller接口,重写它的handleRequest方法

package com.dreams.demo33;

import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Configuration
public class WebConfig {
    @Bean // 内嵌 web 容器工厂
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory(8080);
    }

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

    @Bean // 注册 DispatcherServlet, Spring MVC 的入口
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }

    // /c1  -->  /c1
    // /c2  -->  /c2
    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
        return new BeanNameUrlHandlerMapping();
    }

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

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

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

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

启动类main方法没什么变化。

package com.dreams.demo33;

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;

public class Demo {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context
                = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

BeanNameUrlHandlerMapping处理好映射路径后,访问该路径,SimpleControllerHandlerAdapter就会调用handlerRequest方法

总结:

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

对比:

  • RequestMappingHandlerAdapter, 以 @RequestMapping 作为映射路径
  • 控制器的具体方法会被当作 handler
  • RequestMappingHandlerAdapter, 调用 handler

那么它的底层是如何实现的呢?

大致流程如下:

通过自定义来演示流程:

自定义的 HandlerMapping 实现,用于将请求的 URI 映射到相应的 Controller 实例。

getHandler 方法实现:

  • getHandler(HttpServletRequest request): 根据请求对象 HttpServletRequest 中的 URI 获取对应的 Controller 实例。
  • collect: 是一个通过 Spring 的 ApplicationContext 获取的所有实现了 Controller 接口的 bean 的映射关系。
  • 当请求的 URI 在 collect 中找到对应的 Controller 实例时,将其包装成 HandlerExecutionChain 对象返回,该对象包含了待执行的控制器。

init 方法实现:

  • @Autowired private ApplicationContext context: 自动注入 ApplicationContext,以便在初始化阶段获取所有 Controller 类型的 bean。
  • @PostConstruct: 标记的方法 init() 会在 MyHandlerMapping 实例化后立即执行。
  • collect: 通过 getBeansOfType(Controller.class) 获取所有类型为 Controller 的 bean,并且只保留 URI 路径以 / 开头的 bean。
  • 这些 bean 会被存储在 collect 中,以备 getHandler 方法在处理请求时使用
@Component
static class MyHandlerMapping implements HandlerMapping {
    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        String key = request.getRequestURI();
        Controller controller = collect.get(key);
        if (controller == null) {
            return null;
        }
        return new HandlerExecutionChain(controller);
    }

    @Autowired
    private ApplicationContext context;
    private Map<String, Controller> collect;

    @PostConstruct
    public void init() {
        collect = context.getBeansOfType(Controller.class).entrySet()
                .stream().filter(e -> e.getKey().startsWith("/"))
                .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
        System.out.println(collect);
    }
}

 

自定义的 HandlerAdapter 实现,用于将特定类型的 Controller 处理器适配到 Spring MVC 的处理流程中

supports(Object handler): 判断给定的 handler 是否是 Controller 类型的实例。这个方法在处理器适配器中用于判断是否支持处理特定类型的处理器。

handle(HttpServletRequest request, HttpServletResponse response, Object handler): 在请求到达时,根据传入的 handler 对象(通常是一个 Controller 实例),调用其 handleRequest 方法来处理请求

@Component
static class MyHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {
        return handler instanceof Controller;
    }

    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof Controller) {
            Controller controller = (Controller)handler;
            controller.handleRequest(request, response);
        }
        return null;
    }

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

 

RouterFunctionMapping与HandlerFunctionAdapter

RouterFunctionMapping收集所有RouterFunction,

RouterFunction包括两部分:

  • RequestPredicate 设置映射条件
  • HandlerFunction 包含处理逻辑

请求到达,根据映射条件找到 HandlerFunction,即 handler

最后HandlerFunctionAdapter,调用handler

同样要求加入配置类

package com.dreams.demo34;

import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.function.support.HandlerFunctionAdapter;
import org.springframework.web.servlet.function.support.RouterFunctionMapping;

import static org.springframework.web.servlet.function.RequestPredicates.GET;
import static org.springframework.web.servlet.function.RouterFunctions.route;
import static org.springframework.web.servlet.function.ServerResponse.ok;


@Configuration
public class WebConfig {
    @Bean // 内嵌 web 容器工厂
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory(8080);
    }

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

    @Bean // 注册 DispatcherServlet, Spring MVC 的入口
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }

    @Bean
    public RouterFunctionMapping routerFunctionMapping() {
        return new RouterFunctionMapping();
    }

    @Bean
    public HandlerFunctionAdapter handlerFunctionAdapter() {
        return new HandlerFunctionAdapter();
    }

}

 

这样上面已经有了RouterFunctionMapping与HandlerFunctionAdapter,接着就是需要加入RouterFunction

同样在WebConfig

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

// 简化写法
@Bean
public RouterFunction<ServerResponse> r2() {
    return route(GET("/r2"), request -> ok().body("this is r2"));
}

 

启动类

package com.dreams.demo34;

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;

public class Demo {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

 

SimpleUrIHandlerMapping与HttpRequestHand lerAdapter

这两个组合一般用来处理静态资源。

SimpleUrlHandlerMapping做映射

ResourceHttpRequestHandler作为处理器处理静态资源

HttpRequestHandlerAdapter 调用处理器

同样WebConfig.java

package com.dreams.demo35;

import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.CachingResourceResolver;
import org.springframework.web.servlet.resource.EncodedResourceResolver;
import org.springframework.web.servlet.resource.PathResourceResolver;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import java.util.Arrays;
import java.util.Map;

@Configuration
public class WebConfig {
    @Bean // 内嵌 web 容器工厂
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory(8080);
    }

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

    @Bean // 注册 DispatcherServlet, Spring MVC 的入口
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }
}

 

通过 Spring 的 @Bean 注解配置多个 ResourceHttpRequestHandler,用于处理不同路径下的静态资源请求。

调用 handler.setLocations(Arrays.asList(new ClassPathResource(“static/”))) 方法,设置静态资源的位置。这里使用 ClassPathResource 表示静态资源位于类路径下的 static/ 目录,images同理。

@Bean("/**")
public ResourceHttpRequestHandler handler1() {
    ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
    handler.setLocations(Arrays.asList(new ClassPathResource("static/")));
    return handler;
}

@Bean("/img/**")
public ResourceHttpRequestHandler handler2() {
    ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
    handler.setLocations(Arrays.asList(new ClassPathResource("images/")));
    return handler;
}

 

然后需要SimpleUrlHandlerMapping和HttpRequestHandlerAdapter

方法参数 ApplicationContext context: 这个参数是通过依赖注入方式传入的,Spring 在运行时会将当前的 ApplicationContext 自动传入方法中

因为这个实现没有afterPropertiesSet()方法,所以需要手动设置

context.getBeansOfType(ResourceHttpRequestHandler.class) 方法,获取所有类型为 ResourceHttpRequestHandler 的映射关系,并将其放入一个 Map<String, ResourceHttpRequestHandler> 中,map记录了映射关系。

将获取到的 ResourceHttpRequestHandler 的 Bean Map 设置到 handlerMapping 实例中,通过 setUrlMap(map) 方法完成映射设置。

@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {
    SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
    Map<String, ResourceHttpRequestHandler> map = context.getBeansOfType(ResourceHttpRequestHandler.class);
    handlerMapping.setUrlMap(map);
    System.out.println(map);
    return handlerMapping;
}

@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
    return new HttpRequestHandlerAdapter();
}

启动类

package com.dreams.demo35;

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;

public class Demo {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context
                = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

这个map记录了映射关系。

在static目录下放几个静态页面

在images下放几个图片

 

对于ResourceHttpRequestHandler,他提供了一个afterPropertiesSet()方法,可以看到她设置了默认的资源解析器。PathResourceResolver 只是简单根据指定的位置列表解析请求的资源。

可以对其优化,只要加入上面的resourceResolvers即可

配置了三个不同的资源解析器,以处理静态资源的请求。每个解析器的功能如下:

  • CachingResourceResolver: 缓存已解析的资源,提高请求的处理速度。
  • EncodedResourceResolver: 可以将资源压缩,再访问。不过要自己压缩
  • PathResourceResolver: 根据路径解析静态资源,是最基本的资源解析器之一,通常用于处理静态资源的请求。访问过后CachingResourceResolver缓存,下一次访问就可以不走PathResourceResolver
@Bean("/**")
public ResourceHttpRequestHandler handler1() {
    ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
    handler.setLocations(Arrays.asList(new ClassPathResource("static/")));
    handler.setResourceResolvers(Arrays.asList(
            new CachingResourceResolver(new ConcurrentMapCache("cache1")),
            new EncodedResourceResolver(),
            new PathResourceResolver()
    ));
    return handler;
}

 

欢迎页处理

springboot可以默认跳转到一个默认页面,比如访问http://localhost:8080可以跳转到其他地方,关键就是WelcomePageHandlerMapping,不过这个是springboot提供私有的,所以新建与其相同的目录结构。

同样在webConfig加入即可,不过这里的欢迎页映射器需要用到前面的静态处理器,需要一起加上。

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {
    Resource resource = context.getResource("classpath:static/index.html");
    return new WelcomePageHandlerMapping(null, context, resource, "/**");
    // Controller 接口
}

默认会生成一个将实现了 Controller 接口的 bean,要调用,还需要一个处理器,SimpleControllerHandlerAdapter将实现了 Controller 接口的 bean(例如 AbstractController 或其子类)适配为 Spring MVC 的处理器。

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

欢迎页处理WelcomePageHandlerMapping,映射欢迎页(即只映射 ‘/’),它内置了 handler ParameterizableViewController 处理器作用是不执行逻辑,仅根据视图名找视图,视图名固定为 forward:index.html /**。而SimpleControllerHandlerAdapter调用 handler转发至 /index.html,最后处理 /index.html 又会走上面的静态资源处理流程

 

总结

1.HandlerMapping 负责建立请求与控制器之间的映射关系

  • RequestMappingHandlerMapping (与 @RequestMapping 匹配)
  • WelcomePageHandlerMapping (/)
  • BeanNameUrlHandlerMapping (与 bean 的名字匹配 以 / 开头)
  • RouterFunctionMapping (函数式 RequestPredicate, HandlerFunction)
  • SimpleUrlHandlerMapping (静态资源 通配符 /** /img/**)

之间也会有顺序问题, boot 中默认顺序如上

2.HandlerAdapter 负责实现对各种各样的 handler 的适配调用:

  • RequestMappingHandlerAdapter 处理:@RequestMapping 方法
    参数解析器、返回值处理器体现了组合模式
  • SimpleControllerHandlerAdapter 处理:Controller 接口
  • HandlerFunctionAdapter 处理:HandlerFunction 函数式接口
  • HttpRequestHandlerAdapter 处理:HttpRequestHandler 接口 (静态资源处理)

这也是典型适配器模式体现

3.ResourceHttpRequestHandler.setResourceResolvers 这是典型责任链模式体现

 

3.MVC处理流程

当浏览器发送一个请求 http://localhost:8080/hello 后,请求到达服务器,其处理流程是:

(1)服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术

  • 路径:默认映射路径为 /,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器。
    jsp 不会匹配到 DispatcherServlet
    其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
  • 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
  • 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
    HandlerMapping,初始化时记录映射关系
    HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
    HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
    ViewResolver视图解析器

 

(2)DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法

  • 例如根据 /hello 路径找到 @RequestMapping(“/hello”) 对应的控制器方法
  • 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
  • HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象

 

(3)DispatcherServlet 接下来会:

  1. 调用拦截器的 preHandle 方法

  2. RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod

    • @ControllerAdvice 全局增强点:补充模型数据

    • @ControllerAdvice 全局增强点:补充自定义类型转换器

    • 使用 HandlerMethodArgumentResolver 准备参数

      • @ControllerAdvice 全局增强点:RequestBody 增强

    • 调用 ServletInvocableHandlerMethod,内部调用控制器方法。

    • 使用 HandlerMethodReturnValueHandler 处理返回值

      • @ControllerAdvice 全局增强点:ResponseBody 增强

    • 根据 ModelAndViewContainer 获取 ModelAndView

      • 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程

        • 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null

      • 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程

  3. 调用拦截器的 postHandle 方法

  4. 处理异常或视图渲染

    • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程

      • @ControllerAdvice 全局增强点:@ExceptionHandler 异常处理

    • 正常,走视图解析及渲染流程

  5. 调用拦截器的 afterCompletion 方法

 

7.参考

B站黑马程序员视频:spring原理

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇