Tomcat源码解析(八):一个请求的执行流程(附Tomcat整体总结)

news2024/11/27 1:36:14

Tomcat源码系列文章

Tomcat源码解析(一):Tomcat整体架构

Tomcat源码解析(二):Bootstrap和Catalina

Tomcat源码解析(三):LifeCycle生命周期管理

Tomcat源码解析(四):StandardServer和StandardService

Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper

Tomcat源码解析(六):Connector、ProtocolHandler、Endpoint

Tomcat源码解析(七):底层如何获取请求url、请求头、json数据?

Tomcat源码解析(八):一个请求的执行流程


文章目录

  • 前言
  • 一、Engine管道内容
    • 1、StandardEngineValve
    • 2、如何通过Engine找到Host
  • 二、Host管道内容
    • 1、ErrorReportValve(拼接html错误页面)
    • 2、StandardHostValve
    • 3、如何通过Host找到Context(上下文)
  • 三、Context管道内容
    • 1、StandardContextValve
    • 2、如何通过Context找到Wrapper
  • 四、Wrapper管道内容
    • 1、StandardWrapperValve
      • 1.1、创建过滤器链
      • 1.2、执行过滤器链
        • 1.2.1、Request和Response的门面模式
        • 1.2.2、doFilter方法
  • Tomcat最终总结


前言

  前文中我们介绍了NIO解析请求数据,网络字节流转化为Request和Response对象。接下来介绍拿到Req和Res之后如何走到Servelt,以及正常响应返回。

  回顾之前篇章,NioEndpoint通过socket服务端ServerSocketChannel.accept()监听8080端口接收连接,获取到连接扔给连接池处理,SocketProcessor从NioChannel通道中读取数据到ByteBuff缓冲区再赋值给对应属性,最后通过适配器CoyoteAdapter生成容器Req和Res调用容器管道的执行方法。

在这里插入图片描述

  Endpoint是连接器Connector的核心组件之一,那么NioEndpoint接受到的连接最后交给自己的连接器connector;由如下server.xml可知,Service对象由一个容器Engine和多个连接器Connector组成,所以结合上面核心代码connector.getService().getContainer()获取到的就是自己的顶级容器Engine

在这里插入图片描述

  以前第一篇文章Tomcat源码解析(一):Tomcat整体架构最后一部分有说过管道的结构。这里再简单的说下容器管道,其实可以理解为容器Engine、Host、Context、Wrapper设置的拦截器,一个请求进来,需要通过每个容器设置的拦截器(如下链状结构,可以设置多个),也就是说每个容器可能有多个处理点。作用其实就是在请求Servelt之前可以拦截请求做一些额外处理另外一方面,也是从顶级容器Engine找到Wrapper从而找到Servelt执行我们写的业务逻辑

在这里插入图片描述

一、Engine管道内容

1、StandardEngineValve

  这里感觉没啥核心内容,其实就是找到对应的Host,然后调用Host的管道执行方法。

final class StandardEngineValve extends ValveBase {
	...
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // 从request中获取虚拟主机host
        Host host = request.getHost();
        if (host == null) {
            response.sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost",
                              request.getServerName()));
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }
        // 请此主机处理此请求,调用对应主机的管道执行方法
        host.getPipeline().getFirst().invoke(request, response);
    }
}

2、如何通过Engine找到Host

  顶级容器Engine下可以有多个虚拟主机Host(主机名称和ip地址,默认localhost);在上篇文章中讲过NIO解析请求数据,里面自然包括请求ip地址,此时只要比对下即可在多个虚拟主机Host中找到。

  在解析请求后会调用如下方法,最终会将获取到Host对象的mappingData.host属性赋值给Request,这样上面request.getHost()就能获取到对应的Host了。

在这里插入图片描述

二、Host管道内容

AccessLogValve

  • 这个处理点是日志记录用的,具体也没研究干啥的
  • 这里可以理解为Host的拦截器链,这个执行点执行完,调用下一个

在这里插入图片描述

1、ErrorReportValve(拼接html错误页面)

  • 接下来这个处理点,就是本单元的主要讲的内容,错误页面的拼接
public class ErrorReportValve extends ValveBase {
	...
    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {

        // 执行请求
        getNext().invoke(request, response);

		...
		
		// 此异常是执行请求时候捕获的,如我们的业务逻辑抛出的异常,这里就能获取到
		// 后面会讲到
        Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

		...

        try {
        	// 返回html的错误页面
            report(request, response, throwable);
        } catch (Throwable tt) {
            ExceptionUtils.handleThrowable(tt);
        }
    }
    ...
}
  • 在1xx、2xx和3xx状态下不执行任何操作
  • 4xx客户端错误,5xx服务端错误则需要组装错误响应业务
  • html字符串拼接完成后,将数据通过网络写出到客户端
// ErrorReportValve类方法
protected void report(Request request, Response response, Throwable throwable) {

    int statusCode = response.getStatus();

    // 在 1xx、2xx 和 3xx 状态下不执行任何操作
    // 4xx客户端错误,5xx服务端错误则需要组装错误响应业务
    if (statusCode < 400 || response.getContentWritten() > 0 || !response.setErrorReported()) {
        return;
    }

	...
	
	// sb即为拼接的html返回字符串
    StringBuilder sb = new StringBuilder();

    sb.append("<!doctype html><html lang=\"");
    sb.append(smClient.getLocale().getLanguage()).append("\">");
    sb.append("<head>");
    sb.append("<title>");
    sb.append(smClient.getString("errorReportValve.statusHeader",
            String.valueOf(statusCode), reason));
    sb.append("</title>");
    sb.append("<style type=\"text/css\">");
    sb.append(TomcatCSS.TOMCAT_CSS);
    sb.append("</style>");
    sb.append("</head><body>");
    sb.append("<h1>");
    sb.append(smClient.getString("errorReportValve.statusHeader",
            String.valueOf(statusCode), reason)).append("</h1>");
    if (isShowReport()) {
        sb.append("<hr class=\"line\" />");
        sb.append("<p><b>");
        sb.append(smClient.getString("errorReportValve.type"));
        sb.append("</b> ");
        if (throwable != null) {
            sb.append(smClient.getString("errorReportValve.exceptionReport"));
        } else {
            sb.append(smClient.getString("errorReportValve.statusReport"));
        }
        sb.append("</p>");
        if (!message.isEmpty()) {
            sb.append("<p><b>");
            sb.append(smClient.getString("errorReportValve.message"));
            sb.append("</b> ");
            sb.append(message).append("</p>");
        }
        sb.append("<p><b>");
        sb.append(smClient.getString("errorReportValve.description"));
        sb.append("</b> ");
        sb.append(description);
        sb.append("</p>");
        if (throwable != null) {
            String stackTrace = getPartialServletStackTrace(throwable);
            sb.append("<p><b>");
            sb.append(smClient.getString("errorReportValve.exception"));
            sb.append("</b></p><pre>");
            sb.append(Escape.htmlElementContent(stackTrace));
            sb.append("</pre>");

            int loops = 0;
            Throwable rootCause = throwable.getCause();
            while (rootCause != null && (loops < 10)) {
                stackTrace = getPartialServletStackTrace(rootCause);
                sb.append("<p><b>");
                sb.append(smClient.getString("errorReportValve.rootCause"));
                sb.append("</b></p><pre>");
                sb.append(Escape.htmlElementContent(stackTrace));
                sb.append("</pre>");
                // In case root cause is somehow heavily nested
                rootCause = rootCause.getCause();
                loops++;
            }

            sb.append("<p><b>");
            sb.append(smClient.getString("errorReportValve.note"));
            sb.append("</b> ");
            sb.append(smClient.getString("errorReportValve.rootCauseInLogs"));
            sb.append("</p>");

        }
        sb.append("<hr class=\"line\" />");
    }
    if (isShowServerInfo()) {
        sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
    }
    sb.append("</body></html>");

    try {
        try {
            response.setContentType("text/html");
            response.setCharacterEncoding("utf-8");
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
        }
        Writer writer = response.getReporter();
        if (writer != null) {
	        // 将响应的html写到响应对象Response的一个字符缓冲区CharBuffer中
	        writer.write(sb.toString());
	        // 将响应缓冲区中的数据通过网络发送给客户端
	        response.finishResponse();
        }
    } catch (IOException e) {
        // Ignore
    } catch (IllegalStateException e) {
        // Ignore
    }
}
  • html字符串对应业务关键内容

在这里插入图片描述

2、StandardHostValve

  • 回到执行请求那里,继续向里走

在这里插入图片描述

  • 一旦我们业务代码抛出异常,这里会获取到,然后设置响应码response.setStatus(500)等等
  • 这些都是为上面说的拼接html错误页面做准备
// StandardHostValve类方法
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // 获取请求的Context(上下文)
    Context context = request.getContext();
    if (context == null) {
        return;
    }
	
	...

    try {
    	...
        try {
            if (!response.isErrorReportRequired()) {
            	// 下一个执行点NonLoginAuthenticator
                context.getPipeline().getFirst().invoke(request, response);
            }
        } catch (Throwable t) {
			...
        }
		...
		
		// 此异常是执行请求时候捕获的,如我们的业务逻辑抛出的异常,这里就能获取到
        Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

        // Look for (and render if found) an application level error page
        if (response.isErrorReportRequired()) {
            if (t != null) {
            	// 设置响应信息
            	// public static final int SC_INTERNAL_SERVER_ERROR = 500;
            	// response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                throwable(request, response, t);
            } else {
                status(request, response);
            }
        }	
        ...
    } finally {
		...
    }
}

NonLoginAuthenticator

  • 此处理点主要是tomcat登录权限以及其他权限校验,暂不做研究
  • 接着继续下一个就是Context的处理点

在这里插入图片描述

3、如何通过Host找到Context(上下文)

  虚拟主机Host下可能有多个项目,即webapps目录下的文件夹,每个文件夹就是一个应用项目,而这个文件夹的名称即请求url的统一前缀。

  在解析请求后调用如下方法,最终会将获取到Context对象的mappingData.context属性赋值给Request,这样上面request.getContext()就能获取到上下文Context。

在这里插入图片描述

三、Context管道内容

1、StandardContextValve

  • 禁止直接访问 WEB-INFMETA-INF 下的资源
  • 获取Wrapper,找不到设置错误码404,最后调用Wrapper的处理点
// StandardContextValve类方法
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // 禁止直接访问 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;
    }

    // 获取请求的Wrapper
    Wrapper wrapper = request.getWrapper();
    if (wrapper == null || wrapper.isUnavailable()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // 确认请求
    try {
        // 最终会调用 Http11Processor#ack() 方法
        // 也就是简单地将 HTTP/1.1 100 加上回车换行符写给客户端
        // public static final byte[] ACK_BYTES = ByteChunk.convertToBytes("HTTP/1.1 100 " + CRLF + CRLF);
        response.sendAcknowledgement();
    } 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());
    }
    // 调用Wrapper的处理点
    wrapper.getPipeline().getFirst().invoke(request, response);
}

2、如何通过Context找到Wrapper

  tomcat启动时候,在将所有Servelt实例化以后,会将所有的映射url和Wrapper组成MappedWrapper统一放到esactWrappers集合中。

  在解析请求后调用如下方法,通过请求解析的path找到esactWrappers集合中对应的MappedWrapper,最终会将获取到Wrapper对象的mappingData.wrapper属性赋值给Request,这样上面request.getWrapper()就能获取到Wrapper,从而找到Servelt。

在这里插入图片描述

  之前篇章Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper最后一节Mapper组件介绍过Mapper的组成,下面再来看下Mapper中对应映射和Wrapper的位置

在这里插入图片描述

四、Wrapper管道内容

1、StandardWrapperValve

  • 第一步:获取Wrapper中的Servelt实例(loadOnStartup>0的已经在项目启动时候实例化和初始化),如果loadOnStartup默认值-1则表示此时才会实例化和初始化Servelt并返回
  • 第二步:为此请求创建过滤器链(包括要执行的Servelt),过滤器链先添加Servelt,再通过过滤器的urlPatterns和servletNames匹配当前servelt添加到过滤器链中
  • 第三步:过滤器链执行完以后,释放过滤器链,将过滤器链中的过滤器和Servelt置为空,因为下个请求还需要重新创建过滤器链
// StandardWrapperValve类方法
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

	..

	// 获取Wrapper
    StandardWrapper wrapper = (StandardWrapper) getContainer();
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();

	...

    // 分配一个 servlet 实例来处理此请求
    // 如果是loadOnStartup>0的Servlet直接从Wrapper中获取即可,否则需要实例化创建
    try {
        if (!unavailable) {
            servlet = wrapper.allocate();
        }
    } catch (xxxException e) {
		...
    } 
	
	// 解析请求的mapping映射
	// 如:http://localhost:8080/springmvc/servletTomcat,这里为/serveltTomcat
    MessageBytes requestPathMB = request.getRequestPathMB();

	...	

    // 为此请求创建过滤器链(包括要执行的Servelt)
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    // 为此请求调用过滤器链
    // 注意:这也调用了servlet的service()方法
    try {
        if ((servlet != null) && (filterChain != null)) {
        	...
        	// 执行连接器链
	        filterChain.doFilter(request.getRequest(),response.getResponse());
			...
        }
	} catch (xxxException e) {
        throwable = e;
        // 将异常添加到request,并设置错误码500
        exception(request, response, e);
    }

    // 释放过滤器链,将过滤器链中的过滤器和Servelt置为空
    if (filterChain != null) {
        filterChain.release();
    }
	
	...    
}

1.1、创建过滤器链

  • 从req从获取过滤器链,没有的话创建ApplicationFilterChain过滤器链对象
  • servelt添加到过滤器链中
  • 获取项目启动时候实例化的所有过滤器
  • 先根据过滤器的urlPatterns匹配当前servelt,匹配成功添加到过滤器链中
  • 再根据过滤器的servletNames匹配当前servelt,匹配成功添加到过滤器链中
// ApplicationFilterFactory类方法
public static ApplicationFilterChain createFilterChain(ServletRequest request,
        Wrapper wrapper, Servlet servlet) {

    // 如果servelt为空,则返回null
    if (servlet == null)
        return null;

    // 创建过滤器链对象,并设置给request
    ApplicationFilterChain filterChain = null;
    if (request instanceof Request) {
        Request req = (Request) request;
        if (Globals.IS_SECURITY_ENABLED) {
            // Security: Do not recycle
            filterChain = new ApplicationFilterChain();
        } else {
            filterChain = (ApplicationFilterChain) req.getFilterChain();
            if (filterChain == null) {
                filterChain = new ApplicationFilterChain();
                req.setFilterChain(filterChain);
            }
        }
    } else {
        // Request dispatcher in use
        filterChain = new ApplicationFilterChain();
    }
	
	// 将servelt添加到过滤器链中
    filterChain.setServlet(servlet);
    filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

    // 获取上下文及项目启动加载的所有过滤器
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps();

    // 如果没有过滤器,我们就完成了,自己返回,里面只有servelt
    if ((filterMaps == null) || (filterMaps.length == 0))
        return (filterChain);

    // 拦截方式配置也就是资源被访问的形式(没明白)
    DispatcherType dispatcher =
            (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
	...

    String servletName = wrapper.getName();

    // 根据过滤器的urlPatterns匹配当前servelt
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
            continue;
        }
        if (!matchFiltersURL(filterMaps[i], requestPath)){
            continue;
        }
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            continue;
        }
        // 添加到过滤器链
        filterChain.addFilter(filterConfig);
    }

    // 根据过滤器的servletNames匹配当前servelt
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
            continue;
        }
        if (!matchFiltersServlet(filterMaps[i], servletName)){
            continue;
        }
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            // FIXME - log configuration problem
            continue;
        }
        // // 添加到过滤器链
        filterChain.addFilter(filterConfig);
    }

    // 返回完整的过滤器链
    return filterChain;
}

1.2、执行过滤器链

在这里插入图片描述

1.2.1、Request和Response的门面模式

  在调用拦截器链之前,先看下request.getRequest(), response.getResponse()这两个方法,在这之前Request指的是Request implements HttpServletRequest,Response指的是Response implements HttpServletResponse。从这个方法进入以后,Request指的是RequestFacade implements HttpServletRequest,Response指的是ResponseFacade implements HttpServletResponse这里是利用门面模式,将Req和Res的内容分别包装在RequestFacade和ResponseFacade里面,后者就是起到一个传递作用,为的是保护Req和Res中的属性方法,只在后者暴露想让业务调用者调用的属性和方法。

获取RequestFacade和ResponseFacade

  • 其实很简单,就是在RequestFacade和ResponseFacade对象中分别设置request和response属性
  • 外界获取属性方法都是在RequestFacade的方法中调用Req和Res所得
// Request类方法
public HttpServletRequest getRequest() {
    if (facade == null) {
        facade = new RequestFacade(this);
    }
    if (applicationRequest == null) {
        applicationRequest = facade;
    }
    return applicationRequest;
}
// RequestFacade构造方法
protected Request request = null;
public RequestFacade(Request request) {
    this.request = request;
}

// Response类方法
public HttpServletResponse getResponse() {
    if (facade == null) {
        facade = new ResponseFacade(this);
    }
    if (applicationResponse == null) {
        applicationResponse = facade;
    }
    return applicationResponse;
}
// ResponseFacade构造方法
protected Response response = null;
public ResponseFacade(Response response) {
     this.response = response;
}
1.2.2、doFilter方法

在这里插入图片描述

  • 核心方法,先执行拦截器链,再执行Servelt实例
private void internalDoFilter(ServletRequest request,
                              ServletResponse response)
    throws IOException, ServletException {

    // 如果有,请调用下一个过滤器。n是过滤器的个数,pos默认值是0
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        try {
            Filter filter = filterConfig.getFilter();
            ...
			filter.doFilter(request, response, this);
			...
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } 
        return;
    }

    // 调用Servelt实例
    try {
    	...
		servlet.service(request, response);
		...
    } catch (IOException | ServletException | RuntimeException e) {
        throw e;
    } 
}

  拦截器实例,拦截器的foFilter方法最后一定要调用filterChain.doFilter(servletRequest,servletResponse)这样整个拦截器链包括Servelt实例才能调用完整。

在这里插入图片描述

  就这样,一个请求的执行流程执行完毕。

Tomcat最终总结

看着server.xml更容易理解

在这里插入图片描述

  • 一个Server类的实例就代表了一个Tomcat的容器,一个Tomcat进程只会有一个Server实例,也是Tomcat的主线程Socket监听8005端口,ServerSocket服务端只要接受到Socket客户端发送消息“SHUTDOWN”(不论大小写),就会停止Tomcat应用
  • 一个Server实例可以包含多个Service对象,Service对象由一个容器和多个连接器组成
    • 容器:加载和管理Servlet,以及具体处理Request请求
    • 连接器:处理Socket连接,负责网络字节流与Request和Response对象的转化
  • 容器分为:顶级容器Engine,虚拟主机Host,Web应用程序Context,Servelt包装类Wrapper
    • Engine:从一个或多个Connector中接受请求并处理,并将完成的响应返回给Connector,最终传递给客户端。解析server.xml获取它下面所有的Host引用
    • Host:运行多个Web应用(一个Context代表一个Web应用 ),并负责安装、展开、启动和结束每个Web应用。Context和Wrapper中解析出的请求映射和Servelt的内容统一放到Mapper中获取
    • Context:一个web应用。加载webapps目录下的web应用,实例化和初始化监听器、过滤器、Servlet
  • 考虑到不同网络通信和应用层协议,所以会有不同的连接器
    • 默认8080端口的http协议,8009的AJP协议
    • 连接器核心组件Endpoint使用三种线程接受处理请求
      • Acceptor线程:一直死循环通过SocketChannel的accept方法接受连接,阻塞方法
      • Poller线程:获取到Acceptor线程的连接,通过SocketChannel注册监听读事件,交给连接池处理
      • 任务线程:读取解析socket请求数据封装为request和response调用Servelt方法
  • 请求的处理流程(结合上面server.xml理解)
    • 连接器Connector监听解析拿到请求,通过Service对象找到唯一的顶级容器Engine
    • 顶级容器下有多个虚拟主机Host,与本次请求解析的url对比,获取本次请求的Host
    • 虚拟主机下的webapps下有多个web应用,与本次请求url的path对比,获取本次请求web应用
    • web应用下有多个Servelt,通过Mapper中记录的请求Mapping映射和Servelt对应关系找到Servevlt

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

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

相关文章

keda-P0460. 潜水员

可达信奥 - 登录 - 可达信奥https://kedaoi.cn/p/P0460 代码思路&#xff1a; 01背包DP。 思路也是比较经典的&#xff0c;就是看用这个水缸的最小值小&#xff0c;还是不用这个水缸的最小值小。但是这里涉及到一个初始化的问题&#xff0c;因为要求最小所以初始化理应…

使用NetAssist网络调试助手在单台计算机上配置TCP服务器和客户端

要使用NetAssist网络调试助手在同一台计算机上配置一个实例作为服务器&#xff08;server&#xff09;和另一个实例作为客户端&#xff08;client&#xff09;&#xff0c;可以按照以下步骤进行操作&#xff1a; 前提条件 确保已经安装NetAssist网络调试助手&#xff0c;并了…

streamlit:如何快速构建一个应用,不会前端也能写出好看的界面

通过本文你可以了解到&#xff1a; 如何安装streamlit&#xff0c;运行起来第一个demo熟悉streamlit的基本语法&#xff0c;常用的一些组件使用streamlit库构建应用 大模型学习参考&#xff1a; 大模型学习资料整理&#xff1a;如何从0到1学习大模型&#xff0c;搭建个人或企业…

鞠小云张霖浩闪耀北京广播电视台春晚发布会,豪门姐弟感爆棚

昨日&#xff0c;2025年北京广播电视台“追梦春晚”全国海选发布会在杭州举行&#xff0c;中国内地青年女演员鞠小云同人气幕后张霖浩&#xff0c;受主办方盛情邀请出席本次活动。从现场流露出的照片中可以看出&#xff0c;鞠小云一袭白色长裙灵动温婉素雅&#xff0c;而张霖浩…

springboot与flowable(1):介绍、Flowable-ui使用

一、工作流引擎使用场景 工作流在企业管理系统中是高频使用的功能&#xff0c;一个最常见的例子是请假加班申请与审批的过程。事实上&#xff0c;工作流引擎能支持的业务场景远远不止单据审批&#xff0c;几乎所有涉及到业务流转、多人按流程完成工作的场景背后都可以通过工作流…

Vue3【二】 VSCode需要安装的Vue语法插件

VSCode需要安装的 适配Vue3的插件 Vue-Official插件安装

A股所有公司ZL申请与创新绩效分析(2000-2022年)

数据简介&#xff1a;专利是创新成果的主要载体&#xff0c;专利所包含的技术、经济、法律等信息主要通过结构化专利专利文献著录项的形式加以呈现。专利申请与创新绩效已成为衡量上市公司竞争力的重要指标。目前&#xff0c;各国ZSCQ管理部门开发管理的开放式平台是获取ZL数据…

PyQt5 多进程 多任务 多线程实现进度条功能 无边框 含源码

概述&#xff1a; 在项目 中我们常遇到&#xff0c;大量计算或者加载数据时&#xff0c;需要用到多线程&#xff0c;此时只能等待&#xff0c;我们这个时间需要添加一下进度条&#xff0c;告诉用户当前需要等待&#xff0c;这时间就需要用到多线程和等待进度条&#xff1b; 效…

【STM32】GPIO输出(江科大)

一、GPIO简介 1.GPIO&#xff1a;通用输入输出口 2.可配置为8种输入输出模式 3.引脚电平&#xff1a;0-3.3V&#xff08;输出最大3.3V&#xff09;&#xff0c;部分引脚可容忍5V&#xff08;输入&#xff0c;有FT&#xff09; 4.输出模式下&#xff0c;可控制端口输出高低电平…

94、二叉树的迭代遍历

实现对二叉树的前后序非递归遍历 题解&#xff1a; 递归的实现就是&#xff1a;递去&#xff0c;归来。每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中&#xff0c;然后递归返回的时候&#xff0c;从栈顶弹出上一次递归的各项参数&#xff0c;所以这就是…

MySQL系列-安装配置使用说明(MAC版本)

1、前言 本文将介绍MySQL的安装配置以及基本语法操作说明 环境&#xff1a;mac 版本&#xff1a;MySQL 8.0.28 之前电脑安装卸载过&#xff0c;后面在装的时候遇到一些问题&#xff0c;用了四五天才解决&#xff0c;主要是参考 https://blog.csdn.net/zz00008888/article/deta…

IPv6 自动配置流程图

IPv6 自动配置流程图 IPv6 自动配置生命周期 Mark

人事信息管理系统(Java+MySQL)

一、项目背景 在现代企业中&#xff0c;管理大量员工的工作信息、薪资、请假、离职等事务是一项非常繁琐和复杂的任务。传统的手工管理方式不仅效率低下&#xff0c;而且容易出错。为了提高人事管理的效率&#xff0c;减少人工操作带来的错误&#xff0c;企业迫切需要一个高效…

乒乓球烧拍日记之一结构分析

先上图 这是这两年烧过的球拍&#xff0c;最贵的是palio C2 80元左右&#xff0c;大部分球拍儿都是十多元&#xff0c;其中横拍儿是小朋友用的&#xff0c;直拍是我自己用的。照片中球拍儿的分类是按结构特点和材料来分的&#xff0c;最下面的两个剑轮球拍是成品拍儿。胶皮也都…

基于运动控制卡的圆柱坐标机械臂设计

1 方案简介 介绍一种基于运动控制卡制作一款scara圆柱坐标的机械臂设计方案&#xff0c;该方案控制器用运动控制卡制作一台三轴机械臂&#xff0c;用于自动抓取和放料操作。 2 组成部分 该机械臂的组成部分有研华运动控制卡&#xff0c;触摸屏&#xff0c;三轴圆柱坐标的平面运…

多目标融合参数搜索

多目标融合 权重分类目人群。 trick normlize 不同Score之间含义、量级和分布差异较大&#xff1a;评分计算的不同部分的意义、范围和分布存在显著差异&#xff0c;这使得直接比较或融合它们的结果变得困难。显式反馈&#xff08;如点赞率&#xff09;存在用户间差异&#…

2、数据操作

索引从0开始 一行 [1,:] 一列[:,1] 子区域&#xff1a;[1:3,1:] 第一行和第二行&#xff0c;从第一列开始 [::3,::2] 每3行一跳&#xff0c;每2列一跳 torch.tensor([[1,2,3,4]] 按位置算 xy ,x-y x*y x**y&#xff08;幂&#xff09; 1、广播机制形状不一样&#xff0c;…

2021 hnust 湖科大 操作系统课设 报告+原代码+指导书+流程图源文件

2021 hnust 湖科大 操作系统课设 报告原代码指导书流程图源文件 详情 目录 验证类实验&#xff1a; 1 实验一&#xff1a;Windows进程管理 1 一、 实验题目&#xff1a; 1 二、 实验目的 1 三、 实验内容 1 四、 实验结果与分析 2 五、 小结与心得体会 5 实验二&#xff1a;L…

【前端】详解JavaScript事件代理(事件委托)

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…

设计模式- 责任链模式(行为型)

责任链模式 责任链模式是一种行为模式&#xff0c;它为请求创建一个接收者对象的链&#xff0c;解耦了请求的发送者和接收者。责任链模式将多个处理器串联起来形成一条处理请求的链。 图解 角色 抽象处理者&#xff1a; 一个处理请求的接口&#xff0c;可以通过设置返回值的方…