Tomcat的Connector启动过程分析

news2024/11/29 16:36:33

一. 前言

前面分析了tomcat的整体架构和tomcat的启动过程,在分析启动过程的时候只讲了整体的启动过程,本篇来重点分析一下tomcat的Connector(连接器)组件的启动过程。

二.从Connector的构造开始

那么org.apache.catalina.connector.Connector是在什么时候被创建出来的呢?在上一篇tomcat的启动过程中我们跟进过了Digester解析tomcat的server.xml配置文件,在org.apache.catalina.startup.Catalina#createStartDigester方法中有一个段代码是:

// 此处使用的tomcat版本为8.0
digester.addRule("Server/Service/Connector",
                         new ConnectorCreateRule());

所以当解析到server.xml配置文件中的Connector标签之后,就会创建一个ConnectorCreateRule来执行Connector的相关操作。那么这个org.apache.catalina.startup.ConnectorCreateRule其实是一个org.apache.tomcat.util.digester.Rule,这个Rule接口主要是实现了在匹配相应的XML元素嵌套模式时要采取的操作,简单说就是他是规定XML解析的元素是如何被加载处理的,Rule有一个方法org.apache.tomcat.util.digester.Rule#begin,就是用来处理XML配置文件中的标签开头部分的,也就初始化之类的。
在这里插入图片描述
默认是空实现,所以我们来看org.apache.catalina.startup.ConnectorCreateRulestart方法。

public void begin(String namespace, String name, Attributes attributes)
        throws Exception {
    Service svc = (Service)digester.peek();
    Executor ex = null;
    if ( attributes.getValue("executor")!=null ) {
        ex = svc.getExecutor(attributes.getValue("executor"));
    }
    // 从配置文件中的Connector标签中获取protocol属性的值,那么默认我们的protocol值就是HTTP/1.1
    Connector con = new Connector(attributes.getValue("protocol"));
    if (ex != null) {
        setExecutor(con, ex);
    }
    String sslImplementationName = attributes.getValue("sslImplementationName");
    if (sslImplementationName != null) {
        setSSLImplementationName(con, sslImplementationName);
    }
    digester.push(con);
}

由上面这段代码注释可以知道是通过xml的配置内容进行Connector的创建的,那么跟进到org.apache.catalina.connector.Connector#Connector(java.lang.String)

public Connector(String protocol) {
	// 从上面的跟进可以知道入参的protocol值为 HTTP/1.1
	// 这个setProtocol就是根据入参去选择对应的Protocol  
	// 那么由此可知最终是protocolHandlerClassName的值为org.apache.coyote.http11.Http11NioProtocol
	// 也就是http1.1的nio方式
    setProtocol(protocol);
    // Instantiate protocol handler
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        // 此处通过反射创建Http11NioProtocol
        p = (ProtocolHandler) clazz.getConstructor().newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {
        this.protocolHandler = p;
    }

    if (Globals.STRICT_SERVLET_COMPLIANCE) {
        uriCharset = StandardCharsets.ISO_8859_1;
    } else {
        uriCharset = StandardCharsets.UTF_8;
    }

    // Default for Connector depends on this (deprecated) system property
    if (Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"))) {
        encodedSolidusHandling = EncodedSolidusHandling.DECODE;
    }
}

通过这段代码我们跟进到org.apache.coyote.http11.Http11NioProtocol的创建,来看Http11NioProtocol的继承结构
在这里插入图片描述

public Http11NioProtocol() {
	//创建了一个NioEndpoint
    super(new NioEndpoint());
}

Http11NioProtocol在构造时创建了一个org.apache.tomcat.util.net.NioEndpoint,这个其实就是NIO的默认实现。​Endpoint​​​负责网络通信,监听一个端口,循环接收​​socket​​​请求,读取网络字节流等
在这里插入图片描述
NioEndpoint的父类org.apache.tomcat.util.net.AbstractEndpoint中有一个抽象内部类:org.apache.tomcat.util.net.AbstractEndpoint.Acceptor,而NioEndpoint中有一个org.apache.tomcat.util.net.NioEndpoint.Acceptor继承自它,并实现了他的run()方法,在这个run方法中就是在循环的调用serverSock.accept()接收请求到tomcat的链接,先有个大概了解,后续在详细分析接收请求的部分。

回到Http11NioProtocol的创建

public Http11NioProtocol() {
	//创建了一个NioEndpoint
    super(new NioEndpoint());
}

跟进到父类的构造函数可以知道,最终是把这个new出来的 NioEndpoint赋值给了org.apache.coyote.AbstractProtocol#AbstractProtocolorg.apache.coyote.AbstractProtocol#endpoint属性。在父类org.apache.coyote.http11.AbstractHttp11Protocol构造器中:

public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) {
    super(endpoint);
    setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
    setHandler(cHandler);
    getEndpoint().setHandler(cHandler);
}

创建了一个org.apache.coyote.AbstractProtocol.ConnectionHandler,然后把这个ConnectionHandler设置到前面创建的NioEndpoint中。

然后就完成了Connector的创建。在Connector的创建过程中,我们要注意几个点:

  1. Http11NioProtocol 继承自AbstractProtocolAbstractHttp11Protocol,他持有NioEndpoint
  2. NioEndpoint 继承自AbstractEndpoint,持有ConnectionHandlerNioEndpoint的内部类Acceptor是真正的接收请求的地方
  3. ConnectionHandlerNioEndpoint的一个属性,它在后续会进行NioEndpoint接收的请求处理时使用

到目前为止只是创建了ConnectorConnector也是一个Lifecycle,所以接下来就到了他的initInternal,所以我们直接来到:org.apache.catalina.connector.Connector#initInternal,不了解为啥会来到这里的同学请先看tomcat的启动过程中对Lifecycle相关的内容。

三. Connector的init

Connector实现了Lifecycle,所以init肯定是在org.apache.catalina.connector.Connector#initInternal

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // Initialize adapter
    // 初始化一个CoyoteAdapter  CoyoteAdapter是Adapter接口的实现
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);

    // Make sure parseBodyMethodsSet has a default
    if (null == parseBodyMethodsSet) {
        setParseBodyMethods(getParseBodyMethods());
    }

    if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
        throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",
                getProtocolHandlerClassName()));
    }
    if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
        throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",
                getProtocolHandlerClassName()));
    }
    if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
            protocolHandler instanceof AbstractHttp11JsseProtocol) {
        AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
                (AbstractHttp11JsseProtocol<?>) protocolHandler;
        if (jsseProtocolHandler.isSSLEnabled() &&
                jsseProtocolHandler.getSslImplementationName() == null) {
            // OpenSSL is compatible with the JSSE configuration, so use it if APR is available
            jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
        }
    }

    try {
    	// protocolHandler就是Http11NioProtocol
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}

跟进到protocolHandler.init();执行里面去,由于Http11NioProtocol集成了AbstractHttp11Protocol,这个init()方法就在AbstractHttp11Protocol中:

public void init() throws Exception {
    // Upgrade protocols have to be configured first since the endpoint
    // init (triggered via super.init() below) uses this list to configure
    // the list of ALPN protocols to advertise
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
        configureUpgradeProtocol(upgradeProtocol);
    }

	// 又调用了父类的init,也就是AbstractProtocol的init方法
    super.init();

    // Set the Http11Protocol (i.e. this) for any upgrade protocols once
    // this has completed initialisation as the upgrade protocols may expect this
    // to be initialised when the call is made
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
        if (upgradeProtocol instanceof Http2Protocol) {
            ((Http2Protocol) upgradeProtocol).setHttp11Protocol(this);
        }
    }
}

跟进到org.apache.coyote.AbstractProtocol#init

public void init() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
    }

    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if (oname != null) {
            Registry.getRegistry(null, null).registerComponent(this, oname, null);
        }
    }

    if (this.domain != null) {
        ObjectName rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
        this.rgOname = rgOname;
        Registry.getRegistry(null, null).registerComponent(
                getHandler().getGlobal(), rgOname, null);
    }

    String endpointName = getName();
    // 这里的endpoint就是NioEndpoint  此处给NioEndpoint设置了名字:http-nio-8080  和  domain:Catalina
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);
	// 然后继续执行自组件的init  也就是NioEndpoint的init
    endpoint.init();
}

跟进这个endpoint.init();最终会来到:org.apache.tomcat.util.net.AbstractEndpoint#init,这个方法中我们重点关注其中的bind();方法,其他的代码这里就不贴出来了。
bind()方法是个抽象方法,留给子类实现了,所以来到:org.apache.tomcat.util.net.NioEndpoint#bind

public void bind() throws Exception {
    /*默认执行*/
    if (!getUseInheritedChannel()) {
    	// 开启 ServerSocketChannel
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        // addr是:0.0.0.0/0.0.0.0:8080   其实就是绑定8080端口
        // getAcceptCount()获取的是acceptCount 默认值为100 表示的是socket上请求的最大挂起连接数
        serverSock.socket().bind(addr,getAcceptCount());
    } else {
        /*从系统获取*/
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    // 设置 ServerSocketChannel 为阻塞模式
    serverSock.configureBlocking(true); //mimic APR behavior

    // Initialize thread count defaults for acceptor, poller
    //  设置了Poller和Acceptor的线程数量 在start中根据数量启动相对应数量的线程
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
        acceptorThreadCount = 1;
    }
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    // 设置
    setStopLatch(new CountDownLatch(pollerThreadCount));

    // Initialize SSL if needed
    initialiseSsl();

	// 打开NioSelectorPool
    selectorPool.open();
}

然后Connector的init大致就完成了。
在这个过程中主要是有:

  1. 创建了CoyoteAdapter
  2. 执行Http11NioProtocol的init,在执行endpoint的init
  3. NioEndpoint中开启了ServerSocketChannel并绑定端口
  4. 打开了一个NioSelectorPool

四. Connector的start

大致的了解了的一遍Connectorinit之后,再来看Connectorstart,从org.apache.catalina.connector.Connector#startInternal开始

protected void startInternal() throws LifecycleException {

    // Validate settings before starting
    if (getPort() < 0) {
        throw new LifecycleException(sm.getString(
                "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
    }

    setState(LifecycleState.STARTING);

    try {
    	// Http11NioProtocol的start
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}

根据代码跟进到:org.apache.coyote.AbstractProtocol#start

public void start() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
    }
	// NioEndpoint的start
    endpoint.start();

    // Start timeout thread
    // 启动超时线程
    asyncTimeout = new AsyncTimeout();
    Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
    int priority = endpoint.getThreadPriority();
    if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
        priority = Thread.NORM_PRIORITY;
    }
    timeoutThread.setPriority(priority);
    timeoutThread.setDaemon(true);
    timeoutThread.start();
}

跟进endpoint.start();会先来到org.apache.tomcat.util.net.AbstractEndpoint#start

public final void start() throws Exception {
	// 这个bindState在org.apache.tomcat.util.net.AbstractEndpoint#init中已经设置为BOUND_ON_INIT了  所以这里不会再进入这个if
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    // 跟进这个
    startInternal();
}

跟进startInternal()org.apache.tomcat.util.net.NioEndpoint#startInternal

public void startInternal() throws Exception {
    if (!running) {
        running = true;
        paused = false;

        processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getProcessorCache());
        eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getEventCache());
        nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getBufferPool());

        // Create worker collection
        /*创建工作线程池*/
        if (getExecutor() == null) {
        	// tomcat自定义了线程池实现,并自定义了线程池队列  这个后续再分析
            createExecutor();
        }
         //限流控制  tomcat自定义了一个LimitLatch用来实现限流控制
        initializeConnectionLatch();

        // Start poller threads
        // 启动Poller线程 
        pollers = new Poller[getPollerThreadCount()];
        for (int i=0; i<pollers.length; i++) {
            pollers[i] = new Poller();
            Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
        }

		// 启动Acceptor线程
        startAcceptorThreads();
    }
}

org.apache.tomcat.util.net.NioEndpoint#bind中设置了PollerAcceptor的数量

那么至此start也就大致完成了,在这个过程中几个重点:

  1. 创建了工作线程池org.apache.tomcat.util.threads.ThreadPoolExecutor
  2. 创建了限流LimitLatch
  3. 启动了Poller线程
  4. 启动了Acceptor线程

到此位置,Connector的启动过程就大致分析完成了。在过程中涉及到了Http11NioProtocolNioEndpointConnectionHandlerCoyoteAdapterAcceptorPollerLimitLatchorg.apache.tomcat.util.threads.ThreadPoolExecutor这些重要的类。这些都在后续的文章中详细分析吧,由于篇幅问题,本篇就先了解一个过程,算是抛砖引玉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/142328.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文献学习06_利用句法指示符和句子上下文加强关系抽取

论文信息 Subjects: Computation and Language (cs.CL) &#xff08;1&#xff09;题目&#xff1a;Enhancing Relation Extraction Using Syntactic Indicators and Sentential Contexts &#xff08;利用句法指示符和句子上下文加强关系抽取&#xff09; &#xff08;2&…

论文精读:RPM-Net: Robust Point Matching using Learned Features

论文地址:https://arxiv.org/pdf/2003.13479.pdf 点云配准任务 点云配准可以当做一个基础的上游任务,根据从不同视角下获取的点云数据配准为完整的点云数据,下游任务众多 基本任务:求一个变换矩阵,使得两个具有未知点的点云数据重合。 刚性与非刚性: 刚性配准:旋转和平…

Leetcode 121买卖股票的最佳时机

题目描述&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔…

solr集群配置(使用solr自带的Jetty实现集群配置)

看了很多的资料发现基本集群搭建都是通过tomcat的方式实现的&#xff0c;但是在高版本的solr中&#xff0c;可以通过solr自带的jetty实现集群的搭建 准备 1.虚拟机安装linux 2.安装jdk 3.下载solr并解压 步骤 1.进入到解压后solr的bin目录下&#xff0c;并执行 ./solr -e clo…

赛狐ERP | 如何高效管理亚马逊广告!用这款亚马逊ERP就够了!

亚马逊的广告管理是是每一位亚马逊运营的必修课&#xff0c;除自然流量外&#xff0c;广告来带的流量与转化占比都极高&#xff0c;广告做活了&#xff0c;就是打虎上山&#xff1b;广告搞砸了&#xff0c;就是骑虎难下&#xff1a;不开广告吧没有流量卖不动、开了广告吧财务账…

#B. 部落联盟

一,题目Description在草原上有N个部落&#xff0c;每个部落都有其坐标(xi,yi)每个部落都有个武力值&#xff0c;可正可负由于部落间只能通过马匹来传递信息于是只有当两个部落间的距离为1的时候&#xff0c;两个部落才有可能进行联系&#xff0c;距离计算公式为abs(xi-xj)abs(y…

人生的喜悦、不快与成长,都在那一篇篇的文字中得到记录 | 2022 年终总结

又是一年的总结&#xff0c;不知道自己今年又该写点什么。但提笔总是好的&#xff0c;也算对今年的一个交代和对未来的一份期许。窗外的阳光正好&#xff0c;对面楼的敲打声叮叮咚咚&#xff0c;窗台上的两只猫睡得依旧奔放和舒适。这样一个看似美好的下午&#xff0c;一个平凡…

Internet Download Manager2023最好用的HTTP下载神器

Internet Download Manager 介绍2023最佳下载利器。Internet Download Manager (简称IDM) 是一款Windows 平台功能强大的多线程下载工具&#xff0c;国外非常受欢迎。支持断点续传&#xff0c;支持嗅探视频音频&#xff0c;接管所有浏览器&#xff0c;具有站点抓取、批量下载队…

机器学习100天(二十六):026 k近邻分类算法-理论

机器学习100天,今天讲的是:K 近邻分类算法-理论。 《机器学习100天》完整目录:目录 一、什么是 K 近邻算法 K 近邻算法也叫 KNN(k-Nearest Neighbor)算法,它是一个比较成熟也是最简单的机器学习算法之一。K 近邻分类算法的思路是:如果一个样本在特征空间中与 K 个实例最…

FastAPI集成Socket.io坑点汇集和技术选型

背景 单纯的 websocket 通信方式存在大量的辅助性的工作需要处理&#xff0c;例如心跳机制、粘包处理、协议规范等&#xff0c;所以直接使用 websocket 开发&#xff0c;等于重复造轮子&#xff0c;毫无价值&#xff0c;而 socket.io 整理了一整套规范和机制&#xff0c;可以满…

DSP-频域中的离散时间信号

目录 连续和离散时间傅里叶变换: 四种常用的傅立叶变换: 连续时间傅立叶变换(FT): 离散时间傅里叶变换(DTFT): 对称关系: DTFT的收敛条件: 常用DTFT对&#xff1a; DTFT的性质&#xff1a; 线性&#xff1a; 时间反转&#xff1a; 时移&#xff1a; 频移&#xff1a…

鲁大师2022牛角尖颁奖盛典落幕,各大硬件厂商齐聚襄阳

1月4日&#xff0c;鲁大师2022年度牛角尖颁奖晚会在湖北襄阳成功举办。 鲁大师的”牛角尖”奖是由过去一年上亿用户通过鲁大师测试得到的真实数据&#xff0c;以及鲁大师实验室通过专业的测试规范共同缔造的硬件奖项。颁发给的都是各大PC、手机、电动车领域最优秀、最顶尖的产…

【自学Python】Python HelloWorld

Windows Python HelloWorld Windows Python HelloWorld 教程 使用记事本&#xff0c;新建一个 helloworld.py 文件&#xff0c;输入以下内容&#xff1a; print(嗨客网(www.haicoder.net))打开命令行&#xff0c;进入到 helloworld.py 文件所在目录&#xff0c;输入以下命令…

干货 | 移动端App自动化之App控件定位

客户端的页面通过 XML 来实现 UI 的布局&#xff0c;页面的 UI 布局作为一个树形结构&#xff0c;而树叶被定义为节点。这里的节点也就对应了我们要定位的元素&#xff0c;节点的上级节点&#xff0c;定义了元素的布局结构。在 XML 布局中可以使用 XPath 进行节点的定位。App的…

Spring项目

1.创建一个Java项目 名字为Test_SM_1 2.导包 Java项目&#xff0c;用快捷方式的方法导入 web项目用复制的方式 一共8个包&#xff0c;前两个是通用jar包&#xff0c;Spring相关的jar包有4个&#xff0c;mybatis的jar包有1个&#xff0c;mysql的jar包连接数据库 3. 复原一个MyB…

Rabbitmq消息队列详解(一)——基础介绍

安装rabbitmq 本机&#xff1a; http://erlang.org.download/otp_win64_21.3.exe 一直next https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/:rabbitmq-server-3.7.14.exe 全选&#xff0c;next 进入RabbitMQ安装目录下的sbin目录 rabbitmq-plugins enable …

实操!用Zabbix+500元硬件如何平替5万元动环检测系统,实现UPS温湿度烟雾等数据采集存储、告警、大屏展示?

感谢本文作者 王志杰 。诚邀社区伙伴积极投稿&#xff01; 前提 ►架构展示&#xff1a; ►大屏展示&#xff1a; ►所需软件&#xff1a; Zabbix Grafana 安装Zabbix-agent2的采集服务器 MThings串口调试软件 厂家自带UPS监控软件 USR-VCOM有人虚拟串口软件 ►所需硬件&a…

maven第二篇:IDE创建maven项目

前面将maven安装好了&#xff0c;当然起不会直接用&#xff0c;而是结合着一起用&#xff0c;那么现在就用IDE创建一个maven&#xff0c;来进行演示。 前提 IDE默认是带有maven软件&#xff0c;但是一般不习惯用起自带的&#xff0c;所以前提就是安装一个maven软件&#xff0…

HUAWEI CLOUD Stack 私有云解决方案(HCS)

HUAWEI CLOUD Stack 私有云解决&#xff08;HCS&#xff09; 注明&#xff1a; 本文介绍的版本为HUAWEI CLOUD Stack 6.5&#xff08;也叫华为云Stack或HCS&#xff09;&#xff0c; 在华为6.5版本以前的名字是FusionCloud 6.3&#xff0c; 最近更新的8.0版本不会差别太大&…

Java开发学习(三十九)----SpringBoot整合mybatis

一、回顾Spring整合Mybatis Spring 整合 Mybatis 需要定义很多配置类 SpringConfig 配置类 导入 JdbcConfig 配置类 导入 MybatisConfig 配置类 Configuration ComponentScan("com.itheima") PropertySource("classpath:jdbc.properties") Import({JdbcC…