tomcat-container 源码分析

news2024/9/30 1:32:07

说明

  1. 本文基于 tomcat 8.5.x 编写。
  2. @author blog.jellyfishmix.com / JellyfishMIX - github
  3. LICENSE GPL-2.0

tomcat 的 container 容器

  1. tomcat 由 connector 和 container 两部分组成,connector 接收到请求后,先将请求包装为 request,然后传递给 container 进行处理,最终返回给请求方。
  2. tomcat-container 按照包含关系一共有 4 个容器: StandardEngine, StandardHost, StandardContext, StandardWrapper。
  3. wrapper 表示一个 servlet,context 表示一个 web 应用程序,一个 web 应用程序中可能有多个 servlet。host 表示一个虚拟主机,一个虚拟主机里可能运行着多个 web 应用程序。engine 控制一个虚拟主机的生命周期。

img

tomcat 的 pipeline 管道和 valve 阀门

  1. container 模块中,4 个 container 间不会直接调用对方来传递请求,请求在 4 个 container 之间传递依靠 pipeline 管道,调用 pipeline 传递请求。
  2. 4 种容器都有自己的 pipeline 组件,每个 pipeline 组件上可以有多个 valve(阀门),且至少会设定 1 个 baseValve(基础阀门)。基础阀门的作用是连接下一个 container,baseValve 是两个 container 之间的桥梁。pipeline-valve 应用了设计模式–责任链模式。
  3. pipeline 定义对应的接口是 Pipeline, 具体实现是 StandardPipeline。valve 定义对应的接口是 Valve,抽象类是 ValveBase。4 个容器对应基础阀门分别是 StandardEngineValve, StandardHostValve, StandardContextValve, StandardWrapperValve。

Pipeline 接口

org.apache.catalina.Pipeline

具体实现是 StandardPipeline

  1. Pipeline 中很多的方法用于操作 Valve,包括获取,设置,移除 Valve。
  2. getFirst 方法返回的是 Pipeline 上的第一个 Valve,addValve 方法可以添加一个阀门。getBasic/setBasic 方法用于获取/设置基础阀。basicValve 位于 pipeline 最后一个阀门位置。
public interface Pipeline {
    public Valve getBasic();

    public void setBasic(Valve valve);

    public void addValve(Valve valve);

    public Valve[] getValves();

    public void removeValve(Valve valve);

    public Valve getFirst();

    public boolean isAsyncSupported();

    public Container getContainer();

    public void setContainer(Container container);
}

StandardPipeline#startInternal 方法

org.apache.catalina.core.StandardPipeline#startInternal

启动此 pipeline

  1. 设置当前 valve,如果 first 引用指向了 valve,则 first 指向的 valve 为当前 valve。否则 basicValve 为当前 valve。
  2. 遍历此 pipeline 的 valve 链表,调用链上每个 valve 的 start 方法。
  3. 将 pipeline 状态置为 STARTING
    @Override
    protected synchronized void startInternal() throws LifecycleException {
        // Start the Valves in our pipeline (including the basic), if any
        // 设置当前 valve,如果 first 引用指向了 valve,则 first 指向的 valve 为当前 valve。否则 basicValve 为当前 valve。
        Valve current = first;
        if (current == null) {
            current = basic;
        }
        // 遍历此 pipeline 的 valve 链表,调用链上每个 valve 的 start 方法
        while (current != null) {
            if (current instanceof Lifecycle) {
                ((Lifecycle) current).start();
            }
            current = current.getNext();
        }
        // 将 pipeline 状态置为 STARTING
        setState(LifecycleState.STARTING);
    }

StandardPipeline#setBasic 方法

org.apache.catalina.core.StandardPipeline#setBasic

为此 pipeline 设置基础阀门 basicValve

  1. 如果已经有 basicValve 并且和要设置的值一样,那么直接 return 结束设置。
  2. 如果旧的 basicValve 非空,则调用其 stop 方法,取消和对应 container 的关联。
  3. 启动新的 basicValve 并和 container 进行关联。
  4. 遍历阀门链表,将新的 basicValve 替换至阀门链表。
    @Override
    public void setBasic(Valve valve) {

        // Change components if necessary
        // 如果已经有 basicValve 并且和要设置的值一样,那么直接 return 结束设置
        Valve oldBasic = this.basic;
        if (oldBasic == valve) {
            return;
        }

        // Stop the old component if necessary
        // 如果旧的 basicValve 非空,则调用其 stop 方法,取消和对应 container 的关联
        if (oldBasic != null) {
            if (getState().isAvailable() && (oldBasic instanceof Lifecycle)) {
                try {
                    ((Lifecycle) oldBasic).stop();
                } catch (LifecycleException e) {
                    log.error(sm.getString("standardPipeline.basic.stop"), e);
                }
            }
            if (oldBasic instanceof Contained) {
                try {
                    ((Contained) oldBasic).setContainer(null);
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                }
            }
        }

        // Start the new component if necessary
        if (valve == null) {
            return;
        }
        // 新的 basicValve 和 container 进行关联
        if (valve instanceof Contained) {
            ((Contained) valve).setContainer(this.container);
        }
        // 启动新的 basicValve
        if (getState().isAvailable() && valve instanceof Lifecycle) {
            try {
                ((Lifecycle) valve).start();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardPipeline.basic.start"), e);
                return;
            }
        }

        // Update the pipeline
        // 遍历阀门链表,将新的 basicValve 替换至阀门链表
        Valve current = first;
        while (current != null) {
            if (current.getNext() == oldBasic) {
                current.setNext(valve);
                break;
            }
            current = current.getNext();
        }

        this.basic = valve;

    }

StandardPipeline#addValve 方法

org.apache.catalina.core.StandardPipeline#addValve

为此 pipeline 添加 valve

  1. 启动 valve 并和 container 进行关联。
  2. first 指向阀门链表第一个元素,从 first 开始遍历阀门链表,把 valve 加在现有 valve 链表末尾,basicValve 之前。
  3. 触发 container 添加 valve 事件。
	public void addValve(Valve valve) {

        // Validate that we can add this Valve
        // valve 和 container 进行关联
        if (valve instanceof Contained) {
            ((Contained) valve).setContainer(this.container);
        }

        // Start the new component if necessary
        // 启动 valve
        if (getState().isAvailable()) {
            if (valve instanceof Lifecycle) {
                try {
                    ((Lifecycle) valve).start();
                } catch (LifecycleException e) {
                    log.error(sm.getString("standardPipeline.valve.start"), e);
                }
            }
        }

        // Add this Valve to the set associated with this Pipeline
        // first 指向阀门链表第一个元素,从 first 开始遍历阀门链表,把 valve 加在现有 valve 链表末尾,basicValve 之前
        if (first == null) {
            first = valve;
            valve.setNext(basic);
        } else {
            Valve current = first;
            while (current != null) {
                if (current.getNext() == basic) {
                    current.setNext(valve);
                    valve.setNext(basic);
                    break;
                }
                current = current.getNext();
            }
        }
        // 触发 container 添加 valve 事件
        container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
    }

StandardPipeline#getValves 方法

org.apache.catalina.core.StandardPipeline#getValves

获取此 pipeline 所有的 valve,数组形式返回。

  1. 遍历链表把 valve 添加进集合内,最后集合转数组。
    /**
     * 获取此 pipeline 所有的 valve,数组形式返回。
     */
    @Override
    public Valve[] getValves() {
        // 遍历链表把 valve 添加进集合内,最后集合转数组
        List<Valve> valveList = new ArrayList<>();
        Valve current = first;
        if (current == null) {
            current = basic;
        }
        while (current != null) {
            valveList.add(current);
            current = current.getNext();
        }

        return valveList.toArray(new Valve[0]);
    }

StandardPipeline#removeValve 方法

org.apache.catalina.core.StandardPipeline#removeValve

移除指定的 valve

  1. 遍历阀门链表,定位目标 valve 并移除。
  2. first 是严格定义的除了 basicValve 的第一个阀门,不包括 basicValve。
  3. 取消被移除的 valve 与 container 的关联。
  4. 调用被移除 valve 的 stop, destroy 方法。
  5. 触发 container 移除 valve 事件。
    /**
     * Remove the specified Valve from the pipeline associated with this
     * Container, if it is found; otherwise, do nothing.  If the Valve is
     * found and removed, the Valve's <code>setContainer(null)</code> method
     * will be called if it implements <code>Contained</code>.
     *
     * 移除指定的 valve
     *
     * @param valve Valve to be removed
     */
    @Override
    public void removeValve(Valve valve) {

        Valve current;
        if(first == valve) {
            first = first.getNext();
            current = null;
        } else {
            current = first;
        }
        // 遍历阀门链表,定位目标 valve 并移除
        while (current != null) {
            if (current.getNext() == valve) {
                current.setNext(valve.getNext());
                break;
            }
            current = current.getNext();
        }
        // first 是严格定义的除了 basicValve 的第一个阀门,不包括 basicValve
        if (first == basic) {
            first = null;
        }

        // 取消被移除的 valve 与 container 的关联
        if (valve instanceof Contained) {
            ((Contained) valve).setContainer(null);
        }

        // 调用被移除 valve 的 stop, destroy 方法
        if (valve instanceof Lifecycle) {
            // Stop this valve if necessary
            if (getState().isAvailable()) {
                try {
                    ((Lifecycle) valve).stop();
                } catch (LifecycleException e) {
                    log.error(sm.getString("standardPipeline.valve.stop"), e);
                }
            }
            try {
                ((Lifecycle) valve).destroy();
            } catch (LifecycleException e) {
                log.error(sm.getString("standardPipeline.valve.destroy"), e);
            }
        }
        // 触发 container 移除 valve 事件。
        container.fireContainerEvent(Container.REMOVE_VALVE_EVENT, valve);
    }

StandardPipeline#getFirst 方法

org.apache.catalina.core.StandardPipeline#getFirst

获取此 pipeline 第一个 valve,如果没有普通 valve 则返回 basicValve

/**
 * 获取此 pipeline 第一个 valve,如果没有普通 valve 则返回 basicValve
 */
@Override
public Valve getFirst() {
    if (first != null) {
        return first;
    }

    return basic;
}

Valve 接口

org.apache.catalina.Valve

一个 pipeline 可以有多个 valve,这些 valve 链式存储,上一个 valve 对象持有下一个 valve 对象的引用。调用 getNext() 方法即可获取下个 valve 实例。setNext 方法设置下一个 valve 实例。

public interface Valve {
    public String getInfo();

    public Valve getNext();

    public void setNext(Valve valve);

    public void backgroundProcess();

    public void invoke(Request request, Response response) throws IOException, ServletException;

    public void event(Request request, Response response, CometEvent event) throws IOException,ServletException;
    
    public boolean isAsyncSupported();
}

StandardEngineValve#invoke 方法

org.apache.catalina.core.StandardEngineValve#invoke

边界条件处理,然后调用 hostPipeline 的 valve 链表。

    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            // HTTP 0.9 or HTTP 1.0 request without a host when no default host
            // is defined.
            // Don't overwrite an existing error
            if (!response.isError()) {
                response.sendError(404);
            }
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);
    }

StandardHostValve#invoke 方法

org.apache.catalina.core.StandardHostValve#invoke

边界条件处理,然后调用 contextPipeline 的 valve 链表。

public final class StandardHostValve extends ValveBase {
    protected Container container = null;
    protected Valve next = null;
    
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        Context context = request.getContext();
        if (context == null) {
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(context.getPipeline()
                    .isAsyncSupported());
        }
        boolean asyncAtStart = request.isAsync();
        try {
            context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
            if (!asyncAtStart && 
                    !context.fireRequestInitEvent(request.getRequest())) {
                return;
            }
            try {
                if (!response.isErrorReportRequired()) {
                    // 继续执行管道连接的下个Container方法
                    context.getPipeline().getFirst()
                            .invoke(request, response);
                }
            } catch (Throwable t) {
                ...
            }
            ...
        } finally {
            if (ACCESS_SESSION) {
                request.getSession(false);
            }
            context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
        }
    }
}

StandardContextValve#invoke 方法

org.apache.catalina.core.StandardContextValve#invoke

禁止访问 WEB-INF 或者 META-INF 路径下的资源,ack 确认请求。然后调用 wrapperPipeline 的 valve 链表。

    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Disallow any direct access to resources under WEB-INF or META-INF
        // 禁止访问 WEB-INF 或者 META-INF 路径下的资源
        MessageBytes requestPathMB = request.getRequestPathMB();
        if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/META-INF"))
                || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // Select the Wrapper to be used for this Request
        Wrapper wrapper = request.getWrapper();
        if (wrapper == null || wrapper.isUnavailable()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // Acknowledge the request
        // ack 确认请求
        try {
            response.sendAcknowledgement(ContinueResponseTiming.IMMEDIATELY);
        } catch (IOException ioe) {
            container.getLogger().error(sm.getString(
                    "standardContextValve.acknowledgeException"), ioe);
            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }

        if (request.isAsyncSupported()) {
            request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
        }
        // 调用 wrapperPipeline 的 valve 链表
        wrapper.getPipeline().getFirst().invoke(request, response);
    }

StandardWrapperValve#invoke 方法

org.apache.catalina.core.StandardWrapperValve#invoke

为请求分配 servlet,

  1. 初始化一些局部变量,检查当前应用的状态是否可用等前置校验。
  2. 调用 servlet 的 allocate 方法,为请求分配一个 servlet 实例。
  3. 为此请求创建 ApplicationFilterChain 并调用 doFilter 方法,在 filterChain 中会调用 servlet 的 service 方法处理请求。
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
		// Initialize local variables we may need
        // 初始化一些局部变量
        boolean unavailable = false;
        Throwable throwable = null;
        // This should be a Request attribute...
        long t1=System.currentTimeMillis();
        requestCount.incrementAndGet();
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        Context context = (Context) wrapper.getParent();

        // Check for the application being marked unavailable
        // 检查当前应用的状态是否可用
        if (!context.getState().isAvailable()) {
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                           sm.getString("standardContext.isUnavailable"));
            unavailable = true;
        }

		// 调用 servlet 的 allocate 方法,为请求分配一个 servlet 实例
        try {
            if (!unavailable) {
                servlet = wrapper.allocate();
            }
        } catch

		// Create the filter chain for this request
        // 为此请求创建 ApplicationFilterChain
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        // 为此请求调用 ApplicationFilterChain,在 filterChain 中会调用 servlet 的 service 方法处理请求
        Container container = this.container;
        try {
            if ((servlet != null) && (filterChain != null)) {
                // Swallow output if needed
                if (context.getSwallowOutput()) {
                    // ...
                } else {
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        // 核心方法
                        filterChain.doFilter
                            (request.getRequest(), response.getResponse());
                    }
                }
            }
        } catch () {
            // ...
        } finally {
            // ...
        }
}

FilterChain 接口和 Filter 接口

  1. FilterChain 接口和 Filter 接口应用了责任链设计模式,FilterChain 持有各 filter 的引用并排了序号,提供依次对各 filter 的调用能力。
  2. 由 filterChain 负责从第一个 filter 开始,按序号调用各 filter 的 doFilter 方法。当前 filter 的 doFilter 方法执行完毕后,触发 filterChain 对下一个 filter 调用。所有 filter 调用完毕后,filterChain 负责调用 servlet 的 service 方法处理请求。
public interface FilterChain {

    /**
     * Causes the next filter in the chain to be invoked, or if the calling
     * filter is the last filter in the chain, causes the resource at the end of
     * the chain to be invoked.
     *
     * @param request
     *            the request to pass along the chain.
     * @param response
     *            the response to pass along the chain.
     *
     * @throws IOException if an I/O error occurs during the processing of the
     *                     request
     * @throws ServletException if the processing fails for any other reason
     */
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException;

}

Filter 接口,提供对请求的过滤能力。声明了初始化方法 init 和销毁方法 destroy,核心是 doFilter 方法,子类实现后写过滤逻辑。

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    public void destroy();
}

ApplicationFilterChain#doFilter 方法

filterChain 调用 filter 的封装方法,有一层校验逻辑的封装。直接看 internalDoFilter 方法,最终调用了 filter。

public final class ApplicationFilterChain implements FilterChain {
	@Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        // ...
        internalDoFilter(request,response);
}

ApplicationFilterChain#internalDoFilter 方法

filterChain 调用 filter 的具体方法。

  1. n 是此 filterChain 上 filter 的数量,pos 是当前执行到的 filter 的序号。
  2. 没执行完所有 filter,则继续调用下一个 filter 的 doFilter 方法处理请求。
  3. 执行完所有 filter,到达 filterChain 的末尾,调用 servlet 实例的 service 方法处理请求。
private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
        // Call the next filter if there is one
        // n 是此 filterChain 上 filter 的数量,pos 是当前执行到的 filter 的序号
        // 没执行完所有 filter,则继续调用下一个 filter 的 doFilter 方法处理请求
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                // ...
                filter.doFilter(request, response, this);
            } catch (IOException | ServletException | RuntimeException e) {
                // ...
            } catch (Throwable e) {
                // ...
            }
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        // 执行完所有 filter,到达 filterChain 的末尾,调用 servlet 实例的 service 方法处理请求
        try {
            // ...
            servlet.service(request, response);
        } catch (IOException | ServletException | RuntimeException e) {
            // ...
        } catch (Throwable e) {
            // ...
        } finally {
            // ...
        }
    }

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

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

相关文章

六、H5新特性

文章目录一、H5的兼容二、H5新增特性2.1 语义化标签2.2 增强表单2.3 音频、视频一、H5的兼容 支持 HTML5的浏览器包括Firefox(火狐浏览器)&#xff0c;IE9及其更高版本&#xff0c;Chrome(谷歌浏览器)&#xff0c;Safari,Opera等&#xff0c;国内的遨游浏览器&#xff0c;以及…

【Kubernetes 企业项目实战】07、最新一代微服务网格 Istio 入门到企业实战(上)

目录 一、Istio 介绍 1.1 什么是 service mesh 1.2 什么是 Istio 1.2.1 服务注册和发现 1.2.2 服务度量 1.2.3 灰度发布 1.3 Istio 核心特性 1.3.1 断路器 1.3.2 超时 1.3.3 重试 1.3.4 多路由规则 二、架构和原理 2.1 Istio 架构 2.1.1 数据平面由一组以 Sideca…

【Spring】手动实现简易AOP和IOC

前言 XML&#xff1a;通过Dom4j对XML进行解析和验证。 IOC&#xff1a;通过获取要创建对象的Class类型、构造函数后&#xff0c;通过反射来实现。 AOP&#xff1a;通过使用JDK动态代理和Cglib动态代理实现。 一、解析XML 1.1、解析bean标签 /*** 解析bean标签* param xmlBean…

前端面试当中CDN会问啥------CDN详细教程来啦

⼀、CDN 1. CDN的概念 CDN&#xff08;Content Delivery Network&#xff0c;内容分发⽹络&#xff09;是指⼀种通过互联⽹互相连接的电脑⽹络系统&#xff0c;利 ⽤最靠近每位⽤户的服务器&#xff0c;更快、更可靠地将⾳乐、图⽚、视频、应⽤程序及其他⽂件发送给⽤户&…

代谢组学资讯,全球爆火的ChatGPT,是如何看待三阴性乳腺癌的?

领导说 今天下午6点前必须发出一篇推文 我表面毫无波澜实则内心风起云涌 那么问题来了 我如何才能在下班前发送推文准时下班呢 我要怎么写才能获得趣粉们的认可呢 全球爆火的ChatGPT&#xff0c;让我的格局一下打开~&#xff0c;它能不能成为我的“得力助手”&#xff1f;…

跳空缺口指标公式,主图显示向上向下跳空缺口

跳空缺口包含两种类型&#xff0c;向上跳空缺口和向下跳空缺口。向上跳空缺口是指当天最低价高于昨天的最高价&#xff0c;K线图出现缺口。向下跳空缺口是指当天最高价低于昨天的最低价&#xff0c;K线图出现缺口。 注意一下&#xff0c;上面的缺口定义与百科上有区别&#xf…

授权验证方式有很多、但AOP最为优雅。

前言 有时候项目中需要对接口进行校验&#xff0c;增加鉴权&#xff0c;确保 API 不被恶意调用。 项目中都是这样 这样&#xff0c;部分需要查询一些信息&#xff0c;下文需要使用 这样的代码很多&#xff0c;重复率太高。看着我蛋疼&#xff0c;对此优化一下。 方案 1 …

剑指offer 7 数组中和为0的三个数

此问题属于nsum问题&#xff0c;题目链接&#xff1a;力扣 要求在数组中找到不重复的三元组&#xff0c;三个数加起来为0&#xff0c;且每个下标只能用一次。而且需要返回所有这样的不重复数组。 1. 排序 双指针 1. 「不重复」的本质是什么&#xff1f;我们保持三重循环的大…

SpringBoot——日志文件

基本概念 日志文件记录了程序的报错信息&#xff0c;执行时间&#xff0c;用户的登录状态&#xff0c;操作时间等等 通过日志&#xff0c;我们可以轻松的找到程序的问题&#xff0c;得到程序的相关信息 springBoot启动时控制台打印的这些&#xff0c;就是程序的日志 创建日志…

Kafka报错:Controller 219 epoch 110 failed to change state for partition

集群里面kafka报错&#xff1a;Controller 219 epoch 110 failed to change state for partition maxwell_atlas-0 from OfflinePartition to OnlinePartitionkafka.common.stateChangeFailedException: Failed to elect leader for partition maxwell_atlas-0 under strategy …

SpringWeb

SpringWeb 概述 springWeb 是 spring 框架的一个模块&#xff0c;springWeb 和 spring 无需通过中间整 合层进行整合。 springWeb 是一个基于 mvc 的 web 框架,方便前后端数据的传输. SpringWeb 拥有控制器&#xff0c;接收外部请求&#xff0c;解析参数传给服务层. SpringM…

盘点界面组件DevExtreme 2023年值得期待的一些新功能!

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序&#xff0c;该套件附带功能齐…

一文学会进程控制

目录进程的诞生fork函数fork的本质fork的常规用法fork调用失败的原因进程的死亡进程退出的场景常见的进程退出方法正常终止&#xff08;代码跑完&#xff09;echo $?main函数返回调用exit调用_exitexit和_exit的区别进程等待进程等待的重要性进程等待的函数waitwaitpid进程退出…

uniapp中条件编译

官方&#xff1a;https://uniapp.dcloud.net.cn/tutorial/platform.html#%E8%B7%A8%E7%AB%AF%E5%85%BC%E5%AE%B9 #ifndef H5 代码段… #endif 表示除了H5其他都可以编译 #ifdef H5 代码段… #endef 表示只能编译H5&#xff0c;其他的都不能编译 其他编译平台请查看官方文档。 …

连接器产业深度分析报告,国产化替代如何突出重围?(附厂商名录)

前言 2022年3-4月&#xff0c;上海疫情的封城举措&#xff0c;使得其它地区连接器类产品难以进入上海产业链&#xff0c;车载连接器的终端供应受阻&#xff0c;最终影响到全国多家车企生产&#xff1b; 同年12月&#xff0c;欧洲理事会批准—2024年12月28日之前&#xff0c;各类…

MySQL数据库调优————索引调优技巧

长字段的索引调优 当某张表需要给一个长字段创建索引时&#xff0c;因为索引长度越长&#xff0c;效率越差&#xff0c;所以我们需要对其进行优化。 创建额外的长字段的Hash值列 当长字段需要创建索引时&#xff0c;我们可以为其创建额外的一列&#xff0c;用其Hash值作为值…

如何利用Power Virtual Agents机器人实现成绩查询服务

今天我们继续介绍如何利用Power Virtual Agents来实现成绩查询服务。设计思路是在PVA聊天机器人的对话框中输入学生的姓名和学号来进行成绩的查询。首先&#xff0c;在Microsoft 365的OneDrive中制作一个Excel格式的成绩单。 可以将学生的学号、姓名、各学科成绩进行添加。 在P…

【初探人工智能】2、雏形开始长成

【初探人工智能】2、雏形开始长成【初探人工智能】2、雏形开始长成安装Flask封装Web接口雏形设置接收参数功能验证聊天写代码代码补全生成图片写在后面笔者初次接触人工智能领域&#xff0c;文章中错误的地方还望各位大佬指正&#xff01; 【初探人工智能】2、雏形开始长成 在…

限时活动|凭徽章领披萨大奖,玩转Moonbeam治理论坛

动动手指&#xff0c;无需每天打卡&#xff0c;用刷手机的零碎时间领一份Web3惊喜&#xff01; 本次挑战的目标是鼓励大家参与社区治理、熟悉论坛操作。有关参与方式和原因的信息在Twitter上共享&#xff1a;有兴趣可以和ThinkWildCrypto一起探索论坛以解锁其功能、了解最近和正…

【虹科干货】如何有效运用虹科任意波形发生器工作模式?

图 1&#xff1a;显示从存储器到输出的数据路径的 AWG 概念框图 01引言 任意波形发生器 (AWG) 的强大功能之一是它们可以生成几乎无限数量的波形。 AWG 的工作模式控制这些波形输出方式的时序。 在本应用说明中&#xff0c;我们将研究虹科Spectrum M4i.66xx 系列 AWG 工作模式…