SpringMVC原理(设计原理+启动原理+工作原理)-正文

时间:2024-03-02 10:40:38

在Java还没有SpringMvc时,使用的是servlet + jsp 的方式,对外提供接口,以及和页面进行数据交互等操作。
但是,这种操作,毕竟还是不方便,功能也不够强大。
曾经的写法,需要配置xml文件,如果页面够多,光配置就是一大堆。

发展到后来,servlet3.0的时候,出现了完全注解版的写法。

关于servlet的描述这里不做过多解释,本文将对对springmvc中使用到的servlet特征进行阐述,继而分析它的设计原理,springmvc启动原理,以及工作原理

等到在Spring框架中的时候,就已经是DispatcherServlet了。

而它本身就是一个servlet,其类关系图如下:
在这里插入图片描述

一、设计原理

1.1 servlet生命周期简述

Servlet生命周期分为三个阶段:

  1. 初始化阶段:调用init()方法实现初始化工作。
  2. 运行阶段:处理请求,容器创建代表HTTP请求的ServletRequest对象和代表HTTP响应的ServletResponse对象,并将它们作为参数传递给Servletservice()方法。
  3. 销毁阶段:Servlet将被销毁,生命周期结束。

Servlet本身只是一个接口,在HttpServlet实现类中,对service()方法进行了实现。
而这里的实现是套用了模版方法设计模式,将service的职责拆分了,按照请求方法的类型不同划分。

比如,如果请求方法是 GET请求,则会执行到 HttpServletdoGet方法;如果是POST请求,则会执行到 HttpServletdoPost方法。

FrameworkServlet 又对HttpServlet中的service方法进行了重写:

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
        super.service(request, response);
    } else {
        this.processRequest(request, response);
    }

}

这里判断了请求方法,默认执行 HttpServletservice方法。
但是实际调用的是 doGetdoPost这类方法。而同时,这类方法也被FrameworkServlet重写了

也就是说,servlet 会触发执行到 FrameworkServletprocessRequest 方法。如下图所示:在这里插入图片描述
这里会执行FrameworkServletdoService方法。而这是一个抽象方法。其子类DispatcherServlet 对其进行了实现。如此便贯通了。

1.2 设计原理小结

在1.1小节中的分析中,Servlet继承&实现的关系如下:
在这里插入图片描述
也就是说,在servlet处理请求时,对于springmvc而言,就是执行 doService方法。

二、启动原理

这一小节,主要分析SpringBoot项目启动时,对SpringMvc部分的处理。

2.1 AbstractHandlerMethodMapping 初始化 --RequestMapping注解解析

谈起SpringMvc,最先想起来的俩注解应该是 ControllerRequestMapping
而关于启动项目时,框架对这俩注解的处理,基本都体现在AbstractHandlerMethodMapping中。

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	// ...省略其他方法
	
	public void afterPropertiesSet() {
        this.initHandlerMethods();
    }
}

首先,AbstractHandlerMethodMapping是个抽象类,它的子类会放到Spring容器中。
而在它的子类 RequestMappingHandlerMapping 中,对初始化方法进行了重写,具体内容如下:

public void afterPropertiesSet() {
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch());
    this.config.setContentNegotiationManager(this.getContentNegotiationManager());
    if (this.getPatternParser() != null) {
        this.config.setPatternParser(this.getPatternParser());
        Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch, "Suffix pattern matching not supported with PathPatternParser.");
    } else {
        this.config.setSuffixPatternMatch(this.useSuffixPatternMatch());
        this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch());
        this.config.setPathMatcher(this.getPathMatcher());
    }

    super.afterPropertiesSet();
}

在设置了一堆配置之后,最终调用的还是 AbstractHandlerMethodMappinginitHandlerMethods 方法。


    protected void initHandlerMethods() {
    	// 获取spring容器中的beanName
        String[] var1 = this.getCandidateBeanNames();
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            String beanName = var1[var3];
            if (!beanName.startsWith("scopedTarget.")) {
            	// 通过beanName映射出methodHandler
                this.processCandidateBean(beanName);
            }
        }

		// 初始化handlerMethods
        this.handlerMethodsInitialized(this.getHandlerMethods());
    }

  
    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;

        try {
        	// 通过beanName获取当前的类型
            beanType = this.obtainApplicationContext().getType(beanName);
        } catch (Throwable var4) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Could not resolve type for bean '" + beanName + "'", var4);
            }
        }

		// 当前beanName对应的类定义不为空,并且带有Controller 或 RequestMapping注解时,对其进行处理
        if (beanType != null && this.isHandler(beanType)) {
            this.detectHandlerMethods(beanName);
        }

    }

可以看到,最终处理控制器时,是调用了detectHandlerMethods 方法。具体内容如下:

protected void detectHandlerMethods(Object handler) {
	// 通过beanName获取到对应的类型
    Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();
    if (handlerType != null) {
    	// 获取你自己定义的控制器类型
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        // 将类中符合条件(标注了RequestMapping注解)的 method 映射为 RequestMappingInfo 对象,并放入map中;这一步的实现,在其子类中。并且聚合它们的请求路径。
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
            try {
                return this.getMappingForMethod(method, userType);
            } catch (Throwable var4) {
                throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);
            }
        });
        
		// 记录日志
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(this.formatMappings(userType, methods));
        } else if (this.mappingsLogger.isDebugEnabled()) {
            this.mappingsLogger.debug(this.formatMappings(userType, methods));
        }

		// 方法注册
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            this.registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }

}

这里对方法的处理分了两步,第一步,根据类型简单处理注解,主要是聚合了请求路径。聚合后的结果如下:

可以看到,这里的路径属性已经有值了。
然后就是注册方法了。
而真正注册的方法registerHandlerMethod 是在其子类中实现的。具体内容如下:
在这里插入图片描述

在其父级的实现中,注册的结果如下:
在这里插入图片描述

最后,简单处理RequestBody注解(如果使用了该注解,参数必填)。

2.2 DispatcherServlet 的初始化

框架中定义了自动配置类 DispatcherServletAutoConfiguration。其有个内部类 DispatcherServletConfiguration 对 DispatcherServlet 进行了配置。具体如下:

@Conditional({DefaultDispatcherServletCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
protected static class DispatcherServletConfiguration {
    protected DispatcherServletConfiguration() {
    }

    @Bean(
        name = {"dispatcherServlet"}
    )
    public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

    @Bean
    @ConditionalOnBean({MultipartResolver.class})
    @ConditionalOnMissingBean(
        name = {"multipartResolver"}
    )
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
        return resolver;
    }
}

在第一节设计原理的时候,提到过DispatcherServlet 也是一个servlet 。那么它的初始化,也就包含在servlet的生命周期中。

在servlet生命周期中,有 init 方法,进行初始化。FrameworkServlet 有一个父类HttpServletBean,其中定义了初始化方法。虽然不是原生的servlet 初始化方法,但是也是会间接调用到的(通过模版方法设计模式,由子类代为实现)

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware 

FrameworkServlet 触发初始化时,会执行到 initServletBean 方法。其中有两个比较关键初始化方法,具体如下:
在这里插入图片描述
篇幅原因,这里只拿出关键代码。initWebApplicationContext()内,有这样一段,在处理请求时,会执行到:

if (!this.refreshEventReceived) {
      synchronized(this.onRefreshMonitor) {
            this.onRefresh(wac);
      }
}

onRefresh 由子类DispatcherServlet 重写后,就成了这样:

protected void onRefresh(ApplicationContext context) {
 	this.initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    // 初始化上传文件解析器
    initMultipartResolver(context);
 // 初始化本地解析器
    initLocaleResolver(context);
 // 主题处理器
    initThemeResolver(context);
 // 映射处理器
    initHandlerMappings(context);
 // 处理适配器
    initHandlerAdapters(context);
 // 异常处理器
    initHandlerExceptionResolvers(context);
 // 请求到视图名的翻译器
    initRequestToViewNameTranslator(context);
 // 视图解析器
    initViewResolvers(context);
 // 初始化FlashManager
    initFlashMapManager(context);
}

如果大家想看看这里执行的内容,以及初始化后的结果,可以自行打断点查看。这里因为东西较多,我就不截图了。

PS: 下一小节以 HandlerMappings 为例,进行说明

2.3 DispatcherServlet#initHandlerMappings(…) 初始化示例说明

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
    	// 获取所有的handlerMapping
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        // 对handlerMapping进行排序
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList(matchingBeans.values());
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
        	// 获取名字是 handlerMapping 的handlerMapping
            HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException var4) {
        }
    }
	
	// handlerMappings为空,需要设置默认的handlerMapping
    if (this.handlerMappings == null) {
        this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No HandlerMappings declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
        }
    }

    Iterator var6 = this.handlerMappings.iterator();

    while(var6.hasNext()) {
        HandlerMapping mapping = (HandlerMapping)var6.next();
        if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }
}

默认情况下,如果配置了使用全部,会有以下handlerMapping:
在这里插入图片描述
另外补充一句,如果使用默认的handlerMapping,需要配置DispatcherServlet.properties

三、工作原理

这一小节,以DispatcherServlet 为起点,分析SpringMvc的工作原理。

// todo 待完善–预计2024春节后补充,春节期间玩去了。