当前位置: 首页 > article >正文

spring-mvc源码分析v3.3.0

分析下springboot内嵌tomcat启动流程,即springboot-mvc

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.0</version>
</dependency>

环境信息

  • Java 22
  • Spring Boot v3.3.0
  • Apache Tomcat/10.1.24
  • spring-boot-starter-web 3.3.0

测试项目主要文件结构:

@RestController
public class Controller {
    @GetMapping("/test")
    public String test(){
        return "test";
    }
}

参考文章

  • springboot启动流程
  • tomcat源码分析

下面开始分析源码

1. 创建tomcat服务

要从启动springboot开始说起,在springApplication.run.refreshContext.refresh.onRefresh这一步中,创建tomcat服务。

@Override
protected void onRefresh() {
    super.onRefresh();//设置springboot主题。主要用于国际化和本地化的场景。它允许应用程序根据用户的区域设置动态地更改界面的外观和感觉
    try {
        createWebServer();//创建tomcat
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

private void createWebServer() {
    WebServer webServer = this.webServer;//null
    ServletContext servletContext = getServletContext();//null
    if (webServer == null && servletContext == null) {//true
        StartupStep createWebServer = getApplicationStartup().start("spring.boot.webserver.create");//记录web开始创建步骤
        //webServer创建工厂,这个是重点
        ServletWebServerFactory factory = getWebServerFactory();
        createWebServer.tag("factory", factory.getClass().toString());
        this.webServer = factory.getWebServer(getSelfInitializer());
        createWebServer.end();
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                new WebServerGracefulShutdownLifecycle(this.webServer));
        //bean注册webServerStartStop
        getBeanFactory().registerSingleton("webServerStartStop",
                new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    //初始化配置属性,配置环境
    initPropertySources();
}

1.1. webServer创建工厂getWebServerFactory()

因为程序执行到这一步的时候,springboot beanFactory已经完成所有的bean定义了,然后获取ServletWebServerFactory类型的bean,然后实例化

protected ServletWebServerFactory getWebServerFactory() {
    // Use bean names so that we don't consider the hierarchy
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    //只有一个元素:tomcatServletWebServerFactory
    if (beanNames.length == 0) {
        throw new MissingWebServerFactoryBeanException(getClass(), ServletWebServerFactory.class,
                WebApplicationType.SERVLET);
    }
    if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
                + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    //createBean并返回 factory = {TomcatServletWebServerFactory@6532} 
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

1.2. 实例化webServer服务factory.getWebServer(getSelfInitializer())

getSelfInitializer()是一个Lambda方法引用。

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}
//context初始化的时候执行
private void selfInitialize(ServletContext servletContext) throws ServletException {
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

继续看getWebServer方法。这一步就是模仿了原生的tomcat启动流程,创建server、service、connector、egine、host、context
值得注意的是内嵌的tomcat是直接创建了一个context。而不是扫描webapps目录

//默认的连接协议,nio
public static final String protocol = "org.apache.coyote.http11.Http11NioProtocol";

public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {//true
        Registry.disableRegistry();//禁用tomcat注册对象到MBean
    }
    Tomcat tomcat = new Tomcat();
    //这个Tomcat类里面是一些基本的操作,基本的属性,我认为这就是一个配置上下文类,如下
    // protected Server server;
    // protected int port = 8080;
    // protected String hostname = "localhost";
 

    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");//C:\Users\SHENSH~1\AppData\Local\Temp\tomcat.8080.1801441663765902424
    tomcat.setBaseDir(baseDir.getAbsolutePath());

    //创建Connector,构造方法中创建了Http11NioProtocol
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);

    //创建Server和Service
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    registerConnectorExecutor(tomcat, connector);

    //创建Host和Engine
    tomcat.getHost().setAutoDeploy(false);//禁用自动部署,不扫描webApps目录

    //创建context
    prepareContext(tomcat.getHost(), initializers);

    //创建tomcatWebServer,返回给springboot,用于后续操作
    return getTomcatWebServer(tomcat);
}

1.2.1. 创建Connector

new Connector("org.apache.coyote.http11.Http11NioProtocol");

public Connector(String protocol) {
    configuredProtocol = protocol;
    ProtocolHandler p = null;
    try {
        p = ProtocolHandler.create(protocol);
    } catch (Exception e) {
        log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);
    }
}

1.2.2. 创建Server和Service

public Service getService() {
    return getServer().findServices()[0];
}
public Server getServer() {

    if (server != null) {
        return server;
    }

    System.setProperty("catalina.useNaming", "false");

    server = new StandardServer();//创建StandardServer

    initBaseDir();

    // Set configuration source
    ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

    server.setPort(-1);

    Service service = new StandardService();//创建StandardService
    service.setName("Tomcat");
    server.addService(service);
    return server;
}

1.2.3. 创建Host和Engine

public Host getHost() {
    Engine engine = getEngine();
    if (engine.findChildren().length > 0) {//获取host
        return (Host) engine.findChildren()[0];
    }

    Host host = new StandardHost();//创建StandardHost
    host.setName(hostname);
    getEngine().addChild(host);
    return host;
}

public Engine getEngine() {
    Service service = getServer().findServices()[0];
    if (service.getContainer() != null) {//获取engine
        return service.getContainer();
    }
    Engine engine = new StandardEngine();//创建StandardEngine
    engine.setName("Tomcat");
    engine.setDefaultHost(hostname);
    engine.setRealm(createDefaultRealm());
    service.setContainer(engine);
    return engine;
}

1.2.4. 创建Context

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();//创建TomcatEmbeddedContext,这个是内嵌的tomcat上下文
   
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    host.addChild(context);
    configureContext(context, initializersToUse);//配置context。没有重要逻辑
}

1.2.5. 创建tomcatWebServer封装对象

实例化TomcatWebServer、执行server的init方法

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
    initialize();//启动server,执行start方法
}
1.2.5.1. 启动server,执行start方法

initialize();
//启动server,执行start方法
//这个初始化方法和原生tomcat的start方法一样
//值得注意的是,这里disableBindOnInit禁用了初始化时候绑定8080端口

//StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[]
Context context = findContext();
context.addLifecycleListener((event) -> {
    if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
        // Remove service connectors so that protocol binding doesn't
        // happen when the service is started.
        //当执行start context时候,会删除service中的connector,放到TomcatWebServer中
        removeServiceConnectors();
    }
});
//t禁用了初始化时候绑定8080端口
disableBindOnInit();

// Start the server to trigger initialization listeners
this.tomcat.start();

我们重点看下TomcatEmbeddedContext的启动代码

// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> entry : initializers.entrySet()) {
    try {
        //entry.getKey(),key = {TomcatStarter@6708}
        entry.getKey().onStartup(entry.getValue(), getServletContext());
    } catch (ServletException e) {
        log.error(sm.getString("standardContext.sciFail"), e);
        ok = false;
        break;
    }
}

@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        for (ServletContextInitializer initializer : this.initializers) {
            //initializer = {ServletWebServerApplicationContext$lambda@6808} 
            initializer.onStartup(servletContext);
        }
    }
    catch (Exception ex) {
    }
}
//这个onStartup方法,正是context初始化的时候执行的,参考【## 1.2. 实例化webServer服务】标题
private void selfInitialize(ServletContext servletContext) throws ServletException {
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {//在beanFactory中匹配ServletContextInitializer类型的
        //beans = {DispatcherServletRegistrationBean@6931} "dispatcherServlet urls=[/]"
        beans.onStartup(servletContext);
    }
}

//最终是创建了StandardWrapper并添加到context中
//servlet = {DispatcherServlet@6943}  这个就是最重要的DispatcherServlet,是在DispatcherServletAutoConfiguration类中创建的
wrapper = new StandardWrapper();
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServlet(servlet);

1.3. 注册tomcat bean生命周期WebServerStartStopLifecycle

getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
这个类WebServerStartStopLifecycle继承了Lifecycle

2. connector启动

上面分析过,执行context的启动时候,移除了connector,那么又是在哪一步启动的connector呢?

springApplication.run.refreshContext.refresh.finishRefresh这一步中,connector启动。

// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
//onRefresh调用了startBeans
try {
    startBeans(true);
}

private void startBeans(boolean autoStartupOnly) {
    Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();//beanFactory中匹配Lifecycle类型的bean
    //包含WebServerStartStopLifecycle
    lifecycleBeans.values().forEach(LifecycleGroup::start);
}

//this = {TomcatWebServer@5764} 
public void start() throws WebServerException {
    addPreviouslyRemovedConnectors();
}

private void addPreviouslyRemovedConnectors() {
    Service[] services = this.tomcat.getServer().findServices();
    for (Service service : services) {
        //service = {StandardService@6979} "StandardService[Tomcat]"
        Connector[] connectors = this.serviceConnectors.get(service);
        if (connectors != null) {
            for (Connector connector : connectors) {
                //connector = {Connector@7663} "Connector["http-nio-8080"]" 添加connector到service
                service.addConnector(connector);
            }
            this.serviceConnectors.remove(service);
        }
    }
}

//启动connector
public void addConnector(Connector connector) {
    connector.setService(this);
    connector.start();
}

//绑定8080端口
bindWithCleanup();

// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();

// Start acceptor thread
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();

这里启动了2个线程执行nio模式。acceptor线程用于阻塞监听8080端口,获取到新连接socketChannel,存到tomcat的events事件队列;
poller线程用于循环读取events事件队列,获取新新连接socketChannel,最终通过Selector获取已准备好的channel,执行具体的controller逻辑。
详情请参考另一篇tomcat源码分析文章。

3. 内嵌tomcatEmbedded请求流程

到此内嵌tomcatEmbedded已经启动成功,接下来我们请求GET http://localhost:8080/test来分析一下执行流程。也和原生tomcat差不多。
我们从阻塞监听8080端口开始分析流程。

3.1. Acceptor获取新连接socketChannel

下面这个方法是sun.nio.ch.ServerSocketChannelImpl#implAccept源码java中的。

private int implAccept(FileDescriptor fd, FileDescriptor newfd, SocketAddress[] saa)
    throws IOException
{
    //此类实现 IP 套接字地址 (IP 地址 + 端口号)
    InetSocketAddress[] issa = new InetSocketAddress[1];
    //阻塞方法,是native方法,监听8080端口,直到有新连接请求
    int n = Net.accept(fd, newfd, issa);
    //我们调用[GET http://localhost:8080/test]后,代码继续执行
    if (n > 0) //n = 1
        saa[0] = issa[0];
    return n;
}

//最终返回SocketChannelImpl对象
return new SocketChannelImpl(provider(), family, newfd, sa);

//然后进入到NioEndpoint
endpoint.setSocketOptions(socket);

//注册socketChannel到events
protected boolean setSocketOptions(SocketChannel socket) {
    NioChannel channel = new NioChannel();
    NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);

    channel.reset(socket, newWrapper);
    //注册
    poller.register(newWrapper);
    return true;
}

public void register(final NioSocketWrapper socketWrapper) {
    socketWrapper.interestOps(SelectionKey.OP_READ);//读事件
    PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
    //events = new SynchronizedQueue<>()
    events.offer(pollerEvent);
}

3.2. Poller消费SocketChannel

poller线程一直是死循环读取events,然后调用Processor协议的处理器

@Override
public void run() {
    while (true) {
        //读取events
        events();

        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        while (iterator != null && iterator.hasNext()) {
            SelectionKey sk = iterator.next();
            NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
            //调用协议的处理器
            processKey(sk, socketWrapper);
        }
    }
}

//读取events
public boolean events() {
    PollerEvent pe = null;
    //遍历events
    for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {
        result = true;
        SocketChannel sc = socketWrapper.getSocket().getIOChannel();

        final SelectionKey key = sc.keyFor(getSelector());
        final NioSocketWrapper attachment = (NioSocketWrapper) key.attachment();
        //设置socketChannel为读或写事件
        int ops = key.interestOps() | interestOps;
        attachment.interestOps(ops);
        key.interestOps(ops);
    }
}

//调用协议的处理器 {NioEndpoint$Poller@6539}
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
    if (sk.isReadable()) {//读,我们这里的[GET http://localhost:8080/test]请求是读事件
        processSocket(socketWrapper, SocketEvent.OPEN_READ, true)
    }
    if (sk.isWritable()) {
        processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)
    }
}

//封装Runnable接口,异步处理socket请求
public boolean processSocket(SocketWrapperBase<S> socketWrapper, SocketEvent event, boolean dispatch) {
    SocketProcessorBase<S> sc = new SocketProcessor(socketWrapper, event);//Runnable接口实现类
    Executor executor = getExecutor();
    //异步执行
    executor.execute(sc);    
}

@Override
public void run() {
    //getHandler() = {AbstractProtocol$ConnectionHandler@6630} 
    getHandler().process(socketWrapper, event);
}

//Http11Processor继续处理socket
if (status == SocketEvent.OPEN_READ) {
    state = service(socketWrapper);
}

//下面这几个方法,看之前先了解一下tomcat组件的关系图
//【servlet封装成wrapper,wrapper添加到context,context是host的子容器,host属于engine,engine在service中,service是顶级容器server的子容器。】


//getAdapter() = {CoyoteAdapter@8348} 
getAdapter().service(request, response);
//通过请求路径匹配对应的wrapper,根据[localhost:8080/test]匹配
//因为这里是内嵌的tomcat,匹配到了默认的StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[].StandardWrapper[dispatcherServlet]
postParseSuccess = postParseRequest(req, request, res, response);
//engine
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
//host
host.getPipeline().getFirst().invoke(request, response);
//context = {TomcatEmbeddedContext@8433}
context.getPipeline().getFirst().invoke(request, response);
//wrapper
wrapper.getPipeline().getFirst().invoke(request, response);
//过滤器,StandardWrapperValve里面调用了过滤器链
filterChain.doFilter(request.getRequest(), response.getResponse());
//filterChain = {ApplicationFilterChain@8496}

//执行到最后一个过滤器后,再调用service,这里的servlet是 {DispatcherServlet@8470} 
servlet.service(request, response);

//调用doGet方法
if (method.equals(METHOD_GET))
        doGet(req, resp);

doService(request, response);
//所有的请求最终都走到了DispatcherServlet的doDispatch
doDispatch(request, response);

3.3. DispatcherServlet的doDispatch

这里会DispatcherServlet通过请求路径匹配对应的mappedHandler,然后调用Controller层逻辑并获取返回值,再把返回值封装到ModelAndView

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //通过请求路径匹配对应的mappedHandler
    mappedHandler = getHandler(request);
    //mappedHandler = [cn.xxx.updownloadfile.contr.Controller#test()] 这个是我项目中自己的Controller层的类

    //再通过反射调用cn.xxx.updownloadfile.contr.Controller#test()方法,获取mv
    //如果是@ResponseBody修饰的,这个mv返回是空,如果没有这个注解,则返回对应的文件名称,例如返回"t.html"
    ModelAndView mv = mappedHandler.handle(processedRequest, response); //method.invoke(getBean(), args)

    //处理程序选择和处理程序调用的结果,即 ModelAndView
    //如果mv不是null,则会匹配项目中的对应文件,读取文件内容然后返回给前端,例如读取文件t.html
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

http://www.kler.cn/a/504384.html

相关文章:

  • 工程水印相机结合图纸,真实现场时间地点,如何使用水印相机,超简单方法只教一次!
  • Chatper 4: Implementing a GPT model from Scratch To Generate Text
  • 我国无人机新增实名登记110.3 万架,累计完成飞行2666万小时
  • golang运维开发-gopsutil(1)
  • primitive 的 Appearance编写着色器材质
  • springboot
  • C++并发编程之无锁数据结构及其优缺点
  • 基于springboot的幼儿园管理系统系统
  • 蓝桥杯 男女搭配
  • Golang学习笔记_25——协程
  • 服务器一次性部署One API + ChatGPT-Next-Web
  • Shell Integration Unavailable VSCode + Cline 报错解决
  • 如何检测服务器中的DDOS攻击?
  • AUTOSAR从入门到精通-汽车信息安全框架(二)
  • 小米vela系统(基于开源nuttx内核)——openvela开源项目
  • 【Axure】1500+实用图标库
  • Unity 语音转文字 Vosk 离线库
  • 20.2、主流数据库安全分析与防护
  • 查看 Linux 系统的版本信息
  • JAVA实现2048小游戏(附源码)
  • python实现音频文件格式转换(mp3,m4a,wav,wma,aac...)
  • 【数据库系统概论】第5章 数据库完整性【!触发器】
  • 【掌握 JavaScript 数组迭代:map 和 includes 的使用技巧】
  • Windows 正确配置android adb调试的方法
  • 三小时深度学习PyTorch
  • 嵌入式无人机: 防止信号被有意干扰入侵策略