Spring MVC应用和原理学习

403 阅读9分钟

Spring mvc 原理学习

内置tomcat和外置tomcat区别

  • jar包启动流程:onRefresh--》ServletWebServerApplicationContext--〉createWebServer();--》factory.getWebServer(getSelfInitializer())创建tomcat容器--》TomcatServletWebServerFactory--->[ServletWebServerApplicationContext]getSelfInitializer().onStartup(servletContext);--》RegistrationBean注册serverlet

  • war包启动流程:先启动tomcat---找到spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer(SPI机制)-->SpringServletContainerInitializer--->@HandlesTypes(WebApplicationInitializer.class) 为WebApplicationInitializer 利用accessibleConstructor创建实例>-最后调用每个initializer.onStartup;对于springboot来说调用SpringBootServletInitializer--》onStartup--》SpringApplication-》createRootApplicationContext--运行run方法;

Tomcat和DispatcherServlet是Web应用程序开发中的两个重要概念,它们之间的联系和区别如下:

  • 联系:
  • Web容器与Web组件:Tomcat是一个开源的Servlet容器,用于部署和运行Web应用程序。而DispatcherServlet是Spring框架中的一个核心组件,它是一个Servlet,用于处理Web请求。DispatcherServlet在Tomcat等Servlet容器中运行。
  • 请求处理:当一个Web请求到达Tomcat服务器时,如果是针对由Spring管理的Web应用程序,Tomcat会将请求转发给注册在容器中的DispatcherServlet。
  • 集成:Spring Boot应用程序可以内嵌Tomcat,使得开发者可以非常方便地将Spring应用程序部署在Tomcat上。DispatcherServlet作为Spring MVC的前端控制器,会注册到Tomcat中,处理所有到达Spring应用程序的请求。
  • 区别:
  • 功能定位:Tomcat:作为一个Web容器,主要负责处理HTTP请求,管理Servlet的生命周期,以及提供JSP引擎等功能。DispatcherServlet:作为Spring MVC的一部分,主要负责请求的分发,将请求映射到相应的控制器(Controller)上,处理请求并返回响应。
  • 职责范围:
  • Tomcat:处理底层的HTTP通信,提供Servlet运行环境。
  • DispatcherServlet:处理应用层的请求分发,管理Spring MVC的流程,如视图解析、异常处理等。
  • 配置和使用:
  • Tomcat:通常需要在server.xml等配置文件中配置端口、连接器、虚拟主机等信息。
  • DispatcherServlet:在Spring的配置文件中配置
  • 生命周期:
  • Tomcat:负责整个Web应用程序的生命周期,包括启动和关闭。
  • DispatcherServlet:仅负责处理请求的环节,其生命周期由Tomcat管理。

image.png

Server:指的就是整个 Tomcat 服 务器,包含多组服务,负责管理和 启动各个 Service,同时监听 8005 端口发过来的 shutdown 命令,用 于关闭整个容器 ;

Service:Tomcat 封装的、对外提 供完整的、基于组件的 web 服务, 包含 Connectors、Container 两个 核心组件,以及多个功能组件,各 个 Service 之间是独立的,但是共享 同一 JVM 的资源 ;

Connector:Tomcat 与外部世界的连接器,监听固定端口接收外部请求,传递给 Container,并 将 Container 处理的结果返回给外部;

Container:Catalina,Servlet 容器,内部有多层容器组成,用于管理 Servlet 生命周期,调用 servlet 相关方法。

image.png

java 线程池:原生线程池在达到核心线程数时,是优先添加队列,这样比较适合CPU密集型任务

当前线程>核心线程? false放入队列,true执行拒绝策略;

tomcat线程池:因为Tomcat的任务属于IO密集型,大概率不会长时间占用CPU资源。即期望任务堆积时,优先创建线程来处理,而不是入队

当前线程>核心线程?flase创建线程,ture放入队列;

DispatcherServletAutoConfiguration

DispatcherServletAutoConfiguration自动配置,生成 DispatcherServlet 实例化 DispatcherServlet--》 (HttpServletBean)init-- initServletBean-initWebApplicationContext---》createWebApplicationContext创建springmvc 子容器--》configureAndRefreshWebApplicationContext刷新子容器—》执行 refresh()刷新完之后--》onApplicationEvent监听容器刷新事件[ContextRefreshedEvent]---》(refreshEventReceived置为true)---》onRefresh---》initStrategies----》

static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized
    // by application developers.
    try {
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
      throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
    }
  }

1、initHandlerMappings- getDefaultStrategies-获取DispatcherServlet.properties文件里的默认值--》默认的三个handlermapping( BeanNameUrlHandlerMapping/RequestMappingHandlerMapping/RouterFunctionMapping)--》通过反射创建实例createDefaultStrategy(创建handlermapping bean) 赋值给handlerMappings;

RequestMappingHandlerMapping->afterPropertiesSet->initHandlerMethods--》getCandidateBeanNames(type(object.class))获取所有的bean--》processCandidateBean--》isHandler(beanType)--》【(AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));】--》registerHandlerMethod()--》MappingRegistry

RequestMappingHandlerMapping{

	private final MappingRegistry mappingRegistry;
        
	public final HandlerExecutionChain getHandler(HttpServletRequest request);
}


MappingRegistry{
        private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
	MultiValueMap<String, T> pathLookup;
}
pathLookup的key 就是全路径(类名+方法名),MappingRegistration 里有methodHander;
对于RequestMappingHandlerMapping,T就是RequestMappingInfo;


HandlerMethod{
	bean:对应的具体的controller;
	method:对应的方法;
}

2、initHandlerAdapters—> getDefaultStrategies-获取DispatcherServlet.properties文件里的默认值--( HttpRequestHandlerAdapter/SimpleControllerHandlerAdapter/RequestMappingHandlerAdapter/HandlerFunctionAdapter)

handlermapping和handlerAdapter 之间的组合关系

  • RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter RequestMappingHandlerMapping 将 URL 请求映射到标注了 @RequestMapping 或其派生注解的控制器方法。RequestMappingHandlerAdapter 处理这些控制器方法。 为什么要有这些adapter?因为走了一个门面方法 ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) (
  1. RequestMappingHandlerAdapter 与 HttpRequestHandlerAdapter / SimpleControllerHandlerAdapter RequestMappingHandlerAdapter 将 URL 请求映射到基于 Bean 名称的处理器。 HttpRequestHandlerAdapter 处理实现了 HttpRequestHandler 接口的处理器。 SimpleControllerHandlerAdapter 处理实现了 Controller 接口的处理器。
  2. RouterFunctionMapping 与 HandlerFunctionAdapter RouterFunctionMapping 将 URL 请求映射到基于函数式编程风格定义的 HandlerFunction。 HandlerFunctionAdapter 处理这些 HandlerFunction。 )

通过这些对应关系,Spring MVC 可以灵活地处理不同类型的请求和处理器,满足各种应用需求。

区别: HandlerMapping 的主要职责是根据请求信息(如 URL、HTTP 方法等)找到能够处理该请求的控制器或处理器方法。HandlerAdapter 的主要职责是将找到的处理器方法适配为 ModelAndView 对象,并执行该方法。 HandlerMapping 在 Spring MVC 的前期处理阶段工作,主要负责请求的路由和映射。HandlerAdapter 在后期处理阶段工作,主要负责执行处理器方法并生成响应。

3、initHandlerExceptionResolvers-->getDefaultStrategies-->获取DispatcherServlet.properties文件里的默认值--》默认的三个handlermapping( ExceptionHandlerExceptionResolver/ResponseStatusExceptionResolver/DefaultHandlerExceptionResolver--》--创建createDefaultStrategy--》 赋值到handlerExceptionResolvers;

  • ExceptionHandlerExceptionResolver:用于处理由 @ExceptionHandler 注解标注的方法的异常。适合在控制器内部或全局范围内定义细粒度的异常处理逻辑。
  • ResponseStatusExceptionResolver:用于处理由 @ResponseStatus 注解标注的异常。适合在异常类上直接指定 HTTP 状态码和原因,简化异常处理配置。
  • DefaultHandlerExceptionResolver:用于处理 Spring MVC 内部的标准异常,提供默认处理行为。适合处理常见的框架内部异常,减少开发者的处理负担。

ExceptionHandlerExceptionResolver-->(InitializingBean[afterPropertiesSet])->initExceptionHandlerAdviceCache-->findAnnotatedBeans(ControllerAdvice.class)注解的bean-》new ExceptionHandlerMethodResolver--》找到带有这个注解的 MethodIntrospector.selectMethods(ExceptionHandler.class方法)---》放到mappedMethods;

父子容器: Spring和SpringMVC为什么需要父子容器?

  • 就功能性来说不用子父容器也可以完成(参考:SpringBoot就没用子父容器)
  • 1、所以父子容器的主要作用应该是划分框架边界。有点单一职责的味道。service、dao层我们一般使用spring框架来管理、controller层交给springmvc管理
  • 2、规范整体架构 使 父容器service无法访问子容器controller、子容器controller可以访问父容器 service
  • 3、方便子容器的切换。如果现在我们想把web层从spring mvc替换成struts,那么只需要将spring mvc.xml替换成Struts的配置文件struts.xml即可,而spring-core.xml不需要改变
  • 4、为了节省重复bean创建

spring mvc 拦截器

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
   @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor());
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

请求执行流程:

image.png

  • (1)用户发送请求至前端控制器DispatcherServlet;
  • (2)DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler[getHandler];
  • (3)处理器映射器根据请求url找到具体的处理器Handler,生成处理器对象及处理器拦截器(如果有则生成),一并返回给DispatcherServlet;
  • (4)DispatcherServlet 调用 HandlerAdapter处理器适配器,请求执行Handler;
  • (5)HandlerAdapter 经过适配调用 具体处理器进行处理业务逻辑;
  • (6)Handler执行完成返回ModelAndView;
  • (7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
  • (8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
  • (9)ViewResolver解析后返回具体View;
  • (10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
  • (11)DispatcherServlet响应用户。

前后端分离项目

在前后端分离的项目中,使用 ModelAndView 的场景会大大减少,因为这种架构下,前端和后端通过 JSON 或其他数据格式进行通信,而不是通过服务器渲染的HTML页面。在这种情况下,后端主要的职责是提供RESTful API,返回数据通常是JSON对象,而不是HTML视图。

@RequestBody&@responseBody

@RequestBody 当客户端发送请求(如POST或PUT请求)到服务器时,@RequestBody注解告诉Spring MVC框架,请求体(Body)中的数据应该被转换或绑定到注解的方法参数上。Spring通过使用HTTP消息转换器(Message Converters)来解析请求体,并将其转换为相应的Java对象。

@RespouseBody: 当一个方法被@ResponseBody注解修饰时,它表明该方法的返回结果应该直接写入HTTP响应正文(Body)中。Spring使用HTTP消息转换器将方法的返回值转换为客户端可接受的格式(如JSON、XML)。 processDispatchResult--》render--》resolveViewName--》viewResolver.resolveViewName--》view.render渲染视图(将数据渲染到模型里)

接受请求的时候源码分析

前后端分离项目:

无论(HttpServlet)的dopost/doput/doGet/doDelete---》最后都走(FrameworkServlet)的processRequest--》doService(DispatcherServlet)方法--->doDispatch

  • --》getHandler【返回的是mehtod+执行链(HandlerExecutionChain)】-->org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler -->getHandlerInterna【获取handler,同一个url 获取hander有优先级(BeanNameUrlHandlerMapping> RequestMappingHandlerMapping】->getHandlerExecutionChain>-->lookupHandlerMethod(从这个【mappingRegistry,map里直接取;HandlerMapping里的一个属性】)-->getHandlerExecutionChain[拦截器]获取执行链(HandlerExecutionChain)
  • ->getHandlerAdapter(获取adapter)-->applyPreHandle (如果执行失败直接返回)---》
  • ha.handle(执行方法)----》invokeHandlerMethod-->invokeAndHandle-->invokeForRequest-->getMethodArgumentValues解析参数,把json--》转为实体--》执行方法doInvoke--》org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#beforeBodyWrite把响应写入响应体。
  • applyPostHandle(执行拦截器的后置处理器)-->执行异常processDispatchResult-->如果异常,异常处理机制--》渲染视图-》- 成功or异常(triggerAfterCompletion);

前后端非分离项目: 上面步骤继续执行以下操作 processDispatchResult--》render--》resolveViewName--》viewResolver.resolveViewName--》view.render渲染视图(将数据渲染到模型里)

异常处理: processDispatchResult-->processHandlerException-->resolver.resolveException 成功处理:

postHandle 方法在控制器方法执行完毕后立即被调用,但在视图渲染之前。因此,你可以在此方法中修改模型对象或添加额外的模型属性,这些修改将反映在视图渲染的结果中。

afterCompletion 方法在视图渲染完成后被调用,意味着你可以访问到完整的响应对象,包括响应状态码、响应头和响应体。然而,由于视图已经被渲染和发送到客户端,所以在此方法中对模型对象或响应对象的任何修改都不会影响到最终的响应结果。


HandlerExecutionChain{
	Object handler;//HandlerMethod
	List<HandlerInterceptor> interceptorList;//拦截器链
}


RequestMappingHandlerAdapter{
	boolean supports(Object handler);
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}