前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【springmvc】1. DispatcherServlet的注入

【springmvc】1. DispatcherServlet的注入

作者头像
平凡的学生族
发布2020-03-24 17:11:21
9410
发布2020-03-24 17:11:21
举报
文章被收录于专栏:后端技术

参考Spring Boot与Spring MVC集成启动过程源码分析

1. refresh

1.1 Springboot启动流程中调用refresh

SpringApplication.run一路运行到refresh方法:

代码语言:javascript
复制
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

在启动web服务时,此处的applicationContext类型是AnnotationConfigServletWebServerApplicationContext,原因省略。

1.2 refresh流程中调用createWebServer

此处AbstractApplicationContext执行refresh方法,进而调用onRefresh:

代码语言:javascript
复制
@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            ...
            try {
                ...
                // Initialize other special beans in specific context subclasses.
                onRefresh();
                ...
            }
            ...
        }
    }

AbstractApplicationContext.refresh->ServletWebServerApplicationContext.onRefresh

代码语言:javascript
复制
// ServletWebServerApplicationContext
@Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            ...
        }
    }
代码语言:javascript
复制
private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        else if (servletContext != null) {
            ...
        }
        initPropertySources();
    }

调试到此处会进入第一个if语句:

  • getSelfInitializer()实际上创建了下图的ServletWebServerApplicationContext::lambda,实际上调用ServletWebServerApplicationContext::selfInitialize,它的作用很重要。
  • factory.getWebServer做了如下事情:
    1. 将TomcatEmbeddedContext注入Tomcat的host
    2. 将包括ServletWebServerApplicationContext::lambda的其它初始器注入TomcatStarter
    3. 将TomcatStarter注入Context(TomcatEmbeddedContext)

1.3 附录:关于Servlet

为防止读不懂上一小节,特此附上关于对接Servlet3.0的规范。(主要介绍ServletContainerInitializer,关于filter只是拓展阅读) servlet和filter的区别

  • Filter可认为是Servlet的一种“变种”,它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理,是个典型的处理链。
  • 它与Servlet的区别在于:它不能直接向用户生成响应。完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。

servlet filter生命周期

servlet filter都是在容器启动时初始化,调用init;都是在容器关闭时随着销毁,调用destroy

Servlet3.0后,定义servlet和filter有三种方式:

  1. @WebServlet ,@WebFilter
  2. 调用ServletContext#addServlet等方法
  • javax.servlet.ServletContextListener#contextInitialized(ServletContextEvent sce)回调函数中调用
  • javax.servlet.ServletContainerInitializer#onStartup(Set<Class<?>> c, ServletContext ctx)回调函数中调用

2. 回调selfInitialize注入DispatcherServlet

2.1 获取ServletContextInitializer的Bean

根据上一章,selfInitialize会在Tomcat容器启动时间接被回调:

代码语言:javascript
复制
private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
代码语言:javascript
复制
    /**
     * Returns {@link ServletContextInitializer}s that should be used with the embedded
     * web server. By default this method will first attempt to find
     * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
     * {@link EventListener} beans.
     * @return the servlet initializer beans
     */
    protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
        return new ServletContextInitializerBeans(getBeanFactory());
    }
  1. 代码中调用getServletContextInitializerBeans(),获取类型为ServletContextInitializer的Bean,并回调onStartup方法。并此处我们不展开分析方法,有需要的可以看源码验证一下。
  2. 被回调的Bean就包括DispatcherServletRegistrationBean。其注入的地方,和它的效果,我们下文讲解

2.2 DispatcherServletRegistrationBean

2.2.1 注入

spring-boot-autoconfigure/META-INF/spring.factories中有一段配置:

代码语言:javascript
复制
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\

看下其实现

代码语言:javascript
复制
..
@Configuration(proxyBeanMethods = false)
..
public class DispatcherServletAutoConfiguration {

    /*
     * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    /*
     * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    @Configuration(proxyBeanMethods = false)
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
    protected static class DispatcherServletConfiguration {

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            ..
            return dispatcherServlet;
        }
        ..

    }

    @Configuration(proxyBeanMethods = false)
    ..
    protected static class DispatcherServletRegistrationConfiguration {

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
                WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                    webMvcProperties.getServlet().getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
            multipartConfig.ifAvailable(registration::setMultipartConfig);
            return registration;
        }

    }

利用Bean机制:

  1. 创建DispatcherServlet,并作为Bean注入
  2. 接受DispatcherServlet为参数,创建DispatcherServletRegistrationBean,Bean再作为Bean注入。

2.2.2 回调

DispatcherServletRegistrationBean继承ServletContextInitializer接口,因此也会被回调。其回调实现在RegistrationBean中:

代码语言:javascript
复制
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
  String description = getDescription();
  f (!isEnabled()) {
    logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
    return;
  }
    register(description, servletContext);
}

register方法是一个模板方法,调用子类DynamicRegistrationBean的实现:

代码语言:javascript
复制
@Override
protected final void register(String description, ServletContext servletContext) {
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?)");
        return;
    }
    configure(registration);
}

addRegistration方法又是一个模板方法,调用子类ServletRegistrationBean的实现,

代码语言:javascript
复制
@Override
  protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}

当前处于tomcat启动环境中,调用servletContext.addServlet(javax.servlet.ServletContext)注入了this.servlet,也就是在上文注入的DispatcherServlet。 至此,DispatcherServlet已完成注入

总结

  1. spring利用Tomcat的Servlet中,ServletContainerInitializer会被回调的机制,实现了TomcatStarter,来回调spring自己实现的ServletContextInitializer回调函数。
  2. ServletWebServerApplicationContext::selfInitialize在回调时会获取类型为ServletContextInitializer的Bean并调用其回调函数onStartup,其中包括DispatcherServletRegistrationBean
  3. DispatcherServletRegistrationBean利用springboot的自动配置机制事先被注入为Bean,在回调执行时将DispatcherServlet注入Context中,从而注入到了Tomcat中。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. refresh
    • 1.1 Springboot启动流程中调用refresh
      • 1.2 refresh流程中调用createWebServer
        • 1.3 附录:关于Servlet
        • 2. 回调selfInitialize注入DispatcherServlet
          • 2.1 获取ServletContextInitializer的Bean
            • 2.2 DispatcherServletRegistrationBean
              • 2.2.1 注入
            • 2.2.2 回调
            • 总结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档