最新要闻
- 环球观热点:三月三拜轩辕 | 全球华人同根同源——强月新
- 世界今亮点!雷军:小米13 Ultra将摆脱手机成片的“塑料味”
- 头条:淄博老板娘为赶高铁小伙1V1烤串!完美诠释“好客山东”
- 天天观察:苹果智能戒指专利首曝:VR场景交互只需动下手指
- 当前速读:1TB仅529元!致态TiPlus 7100固态硬盘新史低 选它还是选三星?
- 全球视讯!机构一致唱多金价 贵金属板块表现亮眼
- 每日视点!第八批国家组织药品集采中选结果公布
- 天天新资讯:公开叫停ChatGPT的马斯克暗渡陈仓玩得6 网友:他是懂中国兵法的
- 越野也不费油!坦克300 PHEV实车亮相:动力大增
- 实测RTX 4090平均20帧!《赛博朋克2077》成新一代“显卡危机”
- 每公里造价2.1亿!中国最贵高速公路预计2025年建成
- 全球观热点:被国产车打怕韩系车拼了 起亚新SUV赛图斯预售:仅9.19万元起
- 【独家】昆明部分店外增设“一米线”:商户可在线内摆摊
- 今日精选:比五菱MINI EV还便宜?奇瑞“棒棒糖”亮相:油门到底时速能到100km
- 重点聚焦!小米成中国市场周销量安卓第一!高端旗舰小米13 Ultra本月发布
- 焦点速讯:你达标了吗?国内人均存款增加1.2万:人民币存款继续增长
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
全球观天下!公司入职一个阿里大佬,把 Spring Boot 系统启动时间从 7 分钟降到了 40 秒!
作者:Debugger 链接:https://juejin.cn/post/7181342523728592955
(资料图片仅供参考)
0 背景
公司 SpringBoot 项目在日常开发过程中发现服务启动过程异常缓慢,常常需要6-7分钟才能暴露端口,严重降低开发效率。通过 SpringBoot 的 SpringApplicationRunListener
、BeanPostProcessor
原理和源码调试等手段排查发现,在 Bean 扫描和 Bean 注入这个两个阶段有很大的性能瓶颈。
通过 JavaConfig 注册 Bean, 减少 SpringBoot 的扫描路径,同时基于 Springboot 自动配置原理对第三方依赖优化改造,将服务本地启动时间从7min 降至40s 左右的过程。 本文会涉及以下知识点:
- 基于 SpringApplicationRunListener 原理观察 SpringBoot 启动 run 方法;
- 基于 BeanPostProcessor 原理监控 Bean 注入耗时;
- SpringBoot Cache 自动化配置原理;
- SpringBoot 自动化配置原理及 starter 改造;
1 耗时问题排查
SpringBoot 服务启动耗时排查,目前有2个思路:
- 排查 SpringBoot 服务的启动过程;
- 排查 Bean 的初始化耗时;
Spring Boot 基础就不介绍了,推荐看这个免费教程:
https://github.com/javastacks/spring-boot-best-practice
1.1 观察 SpringBoot 启动 run 方法
该项目使用基于 SpringBoot 改造的内部微服务组件 XxBoot 作为服务端实现,其启动流程与 SpringBoot 类似,分为 ApplicationContext
构造和 ApplicationContext
启动两部分,即通过构造函数实例化 ApplicationContext
对象,并调用其 run
方法启动服务:
public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}
ApplicationContext
对象构造过程,主要做了自定义 Banner 设置、应用类型推断、配置源设置等工作,不做特殊扩展的话,大部分项目都是差不多的,不太可能引起耗时问题。通过在 run
方法中打断点,启动后很快就运行到断点位置,也能验证这一点。
接下就是重点排查 run
方法的启动过程中有哪些性能瓶颈?SpringBoot 的启动过程非常复杂,庆幸的是 SpringBoot 本身提供的一些机制,将 SpringBoot 的启动过程划分了多个阶段,这个阶段划分的过程就体现在 SpringApplicationRunListener
接口中,该接口将 ApplicationContext
对象的 run
方法划分成不同的阶段:
public interface SpringApplicationRunListener { // run 方法第一次被执行时调用,早期初始化工作 void starting(); // environment 创建后,ApplicationContext 创建前 void environmentPrepared(ConfigurableEnvironment environment); // ApplicationContext 实例创建,部分属性设置了 void contextPrepared(ConfigurableApplicationContext context); // ApplicationContext 加载后,refresh 前 void contextLoaded(ConfigurableApplicationContext context); // refresh 后 void started(ConfigurableApplicationContext context); // 所有初始化完成后,run 结束前 void running(ConfigurableApplicationContext context); // 初始化失败后 void failed(ConfigurableApplicationContext context, Throwable exception);}
目前,SpringBoot 中自带的 SpringApplicationRunListener
接口只有一个实现类:EventPublishingRunListener
,该实现类作用:通过观察者模式的事件机制,在 run
方法的不同阶段触发 Event
事件,ApplicationListener
的实现类们通过监听不同的 Event
事件对象触发不同的业务处理逻辑。
通过自定义实现
ApplicationListener
实现类,可以在 SpringBoot 启动的不同阶段,实现一定的处理,可见SpringApplicationRunListener
接口给SpringBoot
带来了扩展性。
这里我们不必深究实现类 EventPublishingRunListener
的功能,但是可以通过 SpringApplicationRunListener
原理,添加一个自定义的实现类,在不同阶段结束时打印下当前时间,通过计算不同阶段的运行时间,就能大体定位哪些阶段耗时比较高,然后重点排查这些阶段的代码。
先看下 SpringApplicationRunListener
的实现原理,其划分不同阶段的逻辑体现在 ApplicationContext
的 run
方法中:
public ConfigurableApplicationContext run(String... args) { ... // 加载所有 SpringApplicationRunListener 的实现类 SpringApplicationRunListeners listeners = getRunListeners(args); // 调用了 starting listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 调用了 environmentPrepared ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 内部调用了 contextPrepared、contextLoaded prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // 调用了 started listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { // 内部调用了 failed handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { // 调用了 running listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;}
run
方法中 getRunListeners(args)
通过 SpringFactoriesLoader
加载 classpath
下 META-INF/spring.factotries
中配置的所有 SpringApplicationRunListener
的实现类,通过反射实例化后,存到局部变量 listeners
中,其类型为 SpringApplicationRunListeners
;然后在 run
方法不同阶段通过调用 listeners
的不同阶段方法来触发 SpringApplicationRunListener
所有实现类的阶段方法调用。
因此,只要编写一个 SpringApplicationRunListener
的自定义实现类,在实现接口不同阶段方法时,打印当前时间;并在 META-INF/spring.factotries
中配置该类后,该类也会实例化,存到 listeners
中;在不同阶段结束时打印结束时间,以此来评估不同阶段的执行耗时。
在项目中添加实现类 MySpringApplicationRunListener
:
@Slf4jpublic class MySpringApplicationRunListener implements SpringApplicationRunListener { // 这个构造函数不能少,否则反射生成实例会报错 public MySpringApplicationRunListener(SpringApplication sa, String[] args) { } @Override public void starting() { log.info("starting {}", LocalDateTime.now()); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { log.info("environmentPrepared {}", LocalDateTime.now()); } @Override public void contextPrepared(ConfigurableApplicationContext context) { log.info("contextPrepared {}", LocalDateTime.now()); } @Override public void contextLoaded(ConfigurableApplicationContext context) { log.info("contextLoaded {}", LocalDateTime.now()); } @Override public void started(ConfigurableApplicationContext context) { log.info("started {}", LocalDateTime.now()); } @Override public void running(ConfigurableApplicationContext context) { log.info("running {}", LocalDateTime.now()); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { log.info("failed {}", LocalDateTime.now()); }}
这边 (SpringApplication sa, String[] args) 参数类型的构造函数不能少,因为源码中限定了使用该参数类型的构造函数反射生成实例。
在 resources
文件下的 META-INF/spring.factotries
文件中配置上该类:
# Run Listenersorg.springframework.boot.SpringApplicationRunListener=\com.xxx.ad.diagnostic.tools.api.MySpringApplicationRunListener
run
方法中是通过getSpringFactoriesInstances
方法来获取META-INF/spring.factotries
下配置的SpringApplicationRunListener
的实现类,其底层是依赖SpringFactoriesLoader
来获取配置的类的全限定类名,然后反射生成实例;这种方式在 SpringBoot 用的非常多,如
EnableAutoConfiguration
、ApplicationListener
、ApplicationContextInitializer
等。
重启服务,观察 MySpringApplicationRunListener
的日志输出,发现主要耗时都在 contextLoaded
和 started
两个阶段之间,在这两个阶段之间调用了2个方法:refreshContext
和 afterRefresh
方法,而 refreshContext
底层调用的是 AbstractApplicationContext#refresh
,Spring 初始化 context 的核心方法之一就是这个 refresh
。
至此基本可以断定,高耗时的原因就是在初始化 Spring 的 context,然而这个方法依然十分复杂,好在 refresh 方法也将初始化 Spring 的 context 的过程做了整理,并详细注释了各个步骤的作用:
通过简单调试,很快就定位了高耗时的原因:
- 在
invokeBeanFactoryPostProcessors(beanFactory)
方法中,调用了所有注册的BeanFactory
的后置处理器; - 其中,
ConfigurationClassPostProcessor
这个后置处理器贡献了大部分的耗时; - 查阅相关资料,该后置处理器相当重要,主要负责
@Configuration
、@ComponentScan
、@Import
、@Bean
等注解的解析; - 继续调试发现,主要耗时都花在主配置类的
@ComponentScan
解析上,而且主要耗时还是在解析属性basePackages
;
即项目主配置类上 @SpringBootApplication
注解的 scanBasePackages
属性:
通过该方法 JavaDoc、查看相关代码,大体了解到该过程是在递归扫描、解析 basePackages
所有路径下的 class,对于可作为 Bean 的对象,生成其 BeanDefinition
;如果遇到 @Configuration
注解的配置类,还得递归解析其 @ComponentScan
。 至此,服务启动缓慢的原因就找到了:
- 作为数据平台,我们的服务引用了很多第三方依赖服务,这些依赖往往提供了对应业务的完整功能,所以提供的 jar 包非常大;
- 扫描这些包路径下的 class 非常耗时,很多 class 都不提供 Bean,但还是花时间扫描了;
- 每添加一个服务的依赖,都会线性增加扫描的时间;
弄明白耗时的原因后,我有2个疑问:
- 是否所有的 class 都需要扫描,是否可以只扫描那些提供 Bean 的 class?
- 扫描出来的 Bean 是否都需要?我只接入一个功能,但是注入了所有的 Bean,这似乎不太合理?
1.2 监控 Bean 注入耗时
第二个优化的思路是监控所有 Bean 对象初始化的耗时,即每个 Bean 对象实例化、初始化、注册所花费的时间,有没有特别耗时 Bean 对象?
同样的,我们可以利用 SpringBoot 提供了 BeanPostProcessor
接口来监控 Bean 的注入耗时,BeanPostProcessor
是 Spring 提供的 Bean 初始化前后的 IOC 钩子,用于在 Bean 初始化的前后执行一些自定义的逻辑:
public interface BeanPostProcessor { // 初始化前 default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } // 初始化后 default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }}
对于 BeanPostProcessor
接口的实现类,其前后置处理过程体现在 AbstractAutowireCapableBeanFactory#doCreateBean
,这也是 Spring 中非常重要的一个方法,用于真正实例化 Bean 对象,通过 BeanFactory#getBean
方法一路 Debug 就能找到。在该方法中调用了 initializeBean
方法:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { ... Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 应用所有 BeanPostProcessor 的前置方法 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { // 应用所有 BeanPostProcessor 的后置方法 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean;}
通过 BeanPostProcessor
原理,在前置处理时记录下当前时间,在后置处理时,用当前时间减去前置处理时间,就能知道每个 Bean 的初始化耗时,下面是我的实现:
@Componentpublic class TimeCostBeanPostProcessor implements BeanPostProcessor { private Map costMap = Maps.newConcurrentMap(); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { costMap.put(beanName, System.currentTimeMillis()); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (costMap.containsKey(beanName)) { Long start = costMap.get(beanName); long cost = System.currentTimeMillis() - start; if (cost > 0) { costMap.put(beanName, cost); System.out.println("bean: " + beanName + "\ttime: " + cost); } } return bean; }}
BeanPostProcessor
的逻辑是在 Beanfactory
准备好后处理的,就不需要通过 SpringFactoriesLoader
加载了,直接 @Component
注入即可。
重启服务,通过以上方法排查 Bean 初始化过程,还真的有所发现:
这个 Bean 初始化耗时43s,具体看下这个 Bean 的初始化方法,发现会从数据库查询大量配置元数据,并更新到 Redis 缓存中,所以初始化非常慢:
另外,还发现了一些非项目自身服务的service、controller对象,这些 Bean 来自于第三方依赖:UPM服务,项目中并不需要:
其实,原因上文已经提到:我只接入一个功能,但我注入了该服务路径下所有的 Bean,也就是说,服务里注入其他服务的、对自身无用的 Bean。
Spring Boot 基础就不介绍了,推荐看这个免费教程:
https://github.com/javastacks/spring-boot-best-practice
2 优化方案
2.1 如何解决扫描路径过多?
想到的解决方案比较简单粗暴:
梳理要引入的 Bean,删掉主配置类上扫描路径,使用 JavaConfig 的方式显式手动注入。
以 UPM 的依赖为例,之前的注入方式是,项目依赖其 UpmResourceClient 对象,Pom 已经引用了其 Maven 坐标,并在主配置类上的 scanBasePackages
中添加了其服务路径:"com.xxx.ad.upm",通过扫描整个服务路径下的 class,找到 UpmResourceClient 并注入,因为该类注解了 @Service
,因此会注入到服务的 Spring 上下文中,UpmResourceClient 源码片段及主配置类如下:
使用 JavaConfig 的改造方式是:不再扫描 UPM 的服务路径,而是主动注入。删除"com.xxx.ad.upm",并在服务路径下添加以下配置类:
@Configurationpublic class ThirdPartyBeanConfig { @Bean public UpmResourceClient upmResourceClient() { return new UpmResourceClient(); }}
Tips:如果该 Bean 还依赖其他 Bean,则需要把所依赖的 Bean 都注入; 针对 Bean 依赖情况复杂的场景梳理起来就比较麻烦了,所幸项目用到的服务 Bean 依赖关系都比较简单,一些依赖关系复杂的服务,观察到其路径扫描耗时也不是很高,就不处理了。
同时,通过 JavaConfig 按需注入的方式,就不存在冗余 Bean 的情况了,也有利于降低服务的内存消耗;解决了上面的引入无关的 upmService、upmController 的问题。
2.2 如何解决 Bean 初始化高耗时?
Bean 初始化耗时高,就需要 case by case 地处理了,比如项目中遇到的初始化配置元数据的问题,可以考虑通过将该任务提交到线程池的方式异步处理或者懒加载的方式来解决。
3 新的问题
完成以上优化后,本地启动时间从之前的 7min 左右降低至 40s,效果还是非常显著的。本地自测通过后,便发布到预发进行验证,验证过程中,有同学发现项目接入的 Redis 缓存组件失效了。
该组件接入方式与上文描述的接入方式类似,通过添加扫描服务的根路径"com.xxx.ad.rediscache",注入对应的 Bean 对象;查看该缓存组件项目的源码,发现该路径下有一个 config 类注入了一个缓存管理对象 CacheManager
,其实现类是 RedisCacheManager
:
缓存组件代码片段:
本次优化中,我是通过 每次删除一条扫描路径,启动服务后根据启动日志中 Bean 缺失错误的信息,来逐个梳理、添加依赖的 Bean,保证服务正常启动的方式来改造的,而删除"com.xxx.ad.rediscache"后启动服务并无异常,因此就没有进一步的操作,直接上预发验证了。这就奇怪了,既然不扫描该组件的业务代码根路径,也就没有执行注入该组件中定义的 CacheManager
对象,为啥用到缓存的地方没有报错呢?
尝试在未添加扫描路径的情况下,从 ApplicationContext
中获取 CacheManager
类型的对象看下是否存在?结果发现确实存在 RedisCacheManager
对象:
其实,前面的分析并没有错,删除扫描路径后生成的 RedisCacheManager
并不是缓存组件代码中配置的,而是 SpringBoot 的自动化配置生成的,也就是说该对象并不是我们想要的对象,是不符合预期的,下文介绍其原因。
3.1 SpringBoot 自动化装配,让人防不胜防
查阅 SpringBoot Cache 相关资料,发现 SpringBoot Cache 做了一些自动推断和注入的工作,原来是 SpringBoot 自动化装配的锅呀,接下来就分析下 SpringBoot Cache 原理,明确出现以上问题的原因。
SpringBoot 自动化配置,体现在主配置类上复合注解 @SpringBootApplication
中的@EnableAutoConfiguration
上,该注解开启了 SpringBoot 的自动配置功能。该注解中的@Import(AutoConfigurationImportSelector.class)
通过加载 META-INF/spring.factotries
下配置一系列 *AutoConfiguration 配置类,根据现有条件推断,尽可能地为我们配置需要的 Bean。这些配置类负责各个功能的自动化配置,其中用于 SpringBoot Cache 的自动配置类是 CacheAutoConfiguration
,接下来重点分析这个配置类就行了。
@SpringBootApplication
复合注解中集成了三个非常重要的注解:@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
,其中@EnableAutoConfiguration
就是负责开启自动化配置功能;SpringBoot 中有多
@EnableXXX
的注解,都是用来开启某一方面的功能,其实现原理也是类似的:通过@Import
筛选、导入满足条件的自动化配置类。
可以看到 CacheAutoConfiguration
上有许多注解,重点关注下@Import({CacheConfigurationImportSelector.class})
,CacheConfigurationImportSelector
实现了 ImportSelector
接口,该接口用于动态选择想导入的配置类,这个 CacheConfigurationImportSelector
用来导入不同类型的 Cache 的自动配置类:
通过调试 CacheConfigurationImportSelector
发现,根据 SpringBoot 支持的缓存类型(CacheType),提供了10种 cache 的自动配置类,按优先级排序,最终只有一个生效,而本项目中恰恰就是 RedisCacheConfiguration
,其内部提供的是 RedisCacheManager
,和引入第三方缓存组件一样,所以造成了困惑:
看下 RedisCacheConfiguration
的实现:
这个配置类上有很多条件注解,当这些条件都满足的话,这个自动配置类就会生效,而本项目恰恰都满足,同时项目主配置类上还加上了 @EnableCaching
,开启了缓存功能,即使缓存组件没生效,SpringBoot 也会自动生成一个缓存管理对象;
即:缓存组件服务扫描路径存在的话,缓存组件中的代码生成缓存管理对象,@ConditionalOnMissingBean(CacheManager.class)
失效;扫描路径不存在的话,SpringBoot 通过推断,自动生成一个缓存管理对象。
这个也很好验证,在 RedisCacheConfiguration
中打断点,不删除扫描路径是走不到这边的SpringBoot 自动装配过程的(缓存组件显式生成过了),删除了扫描路径是能走到的(SpringBoot 自动生成)。
上文多次提到@Import,这是 SpringBoot 中重要注解,主要有以下作用:1、导入
@Configuration
注解的类;2、导入实现了ImportSelector
或ImportBeanDefinitionRegistrar
的类;3、导入普通的 POJO。
3.2 使用 starter 机制,开箱即用
了解缓存失效的原因后,就有解决的办法了,因为是自己团队的组件,就没必要通过 JavaConfig 显式手动导入的方式改造,而是通过 SpringBoot 的 starter 机制,优化下缓存组件的实现,可以做到自动注入、开箱即用。
只要改造下缓存组件的代码,在 resources
文件中添加一个 META-INF/spring.factotries
文件,在下面配置一个 EnableAutoConfiguration
即可,这样项目在启动时也会扫描到这个 jar 中的 spring.factotries
文件,将 XxxAdCacheConfiguration
配置类自动引入,而不需要扫描"com.xxx.ad.rediscache"整个路径了:
# EnableAutoConfigurationsorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.xxx.ad.rediscache.XxxAdCacheConfiguration
SpringBoot 的
EnableAutoConfiguration
自动配置原理还是比较复杂的,在加载自动配置类前还要先加载自动配置的元数据,对所有自动配置类做有效性筛选,具体可查阅 EnableAutoConfiguration 相关代码;
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!
关键词:
-
全球观天下!公司入职一个阿里大佬,把 Spring Boot 系统启动时间从 7 分钟降到了 40 秒!
作者:Debugger链接:https: juejin cn post 71813425237285929550背景公司SpringBoot项目在日常...
来源: 每日报道:数据开发提效有秘诀!离线开发BatchWorks 六大典型场景拆解
全球观天下!公司入职一个阿里大佬,把 Spring Boot 系统启动时间从 7 分钟降到了 40 秒!
HTML中的pre-load 和 pre-fetch
全球最资讯丨京准GPS北斗卫星网络授时服务器助力高速智慧交通
环球观热点:三月三拜轩辕 | 全球华人同根同源——强月新
世界今亮点!雷军:小米13 Ultra将摆脱手机成片的“塑料味”
头条:淄博老板娘为赶高铁小伙1V1烤串!完美诠释“好客山东”
天天观察:苹果智能戒指专利首曝:VR场景交互只需动下手指
当前速读:1TB仅529元!致态TiPlus 7100固态硬盘新史低 选它还是选三星?
全球视讯!机构一致唱多金价 贵金属板块表现亮眼
世界热议:全网最详细中英文ChatGPT-GPT-4示例文档-场景问题智能生成从0到1快速入门——官网推荐的48种最佳应用场景(附python/node.js/
世界速讯:记一次kvm虚机mysql数据库磁盘扩容操作步骤及其问题小坑
每日视点!第八批国家组织药品集采中选结果公布
天天新资讯:公开叫停ChatGPT的马斯克暗渡陈仓玩得6 网友:他是懂中国兵法的
越野也不费油!坦克300 PHEV实车亮相:动力大增
实测RTX 4090平均20帧!《赛博朋克2077》成新一代“显卡危机”
每公里造价2.1亿!中国最贵高速公路预计2025年建成
全球观热点:被国产车打怕韩系车拼了 起亚新SUV赛图斯预售:仅9.19万元起
【独家】昆明部分店外增设“一米线”:商户可在线内摆摊
环球今日报丨中国国债期货收益指数今日发布
今日精选:比五菱MINI EV还便宜?奇瑞“棒棒糖”亮相:油门到底时速能到100km
重点聚焦!小米成中国市场周销量安卓第一!高端旗舰小米13 Ultra本月发布
焦点速讯:你达标了吗?国内人均存款增加1.2万:人民币存款继续增长
天天视点!初代跑男重聚引热议:邓超李晨王宝强等合照引回忆杀
32岁婆孙恋爆红网络!日本61岁奶奶交往29岁男子 网友感叹相貌绝了
新动态:让你脑洞大开的MySQL优化技巧
多地超前谋划专项债项目 “早发快用”加力稳投资
微速讯:女生在冬季剪什么发型好看呢?
天天热头条丨行业首款卫星通信5G手机!中兴Axon 50 Ultra吴京代言图公布:背部外观揭晓
【独家】韩国4月前10天芯片出口下降近40%:1TB SSD三星降599没人买 国产崛起
环球热点评!中老铁路国际旅客列车明日开通:万象到昆明南二等座470元
全球信息:大疆8K航拍无人机明天发 悟3真机亮相:个头感受下
天天观察:97版三兄弟代言!《天龙八部2:飞龙战天》4月14日公测:完美世界发行
每日看点!女子没开会员追剧需看50多分钟广告:遇到了BUG是个例
环球热议:酒也分贵贱? 专家:洋酒就没文化含量 中国酒文化才叫源远流
世界微动态丨图赫尔:0比3输球的结果很苦涩,但今天我爱上了我的球队
环球观点:CSS杂谈——flex布局里面的auto到底多长
光栅化算法-中点画圆算法
Win无敌二十多年后 微软又抓到了AI风口:印度裔CEO立大功
环球关注:“小号Mate 50 Pro”!华为畅享60X来了:骁龙680、又一技术行业首发
全球观点:这株极品牡丹居然有“带刀侍卫”!24小时守护
当前关注:地表能效第一!锐龙9 7950X3D深入测试:i9-13900K无言以对
焦点讯息:SSD散热器疯了:比显卡都要高!还有暴力风扇
最新快讯!2023年北京市积分落户申报时间安排一览表
世界资讯:微信小程序:接手项目,修bug
焦点简讯:选择大城市逐梦还是小城市安稳
世界快消息!电动自行车电池爆炸由谁赔偿?律师:销售商也有责任
每日热闻!会“跳舞”的汽车你见过吗?比亚迪云辇系统再次引领行业
全球热议:韩国26岁女星被发现在家中去世:新剧拍摄中 死因未透露
Python中struct 模块的使用教程
焦点观察:once do, do it well
全球资讯:华为小米vivo一加重磅新机汇总!接下来10天有的看了
世界球精选!终于不缺货了!小米13 Ultra订单增加50%:不愁买不到
视讯!这才是青春_山东聊城市中考满分作文 中考满分作文:静待一场幸
SpringApplication详解
焦点讯息:OpenAI Translator | 基于ChatGPT API全局翻译润色解析及ORC上传图像翻译工具
当前报道:锁屏面试题百日百刷-kafk篇(一)
环球热议:“三轮跑车”尬舞!又被比亚迪装到了 国产车控黑科技登场
每日快播:安“踏”李宁
天天速讯:月薪2万女高管不服从工作安排被炒获赔98万:网友力挺
出水芙蓉?真人版《小美人鱼》电影照更新:黑小美人鱼特写来了
Turtlebot3仿真代码学习笔记
世界热点!.NET 个人博客系统
五金店卖什么卖得最好_五金店卖什么
天天新消息丨腾讯国漫手游《狐妖小红娘》宣布停运 6月16日关闭服务器
电动版帕萨特这模样?大众ID.7申报图亮相
视焦点讯!3599元起 铭凡推出NPB7迷你主机:i7-13700H、双雷电4
天天速读:丰田醒了 可睁开眼发现:世界已经变了!
南方的网友注意了 沙尘已跨过长江!北京影响已到末期
day07-OpenFeign-服务调用
看热讯:stm32 使用多串口通信调试总结
速讯:记录-css实现交融文字效果
环球最新:阿里云 - 连接不同VPC方案
天天时讯:紫薯泥怎么做好吃-紫薯泥
【天天新视野】本周发布!徕卡M11 Monochrom详细规格曝光
旗舰功能全下放!华为nova 11 Ultra曝光:卫星通信、可变光圈都支持
焦点速看:米粉催雷军发小米13 Ultra:你敢发我就敢买
电脑里破解软件又少一款!Win11原生录屏免费平替了:新增快捷键
绝对忠诚?超八成iPhone用户丢机后秒换新款iPhone
全球快讯:从spring boot泄露到接管云服务器平台
ThreadPoolExecutor源码学习
图文介绍 Windows 系统下打包上传 IOS APP 流程
【Visual Leak Detector】使用注意事项
一个TCP 连接可以发送多少个HTTP请求?
为什么打不开空间_腾讯官方的解决方法
《终结者》T-1000成真!科学家研制出可固液转变机器人 形态科幻
当前观点:或命名为“高山” 魏牌全新插混MPV申报:红旗HQ9有话要说
印度版大G来啦!4排10座轴距超3米 马力只有90匹
坚持元宇宙不动摇:Meta百万美元年薪招聘VR程序员
环球观热点:女子下班回工作消息获赔加班费引热议!调查称仅1成多人拒绝下班秒回工作
全球即时:卢拉发推预告:“我将启程前往中国”,期望加强巴中贸易伙伴关系
世界简讯:【一行代码秒上云】Serverless六步构建全栈网站
科创人·中建三局一公司尹奎:数字化变革能创造全新行业,其意义超越形式、范式创新
当前关注:CAD二次开发,安装程序中写注册表
滚动:低代码开发,是稳扎稳打还是饮鸩止渴?
快报:【金融街发布】人民银行:3月货币供应量M2同比增长12.7% 比上月回落0.2个百分点
能打比亚迪宋PLUS DM-i 哈弗枭龙MAX开订:用上Hi4电四驱
焦点关注:两大巨头“世纪大和解”!腾讯视频官方入驻抖音:昵称“鹅家”
天天日报丨13代标压高能轻薄本!华硕无畏15i 2023到手5699元:OLED好屏值得拥有
又来一份10年协议!微软与英国EE达成云游戏合作