先来个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
不用多说了吧,这次真的可以结束了,撒花!!!!!!!!!!!!!!!!!