springboot源码解析(三):springboot内嵌tomcat

springboot源码解析(三):springboot内嵌tomcat

​ 在使用springboot搭建一个web应用程序的时候,我们发现不需要自己搭建一个tomcat服务器,只需要引入spring-boot-starter-web,在应用启动时会自动启动嵌入式的tomcat作为服务器,下面来分析下源码的分析流程:

​ 之前我们已经讲过了自动装配的原理,其实tomcat的实现机制也是从自动装配开始的。

1、ServletWebServerFactoryAutoConfiguration类

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,ServletWebServerFactoryConfiguration.EmbeddedJetty.class,ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {@Beanpublic ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {return new ServletWebServerFactoryCustomizer(serverProperties);}@Bean@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {return new TomcatServletWebServerFactoryCustomizer(serverProperties);}@Bean@ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {ForwardedHeaderFilter filter = new ForwardedHeaderFilter();FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);registration.setOrder(Ordered.HIGHEST_PRECEDENCE);return registration;}/*** Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via* {@link ImportBeanDefinitionRegistrar} for early registration.*/public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {private ConfigurableListableBeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {if (beanFactory instanceof ConfigurableListableBeanFactory) {this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;}}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {if (this.beanFactory == null) {return;}registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",WebServerFactoryCustomizerBeanPostProcessor.class);registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",ErrorPageRegistrarBeanPostProcessor.class);}private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);beanDefinition.setSynthetic(true);registry.registerBeanDefinition(name, beanDefinition);}}}}

从这个类上可以看到,当前配置类主要导入了BeanPostProcessorRegister,该类实现了ImportBeanDefinitionRegister接口,可以用来注册额外的BeanDefinition,同时,该类还导入了EmbeddedTomcat,EmbeddedJetty,EmbeddedUndertow三个类,可以根据用户的需求去选择使用哪一个web服务器,默认情况下使用的是tomcat

2、当自动装配功能完成之后会接着执行onRefresh的方法(ServletWebServerApplicationContext)

@Override
protected void onRefresh() {//创建主题对象,不用在意super.onRefresh();try {//开始创建web服务createWebServer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);}
}

3、创建web服务,默认获取的是tomcat的web容器(ServletWebServerApplicationContext)

private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {//获取servletWebServerFactory,从上下文注册bean中可以找到ServletWebServerFactory factory = getWebServerFactory();//获取servletContextInitializer,获取webServerthis.webServer = factory.getWebServer(getSelfInitializer());}else if (servletContext != null) {try {getSelfInitializer().onStartup(servletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context", ex);}}//替换servlet相关的属性资源initPropertySources();
}

如何获取tomcat的bean的实例对象呢?从如下代码中可以看出

ServletWebServerApplicationContext

protected ServletWebServerFactory getWebServerFactory() {// Use bean names so that we don't consider the hierarchyString[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);if (beanNames.length == 0) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "+ "ServletWebServerFactory bean.");}if (beanNames.length > 1) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));}return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);}
protected ServletWebServerFactory getWebServerFactory() {// Use bean names so that we don't consider the hierarchyString[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);if (beanNames.length == 0) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "+ "ServletWebServerFactory bean.");}if (beanNames.length > 1) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));}return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);}

DefaultListableBeanFactoryf

/*
第一个参数type表示要查找的类型
第二个参数表示是否考虑非单例bean
第三个参数表示是否允许提早初始化
*/
@Overridepublic String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {//配置还未被冻结或者类型为null或者不允许早期初始化if (!isConfigurationFrozen() || type == null || !allowEagerInit) {return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);}//此处注意isConfigurationFrozen为false的时候表示beanDefinition可能还会发生更改和添加,所以不能进行缓存,如果允许非单例bean,那么从保存所有bean的集合中获取,否则从单例bean中获取Map<Class<?>, String[]> cache =(includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);String[] resolvedBeanNames = cache.get(type);if (resolvedBeanNames != null) {return resolvedBeanNames;}//如果缓存中没有获取到,那么只能重新获取,获取到之后就存入缓存resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {cache.put(type, resolvedBeanNames);}return resolvedBeanNames;}
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {List<String> result = new ArrayList<>();// Check all bean definitions.for (String beanName : this.beanDefinitionNames) {// Only consider bean as eligible if the bean name// is not defined as alias for some other bean.//如果时别名则跳过(当前集合会保存所有的主beanname,并且不会保存别名,别名由beanfactory中别名map维护)if (!isAlias(beanName)) {try {//获取合并的beandefinition,合并的beandefinition是指spring整合了父beandefinition的属性,将其beandefinition编程了rootBeanDefinitionRootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);// Only check bean definition if it is complete.//抽象的beandefinition是不做考虑,抽象的就是拿来继承的,如果允许早期初始化,那么直接短路,进入方法体,如果不允许早期初始化,那么需要进一步判断,如果是不允许早期初始化的,并且beanClass已经被加载或者它是可以早期初始化的,那么如果当前bean是工厂bean,并且指定的bean又是工厂那么这个bean就必须被早期初始化,也就是说就不符合我们制定的allowEagerInit为false的情况,直接跳过if (!mbd.isAbstract() && (allowEagerInit ||(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&!requiresEagerInitForType(mbd.getFactoryBeanName()))) {//如果当前bean是工厂beanboolean isFactoryBean = isFactoryBean(beanName, mbd);//如果允许早期初始化,那么基本上会调用最后的isTypeMatch方法,这个方法会导致工厂的实例化,但是当前不允许进行早期实例化在不允许早期实例化的情况下,如果当前bean是工厂bean,那么它只能在已经被创建的情况下调用isTypeMatch进行匹配判断否则只能宣告匹配失败,返回falseBeanDefinitionHolder dbd = mbd.getDecoratedDefinition();boolean matchFound = false;boolean allowFactoryBeanInit = allowEagerInit || containsSingleton(beanName);boolean isNonLazyDecorated = dbd != null && !mbd.isLazyInit();if (!isFactoryBean) {if (includeNonSingletons || isSingleton(beanName, mbd, dbd)) {matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);}}else  {//如果没有匹配到并且他是个工厂bean,那么加上&前缀,表示要获取factorybean类型的beanif (includeNonSingletons || isNonLazyDecorated ||(allowFactoryBeanInit && isSingleton(beanName, mbd, dbd))) {matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);}if (!matchFound) {// In case of FactoryBean, try to match FactoryBean instance itself next.beanName = FACTORY_BEAN_PREFIX + beanName;matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);}}//找到便记录到result集合中,等待返回if (matchFound) {result.add(beanName);}}}catch (CannotLoadBeanClassException | BeanDefinitionStoreException ex) {if (allowEagerInit) {throw ex;}// Probably a placeholder: let's ignore it for type matching purposes.LogMessage message = (ex instanceof CannotLoadBeanClassException) ?LogMessage.format("Ignoring bean class loading failure for bean '%s'", beanName) :LogMessage.format("Ignoring unresolvable metadata in bean definition '%s'", beanName);logger.trace(message, ex);onSuppressedException(ex);}}}
// Check manually registered singletons too.//从单例注册集合中获取,这个单例集合石保存spring内部注入的单例对象,他们的特点就是没有beanDefinitionfor (String beanName : this.manualSingletonNames) {try {// In case of FactoryBean, match object created by FactoryBean.//如果是工厂bean,那么调用其getObjectType去匹配是否符合指定类型if (isFactoryBean(beanName)) {if ((includeNonSingletons || isSingleton(beanName)) && isTypeMatch(beanName, type)) {result.add(beanName);// Match found for this bean: do not match FactoryBean itself anymore.continue;}// In case of FactoryBean, try to match FactoryBean itself next.beanName = FACTORY_BEAN_PREFIX + beanName;}// Match raw bean instance (might be raw FactoryBean).//如果没有匹配成功,那么匹配工厂类if (isTypeMatch(beanName, type)) {result.add(beanName);}}catch (NoSuchBeanDefinitionException ex) {// Shouldn't happen - probably a result of circular reference resolution...logger.trace(LogMessage.format("Failed to check manually registered singleton with name '%s'", beanName), ex);}}return StringUtils.toStringArray(result);}

4、tomcat对象的初始化(ServletWebServerApplicationContext)

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {return this::selfInitialize;}private void selfInitialize(ServletContext servletContext) throws ServletException {//使用给定的完全加载的servletContext准备WebApplicationContextprepareWebApplicationContext(servletContext);registerApplicationScope(servletContext);//使用给定的BeanFactory注册特定于web的作用域bean(contextParameters,contextAttributes)WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);for (ServletContextInitializer beans : getServletContextInitializerBeans()) {beans.onStartup(servletContext);}
}

5、完成内嵌tomcat的api调用(TomcatServletWebServerFactory)

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {Registry.disableRegistry();}//完成tomcat的api调用Tomcat tomcat = new Tomcat();File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());Connector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);tomcat.getHost().setAutoDeploy(false);configureEngine(tomcat.getEngine());for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}//准备tomcatEmbeddedContext并设置到tomcat中prepareContext(tomcat.getHost(), initializers);//构建tomcatWebServerreturn getTomcatWebServer(tomcat);
}

6、获取tomcat服务(TomcatServletWebServerFactory)

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {return new TomcatWebServer(tomcat, getPort() >= 0);
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {Assert.notNull(tomcat, "Tomcat Server must not be null");this.tomcat = tomcat;this.autoStart = autoStart;//初始化initialize();
}

7、完成tomcat的初始化

private void initialize() throws WebServerException {logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));synchronized (this.monitor) {try {//engineName拼接instanceIdaddInstanceIdToEngineName();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.//删除Connectors,以便再启动服务时不发生协议绑定removeServiceConnectors();}});// Start the server to trigger initialization listeners//启动服务触发初始化监听器this.tomcat.start();// We can re-throw failure exception directly in the main thread//在主线程中重新抛出失败异常rethrowDeferredStartupExceptions();try {ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());}catch (NamingException ex) {// Naming is not enabled. Continue}// Unlike Jetty, all Tomcat threads are daemon threads. We create a// blocking non-daemon to stop immediate shutdown//所有的tomcat线程都是守护线程,我们创建一个阻塞非守护线程来避免立即关闭startDaemonAwaitThread();}catch (Exception ex) {//异常停止tomcatstopSilently();destroySilently();throw new WebServerException("Unable to start embedded Tomcat", ex);}}}
-----------------------private void removeServiceConnectors() {for (Service service : this.tomcat.getServer().findServices()) {Connector[] connectors = service.findConnectors().clone();//将将要移除的conntector放到缓存中暂存this.serviceConnectors.put(service, connectors);for (Connector connector : connectors) {//移除connectorservice.removeConnector(connector);}}}

8、除了refresh方法之外,在finishRefresh()方法中也对tomcat做了相关的处理(ServletWebServerApplicationContext)

	protected void finishRefresh() {//调用父类的finishRefresh方法super.finishRefresh();//启动webServerWebServer webServer = startWebServer();if (webServer != null) {//发布webServer初始化完成事件publishEvent(new ServletWebServerInitializedEvent(webServer, this));}}
ServletWebServerApplicationContext
	private WebServer startWebServer() {WebServer webServer = this.webServer;if (webServer != null) {//启动webserverwebServer.start();}return webServer;}

TomcatWebServer

	public void start() throws WebServerException {synchronized (this.monitor) {if (this.started) {return;}try {//添加之前移除的connectoraddPreviouslyRemovedConnectors();Connector connector = this.tomcat.getConnector();if (connector != null && this.autoStart) {//延迟加载启动performDeferredLoadOnStartup();}//检查connector启动状态是否为失败,失败抛出异常checkThatConnectorsHaveStarted();this.started = true;logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"+ getContextPath() + "'");}catch (ConnectorStartFailedException ex) {//异常停止tomcatstopSilently();throw ex;}catch (Exception ex) {if (findBindException(ex) != null) {throw new PortInUseException(this.tomcat.getConnector().getPort());}throw new WebServerException("Unable to start embedded Tomcat server", ex);}finally {Context context = findContext();//context解绑classloadContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());}}}
private void addPreviouslyRemovedConnectors() {Service[] services = this.tomcat.getServer().findServices();for (Service service : services) {//从上面移除connector添加的缓存中取出connectorConnector[] connectors = this.serviceConnectors.get(service);if (connectors != null) {for (Connector connector : connectors) {//添加到tomcat service中service.addConnector(connector);if (!this.autoStart) {//如果不是自动启动,则暂停connectorstopProtocolHandler(connector);}}//添加完成后移除this.serviceConnectors.remove(service);}}}
private void performDeferredLoadOnStartup() {try {for (Container child : this.tomcat.getHost().findChildren()) {if (child instanceof TomcatEmbeddedContext) {//延迟加载启动((TomcatEmbeddedContext) child).deferredLoadOnStartup();}}}catch (Exception ex) {if (ex instanceof WebServerException) {throw (WebServerException) ex;}throw new WebServerException("Unable to start embedded Tomcat connectors", ex);}}
	void deferredLoadOnStartup() throws LifecycleException {doWithThreadContextClassLoader(getLoader().getClassLoader(),() -> getLoadOnStartupWrappers(findChildren()).forEach(this::load));}

9、应用上下文关闭时会调用tomcat的关闭

在refreshContext中注册一个关闭的钩子函数,而钩子函数可以完成关闭的功能

ServletWebServerApplicationContext

	@Overrideprotected void onClose() {super.onClose();stopAndReleaseWebServer();}
	private void stopAndReleaseWebServer() {WebServer webServer = this.webServer;if (webServer != null) {try {webServer.stop();this.webServer = null;}catch (Exception ex) {throw new IllegalStateException(ex);}}}

TomcatWebServer

@Overridepublic void stop() throws WebServerException {synchronized (this.monitor) {boolean wasStarted = this.started;try {this.started = false;try {stopTomcat();this.tomcat.destroy();}catch (LifecycleException ex) {// swallow and continue}}catch (Exception ex) {throw new WebServerException("Unable to stop embedded Tomcat", ex);}finally {if (wasStarted) {containerCounter.decrementAndGet();}}}}

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

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

相关文章

SQL SERVER的安装

目录 1.百度SQL SERVER找到图下的所显示的&#xff0c;点击进去 2.找到图下红色框起来的&#xff0c;点击立即下载​ 3.下载好之后点开&#xff0c;选择下载介质 4.SQLSERVER下载成功之后选择打开文件夹​ 6.双击后缀名是.iso的镜像文件 7.双击setup.exe进行安装​ 8.安…

BTI性能开销权衡及优化措施

BTI分支目标识别精讲与实践系列 思考 1、什么是代码重用攻击&#xff1f;什么是ROP攻击&#xff1f;区别与联系&#xff1f; 2、什么是JOP攻击&#xff1f;间接分支跳转指令&#xff1f; 3、JOP攻击的缓解技术&#xff1f;控制流完整性保护&#xff1f; 4、BTI下的JOP如何…

vue:如何通过两个点的经纬度进行距离的计算(很简单)

首先假设从api获取到了自己的纬经度和别人的纬经度 首先有一个概念需要说一下 地球半径 由于地球不是一个完美的球体&#xff0c;所以并不能用一个特别准确的值来表示地球的实际半径&#xff0c;不过由于地球的形状很接近球体&#xff0c;用[6357km] 到 [6378km]的范围值可以…

免费HTTPS证书获取攻略

申请SSL证书可简化为以下三个步骤&#xff1a; 第一步&#xff1a;选择证书类型与提供商 - 确定网站需求&#xff0c;选择合适的SSL证书类型&#xff08;如DV、OV、EV&#xff09;。 - 选取信誉良好的证书颁发机构&#xff08;CA&#xff09;。 永久免费SSL证书_永久免费htt…

腾讯云优惠券领取及使用教程详解

腾讯云作为国内领先的云服务提供商&#xff0c;以其稳定可靠、性能卓越的服务赢得了广大用户的青睐。为了回馈用户&#xff0c;腾讯云经常推出各种优惠活动&#xff0c;其中优惠券就是非常受欢迎的一种。本文将详细介绍腾讯云优惠券的领取和使用方法&#xff0c;帮助大家更好地…

多维 HighCharts

1&#xff1a;showHighChart.html <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><!-- js脚本都是官方的,后两个是highchart脚本 --><script type"text/javascript" src"jquery1.7.1.mi…

Linux用户及用户组管理命令

Linux操作系统是一种基于UNIX的多用户、多任务的操作系统。在Linux系统中&#xff0c;用户和用户组的管理是非常重要的&#xff0c;因为它关系到系统安全和多用户环境下的资源共享。本文将详细介绍Linux中用户和用户组管理的相关命令&#xff0c;帮助用户更好地理解和管理Linux…

MoonBit 2024 Qcon 北京精彩回顾速览

2024年4月11日至13日&#xff0c;QCon 全球软件开发大会暨智能软件开发生态展在北京国测国际会议会展中心举办。本次 QCon 大会汇集了100技术大咖&#xff0c;通过1场主论坛、近30分论坛以及5场高端闭门交流和多场闪电演讲等多样化的活动形式&#xff0c;促进了与会者的深入交流…

解决:IDEA编译报错,自动切换JDK编译版本

一、IDEA切换JDK版本 要想在IDEA中完成对JDK版本的切换有多个地方需要我们进行修改 File | Settings | Build, Execution, Deployment | Compiler | Java Compiler File->ProjectStruct->platform settings->SDKS File->ProjectStruct->projectSettings->…

JavaScript函数式编程

函数式编程 课程介绍 为什么要学习函数编程以及什么是函数式编程函数式编程的特性(纯函数、柯里化、函数组合等)函数式编程的应用场景函数式编程库Lodash 为什么要学习函数式编程 函数式编程是非常古老的一个概念&#xff0c;早于第一台计算机的诞生&#xff0c; 函数式编程…

500以内的不入耳运动耳机推荐,首推五大业内顶级优品

不入耳式运动耳机因其独特的佩戴方式和设计&#xff0c;能够在运动过程中保持对周围环境的警觉&#xff0c;避免因音乐沉浸而忽视潜在的安全隐患&#xff0c;同时它们还能有效减少对耳道的压迫&#xff0c;让运动更加舒适自在&#xff0c;接下来&#xff0c;就让我为大家推荐一…

智能助手大比拼!5款热门思维导图软件细致评估!

思维导图是一种创造性的方法&#xff0c;集思广益&#xff0c;寻找不同想法之间的联系。如果你做得好&#xff0c;你可以为难题提出新的想法和解决方案&#xff0c;总结一篇文章或演示稿&#xff0c;让你的想法井然有序。在数字时代&#xff0c;纸质思维导图存在不能随意更改、…

arm64-v8a、armeabi-v7a、x86、x86_64

当我们去GitHub下载应用的时候是不是经常很懵逼&#xff0c;就像下图一样&#xff0c;粗看一下如此多安装包到底要选择下载哪个且每种安装包到底有哪差别&#xff1f;毕竟因为自己一无所知&#xff0c;有时便随意下载一个后&#xff0c;安装时却报『此版本与你的系统不兼容』的…

Python的pytest框架(1)--基本概念、入门

按基础到进阶的顺序&#xff0c;学习Python的pytest框架&#xff0c;本篇文章先讲一讲pytest的基本概念、入门使用规则。 目录 一、pytest基础知识 1、安装 2、pytest框架主要做了什么工作 二、pytest的规则约定、运行方式以及参数详解 1、编写测试用例 模块&#xff08…

Oracle 19c RAC 补丁升级 补丁回退

补丁升级流程 补丁升级 停止集群备份家目录 两节点分别操作 cd /u01/app/19.3.0/grid/bin/ crsctl stop crs tar -zcvf /u01/app.tar.gz /u01/app /u01/app/19.0.0/grid/bin/crsctl start crs 两节点OPatch替换 --- 表示 root 用户&#xff0c;$ 表示 Oracle 用户提示符&#…

负荷预测 | Matlab基于TCN-GRU-Attention单变量时间序列多步预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于TCN-GRU-Attention单变量时间序列多步预测&#xff1b; 2.单变量时间序列数据集&#xff0c;采用前12个时刻预测未来96个时刻的数据&#xff1b; 3.excel数据方便替换&#xff0c;运行环境matlab2023及以…

无人棋牌室软硬件方案

先决思考 软件这一套确实是做一套下来&#xff0c;可以无限复制卖出&#xff0c;这个雀氏是一本万利的买卖。 现在肯定是有成套的方案&#xff0c;值不值得重做&#xff1f;为什么要重做&#xff1f; 你想达到什么效果&#xff1f;还是需要细聊的。 做这个东西难度不高&…

DNF手游攻略:萌新入坑大全!

玩DNF手游国服已经正式定档&#xff0c;离上线已经越来越近了&#xff0c;很多小伙伴对于装备打造以及附魔还不是特别了解。如果你还不知道装备要怎么附魔&#xff0c;不要担心&#xff0c;本篇攻略将为你全面解析全职业过渡和毕业附魔推荐。 ​ 一、物理职业附魔推荐 1. 武器…

1688推出跨境业务,用API自动对接商品货源

2023年底&#xff0c;出海圈迎来一则重磅消息&#xff1a;1688正式进军海外市场。这一决策引发了众多卖家的关注与疑惑&#xff0c;为何1688会在这个时候推出跨境版呢&#xff1f; 事实上&#xff0c;1688早已涉足跨境业务&#xff0c;拥有“跨境专供”板块&#xff0c;成为众…

【C++学习】C++IO流

这里写目录标题 &#x1f680;C语言的输入与输出&#x1f680;什么是流&#x1f680;CIO流&#x1f680;C标准IO流&#x1f680;C文件IO流 &#x1f680;C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取…