springboot如何添加dispacherServerlet到tomcat中(多图长文不信你看不懂!!!)

先来个spring-web内置tomcat启动的例子

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerMapping;
import web.controller.BeanController;

import java.io.File;
import java.util.List;

public class Application {

    public static void main(String[] args) throws LifecycleException {
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(AppConfig.class);
        applicationContext.refresh();

        //BeanController beanController= (BeanController) applicationContext.getBean("/bean");
        //System.out.println(beanController);

        File file = new File(System.getProperty("java.io.tmpdir"));

        Tomcat tomcat = new Tomcat();
        tomcat.setPort(8080);

        Context rootContext = tomcat.addContext("/", file.getAbsolutePath());

        DispatcherServlet dispatcher = new DispatcherServlet(applicationContext);
        tomcat.addServlet(rootContext, "web", dispatcher)
                .setLoadOnStartup(1);

        rootContext.addServletMapping("/", "web");

        tomcat.start();

        List<HandlerMapping> handlerMappings= dispatcher.getHandlerMappings();
        for(HandlerMapping hm:handlerMappings){
        }
        tomcat.getServer().await();

    }

}

我们可以看到在tomcat.start();之前我们手动调用tomcat.addServlet方法,添加的DispatcherServlet到tomcat当中,那么springboot是如何做的呢

来看下spring启动流程

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

    public static void main(String[] args) throws InterruptedException {
        
        //SpringApplication().run(Application.class);
        //SpringApplication(Application.class).run(args);
        //SpringApplication.run(Application.class, args);
        SpringApplication application = new SpringApplication(Application.class);
        ConfigurableApplicationContext applicationContext = application.run(args);

    }

}
SpringApplication实例化只要是一些初始化、判断当前是一个什么工程(REACTIVE、SERVLET、普通java项目)、添加listener、找到主启动方法(这个挺有意思的)

我们接着看run方法,去找tomcat的启动流程

我们注意到context的实例是org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext,继承树如下

进入refreshContext方法

 

我们发现它调用的其实是父类的refresh

这个太熟悉了吧,AbstractApplicationContext,看过spring源码的估计都把这个方法翻烂了,我们知道这个类的onRefresh()方法其实是个空壳方法,这个方法前面是处理完所有的BeanDefinition、实例化了各种系统内置的class、BeanPostProcessors以及multicaster等等,方法后面主要是注册监听器、实例化对象、注入属性等

但是当我们进入onRefresh方法后,发现他被重写了,看到createWebServer这个方法了吧,这个就是tomcat启动的核心,跟进去

第一次进来webServer和servletContext都是空的,进入创建代码块,先看getWebServerFactory

这个方法是在bean工厂中拿到名字为tomcatServletWebServerFactory,类型为ServletWebServerFactory.class的factory对象,然后叫这个factory去创建我们的webServer,看名字都知道是工厂模式,我看下第二行代码:this.webServer = factory.getWebServer(getSelfInitializer())

看到tomcat创建的地方了吧,前面是实例化,设置connector、autoDeploy、、、serverlet是要放在context当中的,我们看prepareContext方法

前面都是设置属性以及是不是要创建默认的Servlet,继续进入configureContext(context, initializersToUse)

关键点来了啊,我们看一下TomcatStarter这个类

TomcatStarter实现了ServletContainerInitializer接口重写了onStartup这个方法,实现ServletContainerInitializer接口的类会在容器启动过程中被调用其onStartup()方法,也就是说他会被tomcat启动时调用到,好了这个时候就可以直接在onStartup里面打上断点按F8了  ->_->

进入方法,我们发现initializers有三个对象,前两个像是lambda和session的支持,第三个就是我们要找的设置dispathcer的地方,跟进

看到没,第一个就是,这个对象是DispatcherServletRegistrationBean,里面存了dispatcherServlet,是由这个类往context中添加的,看继承树

跟进去

发现入了父类RegistrationBean的onStartup方法,继续进入register方法

发现进入的是父类DynamicRegistrationBean的register()方法,看到了吧,就在这里,继续跟进

 

先看addServerlet前半部分

前面全是空,创建wrapper,wrapper设置名字为dispatcherServlet,然后添加到context(TomcatEmbeddedContext)中,跟进去

protected final HashMap<String, Container> children = new HashMap<>();
children是一个context中的map,我们实际上是调用ContainerBase.addChildInternal把dispatcherServlet放到了这个map中
我们注意到,上面并没有给wrapper赋值,只是给他设置了名字,instance还是空的,接着再看addServerlet后部分

这里继续给wrapper设置servletClass和dispatcherServlet的真实对象,至此我们看一下context的属性

里面有两个值,第一个default是前面创建的默认值,第二个就是我们要找的的dispatcherServlet,并且instance也有实例了

但是我们发现servletMappings里面还是指向default,正常我们应该指向新加进去的dispatcherServlet才对,

回到DynamicRegistrationBean.register方法,看addRegistration()方法下面的configure(registration);

在urlmappings里面找到了“/”这个路径进入ApplicationServletRegistration.addMapping()方法

发现这个类是可以被覆盖的,然后就把他从servletMappings中移除了

移除完成后又发了个通知,感兴趣的listener可以做相应的处理

然后再把dispatcherServlet加到servletMappings

至此路径指向正确的servlet,剧终/撒花!!!!!!

但是好像还有个问题,dispatcherServlet在哪创建的呢,全称没有看到创建啊,DispatcherServletRegistrationBean中直接就有实例啊,另外DispatcherServletRegistrationBean在哪来的呢?

spring-boot-autoconfigure了解一下,spring boot会在启动的时候加载spring.factories这个文件,下图是源码

private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

SpringFactoriesLoader.loadSpringFactories()这个方法来读取文件,因为这个方法会被调用多次,所以会放到cache这个map中

我们看spring-boot-autoconfigure的spring.factories

在众多的路径里面我们找到了

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration

不用多说了吧,这次真的可以结束了,撒花!!!!!!!!!!!!!!!!!

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值