Tomcat源码:CoyoteAdapter、Valve#invoke、ApplicationFilterChain

news/2024/10/18 16:46:22/

  前文:

《Tomcat源码:启动类Bootstrap与Catalina的加载》

《Tomcat源码:容器的生命周期管理与事件监听》

《Tomcat源码:StandardServer与StandardService》

《Tomcat源码:Container接口》

《Tomcat源码:StandardEngine、StandardHost、StandardContext、StandardWrapper》

 《Tomcat源码:Pipeline与Valve》

《Tomcat源码:连接器与Executor、Connector》

《Tomcat源码:ProtocolHandler与Endpoint》

《Tomcat源码:Acceptor与Poller、PollerEvent》

《Tomcat源码:SocketProcessor、ConnectionHandler与Http11Processor》

前言

        在前文中,我们介绍了Processor如何接收来自EndPoint的Socket,读取字节流解析成 Tomcat Request 和 Response 对象,最后将请求传递给了Adapter做进一步的处理。本文我们就来介绍下一个请求是如何从连接器被转发到容器中并由相应的servlet处理的。

目录

前言

一、CoyoteAdapter

        1、CoyoteAdapter

       2、service

        3、invoke

二、Valve#invoke

        1、invoke

        2、wrapper.allocate

        3、loadServlet  

        4、initServlet

三、ApplicationFilterChain

        1、createFilterChain

        findFilterMaps

         2、doFilter

         internalDoFilter


一、CoyoteAdapter

        1、CoyoteAdapter

        CoyoteAdapter的创建是在Connector#initInternal中完成的,这里会new一个CoyoteAdapter对象并将当前的Connector 对象传入其中。

public class Connector extends LifecycleMBeanBase {/*** Coyote adapter.*/protected Adapter adapter = null;protected void initInternal() throws LifecycleException {super.initInternal();// Initialize adapteradapter = new CoyoteAdapter(this);protocolHandler.setAdapter(adapter);// ...
}

        CoyoteAdapter类继承自 Adapter 接口,该接口定义了适配器的基本功能。

public class CoyoteAdapter implements Adapter {/*** Construct a new CoyoteProcessor associated with the specified connector.** @param connector CoyoteConnector that owns this processor*/public CoyoteAdapter(Connector connector) {super();this.connector = connector;}
}

       2、service

        然后我们来看service方法。

    public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {Request request = (Request) req.getNote(ADAPTER_NOTES);Response response = (Response) res.getNote(ADAPTER_NOTES);if (request == null) {// Create objectsrequest = connector.createRequest();request.setCoyoteRequest(req);response = connector.createResponse();response.setCoyoteResponse(res);// Link objectsrequest.setResponse(response);response.setRequest(request);// Set as notesreq.setNote(ADAPTER_NOTES, request);res.setNote(ADAPTER_NOTES, response);// Set query string encodingreq.getParameters().setQueryStringCharset(connector.getURICharset());}// ...try {postParseSuccess = postParseRequest(req, request, res, response);if (postParseSuccess) {request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);}request.finishRequest();response.finishResponse();} catch (IOException e) {} finally {// ...}}

        首先根据 org.apache.coyote.Request#getNote(ADAPTER_NOTES) 和 org.apache.coyote.Response#getNote(ADAPTER_NOTES) 来获取 org.apache.catalina.connector.Request 和 org.apache.catalina.connector.Response 对象,
如果获取不到 org.apache.catalina.connector.Request 对象,就创建 org.apache.catalina.connector.Request 和 org.apache.catalina.connector.Response 对象,并分别设置到 notes 数组的 notes[1] 元素里。

Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);public final class Request {/*** Notes.*/private final Object notes[] = new Object[Constants.MAX_NOTES];public Object getNote(int pos) {return notes[pos];}
}public final class Response {/*** Notes.*/final Object notes[] = new Object[Constants.MAX_NOTES];public Object getNote(int pos) {return notes[pos];}
}

        Connector 的 createRequest 和 createResponse 也很简单。

    /*** Create (or allocate) and return a Request object suitable for specifying the contents of a Request to the* responsible Container.** @return a new Servlet request object*/public Request createRequest() {Request request = new Request();request.setConnector(this);return (request);}/*** Create (or allocate) and return a Response object suitable for receiving the contents of a Response from the* responsible Container.** @return a new Servlet response object*/public Response createResponse() {Response response = new Response();response.setConnector(this);return (response);}

        然后就进入 try-catch 语句块。首先调用 postParseRequest 方法,这个方法是在 http 的 header 解析完之后,对 Request 和 Response 做一些设置的工作,里面包扩了uri参数解析,Host映射等重要步骤。

        3、invoke

        调用完 postParseRequest 后就进入关键代码 if (postParseRequest) 里,在这个 if 里,先调用一下 request.setAsyncSupported,然后调用        

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

         这里的connector 是在 CoyoteAdapter 初始化时传入的,根据我们前文中对容器组件结构的介绍,这里connector.getService() 返回的是 Connector 关联的 Service 属性,而后续的getContainer() 返回的是 Service 里的容器 Engine 属性,至于getPipeline().getFirst()则是获取Engine容器的Valve。(这块内容我们在前文《Tomcat源码:Pipeline与Valve》已做过详细介绍,这里就不赘述了)

public CoyoteAdapter(Connector connector) {super();this.connector = connector;
}

        由于各层容器见的关系如下图所示,因此最后会调用StandardWrapperValve类的invoke方法。

    // StandardEngineValve.javapublic void invoke(Request request, Response response) throws IOException, ServletException {// 获取一个 Host 对象,获取不到就直接返回Host host = request.getHost();if (host == null) {if (!response.isError()) {response.sendError(404);}return;}if (request.isAsyncSupported()) {request.setAsyncSupported(host.getPipeline().isAsyncSupported());}host.getPipeline().getFirst().invoke(request, response);}    // StandardHostValve.javapublic void invoke(Request request, Response response) throws IOException, ServletException {Context context = request.getContext();if (context == null) {if (!response.isError()) {response.sendError(404);}return;}// 其余代码context.getPipeline().getFirst().invoke(request, response);}  // StandardContextValve.javapublic void invoke(Request request, Response response) throws IOException, ServletException {Wrapper wrapper = request.getWrapper();if (wrapper == null || wrapper.isUnavailable()) {response.sendError(HttpServletResponse.SC_NOT_FOUND);return;}// Acknowledge the requesttry {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;}wrapper.getPipeline().getFirst().invoke(request, response);}

         整个流程如下图所示,StandardEngineValve、StandardHostValve、StandardContextValve这三个 Valve 的 invoke 方法的核心逻辑就是调用子容器的 Pipeline 的 Valve 的invoke 方法,也就是 StandardEngineValve#invoke -> StandardHostValve#invoke -> StandardContextValve#invoke -> StandardWrapper#invoke 方法。 

                

        最后response.finishResponse()负责将数据返回给客户端。

    request.finishRequest();response.finishResponse();

二、Valve#invoke

        1、invoke

         紧接上文,invoke(request, response)最后会调用StandardWrapperValve类的invoke方法。这段比较长,下面我们来分别讲解下

final class StandardWrapperValve extends ValveBase {public void invoke(Request request, Response response) throws IOException, ServletException {boolean unavailable = false;StandardWrapper wrapper = (StandardWrapper) getContainer();Servlet servlet = null;Context context = (Context) wrapper.getParent();// Allocate a servlet instance to process this requesttry {if (!unavailable) {servlet = wrapper.allocate();}} catch (Throwable e) {// ...}// Create the filter chain for this requestApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);Container container = this.container;try {if ((servlet != null) && (filterChain != null)) {// Swallow output if neededif (context.getSwallowOutput()) {try {SystemLogHandler.startCapture();if (request.isAsyncDispatching()) {request.getAsyncContextInternal().doInternalDispatch();} else {filterChain.doFilter(request.getRequest(), response.getResponse());}} finally {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {context.getLogger().info(log);}}} else {if (request.isAsyncDispatching()) {request.getAsyncContextInternal().doInternalDispatch();} else {filterChain.doFilter(request.getRequest(), response.getResponse());}}}} finally {// ...}}
}

        2、wrapper.allocate

         首先是wrapper.allocate(),其实现类在StandardWrapper中,从注释中可以看出,这里会先根据singleThreadModel 属性判断是否为单线程模型,然后根据不同的情况选择调用loadServlet、initServlet来加载或是初始化servlet对象。

public class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper, NotificationEmitter {/*** Does this servlet implement the SingleThreadModel interface?** @deprecated This will be removed in Tomcat 10.1 onwards.*/@Deprecatedprotected volatile boolean singleThreadModel = false;/*** The (single) possibly uninitialized instance of this servlet.*/protected volatile Servlet instance = null;public Servlet allocate() throws ServletException {boolean newInstance = false;// If not SingleThreadedModel, return the same instance every timeif (!singleThreadModel) {// Load and initialize our instance if necessaryif (instance == null || !instanceInitialized) {synchronized (this) {if (instance == null) {try {instance = loadServlet();newInstance = true;if (!singleThreadModel) {countAllocated.incrementAndGet();}} catch (Throwable e) {// ...}}if (!instanceInitialized) {initServlet(instance);}}}if (singleThreadModel) {if (newInstance) {synchronized (instancePool) {instancePool.push(instance);nInstances++;}}} else {if (!newInstance) {countAllocated.incrementAndGet();}return instance;}}synchronized (instancePool) {while (countAllocated.get() >= nInstances) {// Allocate a new instance if possible, or else waitif (nInstances < maxInstances) {try {instancePool.push(loadServlet());nInstances++;} catch (ServletException e) {throw e;} catch (Throwable e) {ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("standardWrapper.allocate"), e);}} else {try {instancePool.wait();} catch (InterruptedException e) {// Ignore}}}countAllocated.incrementAndGet();return instancePool.pop();}}
}

        3、loadServlet  

       loadServlet这里的逻辑很简单,就是通过instanceManager.newInstance(servletClass)来创建

    /*** The fully qualified servlet class name for this servlet.*/protected String servletClass = null;public synchronized Servlet loadServlet() throws ServletException {// Nothing to do if we already have an instance or an instance poolif (!singleThreadModel && (instance != null)) {return instance;}Servlet servlet;try {long t1 = System.currentTimeMillis();// Complain if no servlet class has been specifiedif (servletClass == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.notClass", getName()));}InstanceManager instanceManager = ((StandardContext) getParent()).getInstanceManager();try {servlet = (Servlet) instanceManager.newInstance(servletClass);} catch (Throwable e) {// ...}initServlet(servlet);} finally {// ...}return servlet;}

        getParent()).getInstanceManager()回到StandardContext容器中获取 instanceManager对象,这个对象是在StandardContext的startInternal方法中初始化的,默认的实现类为DefaultInstanceManager ,从其具体实现可以看到这里newInstance实际上就是调用servlet的构造方法创建了实例(clazz.getConstructor().newInstance())。

public class DefaultInstanceManager implements InstanceManager {@Overridepublic Object newInstance(String className)throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException,ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException {Class<?> clazz = loadClassMaybePrivileged(className, classLoader);return newInstance(clazz.getConstructor().newInstance(), clazz);}
}

        4、initServlet

         servlet实例创建好了之后就是调用initServlet来初始化了,内容也很简单,就是调用了servlet的初始化方法init(),我们之前有专门介绍过servlet,有兴趣的可以看下这篇文章《Tomcat:servlet与servlet容器》

    private synchronized void initServlet(Servlet servlet) throws ServletException {if (instanceInitialized && !singleThreadModel) {return;}// Call the initialization method of this servlettry {if (Globals.IS_SECURITY_ENABLED) {boolean success = false;try {Object[] args = new Object[] { facade };SecurityUtil.doAsPrivilege("init", servlet, classType, args);success = true;} finally {if (!success) {SecurityUtil.remove(servlet);}}} else {servlet.init(facade);}instanceInitialized = true;}catch (Throwable f) {// ...}}

        在获取到了servlet对象后,下一步会获取过滤器调用链,并使用doFilter方法来进行过滤。

三、ApplicationFilterChain

        1、createFilterChain

        createFilterChain从名字可以看出来是创建过滤链。首先创建ApplicationFilterChain对象,然后调用context.findFilterMaps()获取所有的过滤器,最后通过映射路径和servlet名来匹配能作用当前servlet上的过滤器,最后调用context.findFilterConfig来获取一个 ApplicationFilterConfig 对象,并调用filterChain.addFilter(filterConfig) 把这个对象加入到 filterChain 里。

    public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {// If there is no servlet to execute, return nullif (servlet == null) {return null;}// Create and initialize a filter chain objectApplicationFilterChain filterChain = null;if (request instanceof Request) {Request req = (Request) request;if (Globals.IS_SECURITY_ENABLED) {// Security: Do not recyclefilterChain = new ApplicationFilterChain();} else {filterChain = (ApplicationFilterChain) req.getFilterChain();if (filterChain == null) {filterChain = new ApplicationFilterChain();req.setFilterChain(filterChain);}}} else {// Request dispatcher in usefilterChain = new ApplicationFilterChain();}filterChain.setServlet(servlet);filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());// Acquire the filter mappings for this ContextStandardContext context = (StandardContext) wrapper.getParent();FilterMap filterMaps[] = context.findFilterMaps();// If there are no filter mappings, we are doneif ((filterMaps == null) || (filterMaps.length == 0)) {return filterChain;}// Acquire the information we will need to match filter mappingsDispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);String requestPath = null;Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);if (attribute != null) {requestPath = attribute.toString();}String servletName = wrapper.getName();// Add the relevant path-mapped filters to this filter chainfor (FilterMap filterMap : filterMaps) {if (!matchDispatcher(filterMap, dispatcher)) {continue;}if (!matchFiltersURL(filterMap, requestPath)) {continue;}ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());if (filterConfig == null) {// FIXME - log configuration problemcontinue;}filterChain.addFilter(filterConfig);}// Add filters that match on servlet name secondfor (FilterMap filterMap : filterMaps) {if (!matchDispatcher(filterMap, dispatcher)) {continue;}if (!matchFiltersServlet(filterMap, servletName)) {continue;}ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());if (filterConfig == null) {// FIXME - log configuration problemcontinue;}filterChain.addFilter(filterConfig);}// Return the completed filter chainreturn filterChain;}

        findFilterMaps

        StandardContext中定义了filterMaps用来保存应用程序里的 web.xml 文件里配置的 Filter 的相关信息的,比如名字、作用路径等。因此拿到 FilterMap[] 数组后,会分别调用matchDispatcher、matchFiltersURL这两个 for 循环。

    /*** @return the set of filter mappings for this Context.*/@Overridepublic FilterMap[] findFilterMaps() {return filterMaps.asArray();}

        在前文介绍StandardContext时我们提到过在web.xml被解析时会给context添加了一个监听器ContextConfig,这给监听器内部会调用方法为context容器查找出web.xml中所有的过滤器,并调用addFilterDef、addFilterMap将过滤器以不同的形式和内容注入到context中去。

        StandardContext#startInternal中会调用filterStart方法,该方法内会将过滤器封装到filterConfigs中,供上文中的context.findFilterConfig方法调用。

public class ContextConfig implements LifecycleListener {private void configureContext(WebXml webxml) {for (FilterDef filter : webxml.getFilters().values()) {if (filter.getAsyncSupported() == null) {filter.setAsyncSupported("false");}context.addFilterDef(filter);}for (FilterMap filterMap : webxml.getFilterMappings()) {context.addFilterMap(filterMap);}}
}public class StandardContext extends ContainerBase implements Context, NotificationEmitter {/*** The set of filter definitions for this application, keyed by filter name.*/private HashMap<String, FilterDef> filterDefs = new HashMap<>();/*** The set of filter mappings for this application, in the order they were defined in the deployment descriptor with* additional mappings added via the {@link ServletContext} possibly both before and after those defined in the* deployment descriptor.*/private final ContextFilterMaps filterMaps = new ContextFilterMaps();private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();public void addFilterMap(FilterMap filterMap) {validateFilterMap(filterMap);// Add this filter mapping to our registered setfilterMaps.add(filterMap);fireContainerEvent("addFilterMap", filterMap);}public FilterMap[] findFilterMaps() {return filterMaps.asArray();}public boolean filterStart() {// Instantiate and record a FilterConfig for each defined filterboolean ok = true;synchronized (filterConfigs) {filterConfigs.clear();for (Entry<String, FilterDef> entry : filterDefs.entrySet()) {String name = entry.getKey();if (getLogger().isDebugEnabled()) {getLogger().debug(" Starting filter '" + name + "'");}try {ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue());filterConfigs.put(name, filterConfig);} catch (Throwable t) {t = ExceptionUtils.unwrapInvocationTargetException(t);ExceptionUtils.handleThrowable(t);getLogger().error(sm.getString("standardContext.filterStart", name), t);ok = false;}}}return ok;}protected synchronized void startInternal() throws LifecycleException {// ...if (ok) {if (!filterStart()) {log.error(sm.getString("standardContext.filterFail"));ok = false;}}}public FilterConfig findFilterConfig(String name) {return filterConfigs.get(name);}
}

         2、doFilter

        doFilter会将请求一层层的经过上文中匹配到的过滤器链中的各个组件处理,可以看到这里其实是调用了internalDoFilter方法

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {if (Globals.IS_SECURITY_ENABLED) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction<Void>() {@Overridepublic Void run() throws ServletException, IOException {internalDoFilter(req, res);return null;}});} catch (PrivilegedActionException pe) {...}} else {internalDoFilter(request, response);}}

         internalDoFilter

        internalDoFilter 方法里分了两部分,if (pos < n) 和 if 后面的 try-catch 语句。

        if (pos < n)先从 filter 是数组里获取下一个 ApplicationFilterConfig 对象。

ApplicationFilterConfig filterConfig = filters[pos++];

         然后通过 filterConfig.getFilter() 获取一个 Filter 对象,并调用这个 Filter 的 doFilter 方法,并把 ApplicationFilterChain 对象传入 Filter#doFilter。

        在 if (pos < n) 里的 n 是这个 ApplicationFilterChain 里包含的 Filter 的总数。每次调用 internalDoFilter 方法,pos 就加1,也就是获取下一个 ApplicationFilterConfig,直到 pos 等于 n 的时候才不执行 if 语句,为了达到这个效果,应用程序必须在自定义的在 Filter 里执行 FilterChain#doFilter 方法,才能再次进入 ApplicationFilterChain#internalDoFilter 方法,这样就完成了调用 ApplicationFilterChain 里所有的 Filter 的 doFilter 方法。
这种方式是责任链模式的一种体现。

        internalDoFilter里的 try-catch 是在执行完所有的 Filter#doFilter 方法之后执行的。
try 语句块里先设置一下属性,然后执行

servlet.service(request, response);

        通过调用 Servlet 对象的 service 方法来处理请求。到这里,tomcat 终于把请求交给了应用程序了,tomcat 整个接受请求、转发处理请求的流程就差不多完结了。

    private void internalDoFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {// Call the next filter if there is oneif (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);}if (Globals.IS_SECURITY_ENABLED) {final ServletRequest req = request;final ServletResponse res = response;Principal principal = ((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[] { req, res, this };SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);} else {filter.doFilter(request, response, this);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.filter"), e);}return;}// We fell off the end of the chain -- call the servlet instancetry {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(request);lastServicedResponse.set(response);}if (request.isAsyncSupported() && !servletSupportsAsync) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);}// Use potentially wrapped request from this pointif ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) &&Globals.IS_SECURITY_ENABLED) {final ServletRequest req = request;final ServletResponse res = response;Principal principal = ((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[] { req, res };SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal);} else {servlet.service(request, response);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.servlet"), e);} finally {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(null);lastServicedResponse.set(null);}}}

        本文中流程处理过程总结如下图,到这里我们整个tomcat请求传递与映射的逻辑就整理完了,总体来看整个tomcat使用连接器与容器两部分来分别处理请求的传递与处理,并使用了一些方法来增加了并发以及吞吐能力。


http://www.ppmy.cn/news/1103751.html

相关文章

cookies 设置过期时间

1.如何在浏览器中查看cookie过期时间 F12-Application-Cookies可以查看到网页所有设置cookie值&#xff0c; 如果设置了过期时间的cookie是可以看到过期时间的持久cookie&#xff08;persistent cookie&#xff09;&#xff0c; 没有设置过期时间的是会话cookie&#xff08;s…

Sharding-JDBC分库分表-自定义分片算法-4

默认分片算法 Sharding JDBC通过org.apache.shardingsphere.sharding.spi.ShardingAlgorithm接口定义了数据分片算法&#xff0c;5.2.1版本默认提供了如下的分片算法 配置标识自动分片算法详细说明类名MODY基于取模的分片算法ModShardingAlgorithmHASH_MODY基于哈希取模的分片…

Eigen库中MatrixXd类型与VectorXd类型的相互映射与数据复制

一、Eigen库的Map功能 Eigen库的Map功能是一个强大的工具&#xff0c;用于将现有的数据&#xff08;例如数组或其他线性代数库的数据结构&#xff09;映射到Eigen矩阵或向量中&#xff0c;而无需复制数据。这种映射可以大大提高性能&#xff0c;因为它避免了不必要的数据复制&a…

中介模式简介

概念&#xff1a; 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为型设计模式&#xff0c;它通过引入一个中介者对象来解耦多个相关对象之间的交互。中介者充当了多个对象之间的协调者&#xff0c;使得这些对象不需要直接相互通信&#xff0c;而是通过与中介者…

Qt学习记录___9.10

1.QtSvg初体验 #include <QtSvg> QSvgWidget w1;w1.load(QString(":/iconfont-gongyichongwu.svg"));QSvgRenderer *renderw1.renderer();qDebug()<<render->defaultSize();w1.resize(render->defaultSize());w1.show(); 2. 对话框实验。 QT之隐藏…

YOLO的基本原理详解

YOLO介绍 YOLO是一种新的目标检测方法。以前的目标检测方法通过重新利用分类器来执行检测。与先前的方案不同&#xff0c;将目标检测看作回归问题从空间上定位边界框&#xff08;bounding box&#xff09;并预测该框的类别概率。使用单个神经网络&#xff0c;在一次评估中直接…

PLC-Recorder离线分析软件Ana里为什么不能显示变量的编号?

在PLC-Recorder在线软件里&#xff0c;大家可以在曲线上找到变量的编号&#xff08;由通道编号、变量类型、同类型序号组成&#xff09;。这个编号也是各软件识别变量的唯一标识。在变量和PLC很多时&#xff0c;可以方便地找到对应的PLC&#xff0c;如下&#xff1a; 有朋友问&…

30 | 工欲善其事必先利其器:后端性能测试工具原理与行业常用工具简介

对性能测试的理解和认识&#xff1a; 后端性能测试和后端性能测试工具之间的关系是什么&#xff1f; 后端性能测试工具和 GUI 自动化测试工具最大的区别是什么&#xff1f; 后端性能测试工具的原理是什么&#xff1f; 后端性能测试中&#xff0c;性能测试…