SpringSecurity6从入门到上天系列第六篇:解决这个问题为什么在引入SpringSecurity之后所有的请求都需要先做登录认证才可以进行访问呢
文章目录
问题引入
1:问题阐述
2:问题分析
一:从SpringBoot的自动装配
1:@SpringBootApplication介绍
2:自动装配的核心方法
3:核心方法的调用路径
4:SpringSecurity核心配置
5:SpringBoot...Configuration详解
6:总结一下
大神链接:作者有幸结识技术大神孙哥为好友,获益匪浅。现在把孙哥视频分享给大家。
孙哥链接:孙哥个人主页
作者简介:一个颜值99分,只比孙哥差一点的程序员
本专栏简介:话不多说,让我们一起干翻SpringSecurity6本文章简介:话不多说,让我们讲清楚SpringSecurity6中为什么在引入SpringSecurity之后所有的请求都需要先做登录认证才可以进行访问呢
问题引入
1:问题阐述
为什么在引入SpringSecurity之后所有的请求都需要先做登录认证才可以进行访问呢?
2:问题分析
分析清楚这个问题之前,我们先从自动装配开始研究。
一:从SpringBoot的自动装配
1:@SpringBootApplication介绍
这个注解的作用就是标志这个类是SpringBootApplication的启动类。
@SpringBootApplication
public class BigtreeApplication {public static void main(String[] args) {SpringApplication.run(BigtreeApplication.class, args);}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class,attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class,attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor( annotation = Configuration.class)boolean proxyBeanMethods() default true;
}
这个注解是一个复合注解@SpringBootConfiguration这个注解的作用带表了这当前这个类是一个SpringBoot配置类,自动交给SpringIOC容器进行管理。
第二个注解是ComponentScan这个注解的作用是定义Spring的扫描路径的。通畅对应我们自己定义的组件例如:Controller,Service,Dao这些组件。
第三个注解是:@EnableAutoConfiguration这个注解是SpringBoot自动装配的关键注解,这个注解包含两个核心注解
第一个注解是:@Import({AutoConfigurationImportSelector.class})这个注解的作用就是在导入当前类的同时顺便导入AutoConfigurationImportSelector这个类也加载进来。
2:自动装配的核心方法
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).getCandidates();Assert.notEmpty(configurations,"No auto configuration classes found in "+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;}
org.springframework.boot.context.annotation.ImportCandidates
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {Assert.notNull(annotation, "'annotation' must not be null");ClassLoader classLoaderToUse = decideClassloader(classLoader);String location = String.format(LOCATION, annotation.getName());Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);List<String> importCandidates = new ArrayList<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();importCandidates.addAll(readCandidateConfigurations(url));}return new ImportCandidates(importCandidates);}
此方法执行完毕的返回值:

这个配置在:spring-boot-autoconfigure-3.0.12.jar包下!\META-INF\spring\包下的
org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中,这里边就是Spring中的各种需要自动装配的组件。其中就有很多的配置组件。

3:核心方法的调用路径
"main@1" prio=5 tid=0x1 nid=NA runnablejava.lang.Thread.State: RUNNABLEat org.springframework.boot.context.annotation.ImportCandidates.load(ImportCandidates.java:90)at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.getCandidateConfigurations(AutoConfigurationImportSelector.java:180)at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.getAutoConfigurationEntry(AutoConfigurationImportSelector.java:126)at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup.process(AutoConfigurationImportSelector.java:430)at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGrouping.getImports(ConfigurationClassParser.java:796)at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler.processGroupImports(ConfigurationClassParser.java:726)at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorHandler.process(ConfigurationClassParser.java:697)at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:182)at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:415)at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:287)at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344)at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:115)at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:779)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:597)- locked <0x12a7> (a java.lang.Object)at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:733)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:435)at org.springframework.boot.SpringApplication.run(SpringApplication.java:311)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290)at com.dashu.AlibabaApplication.main(AlibabaApplication.java:10)
这个调用路径是怎么获得的?方法很简单,只需要在这个核心方法的中间打上一个断点。
然后我们启动main方法,等线程执行过这个方法的断点。然后我们在idea上玩一个骚操作就可以了。

然后,我们在debug区域,找到最上层一个方法,然后我们右键Exports Thread即可。

4:SpringSecurity核心配置
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
@AutoConfiguration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {@Bean@ConditionalOnMissingBean(AuthenticationEventPublisher.class)public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {return new DefaultAuthenticationEventPublisher(publisher);}}
然后,就会加载这两个组件:SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class 尤其是第一个。
5:SpringBoot...Configuration详解
这里边只有一个@Bean注解,最终会创建一个对象:SecurityFilterChain,为什么最终引入了SpringSecurity依赖之后就会所有的请求都会被拦截答案就在这个方法里边。
@Bean@Order(SecurityProperties.BASIC_AUTH_ORDER)SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {//任意Http请求都会被认证拦截http.authorizeHttpRequests().anyRequest().authenticated();//认证的时候支持form表单认证http.formLogin();//http的basic认证http.httpBasic();return http.build();}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {@Configuration(proxyBeanMethods = false)@ConditionalOnDefaultWebSecuritystatic class SecurityFilterChainConfiguration {@Bean@Order(SecurityProperties.BASIC_AUTH_ORDER)SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests().anyRequest().authenticated();http.formLogin();http.httpBasic();return http.build();}}@Configuration(proxyBeanMethods = false)@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)@ConditionalOnClass(EnableWebSecurity.class)@EnableWebSecuritystatic class WebSecurityEnablerConfiguration {}}
6:总结一下
基于SpringBoot的自动装配,由于SpringSecurity的装配配置在SpringBoot配置环境中,所以它默认会被加载,加载完毕之后defaultSecurityFilterChain被调用,SecurityFilterChain对象被创创建。所有的方法都会被被鉴权。
具体的方法调用路径或者叫配置路径是这样的:首先是三个核心的注解:
@SpringBootApplication-> @EnableAutoConfiguration>@Import(AutoConfigurationImportSelector)
这样的代码就会基于下面这个调用路径:
"main@1" prio=5 tid=0x1 nid=NA runnablejava.lang.Thread.State: RUNNABLEat org.springframework.boot.context.annotation.ImportCandidates.load(ImportCandidates.java:90)at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.getCandidateConfigurations(AutoConfigurationImportSelector.java:180)at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.getAutoConfigurationEntry(AutoConfigurationImportSelector.java:126)at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup.process(AutoConfigurationImportSelector.java:430)at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGrouping.getImports(ConfigurationClassParser.java:796)at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler.processGroupImports(ConfigurationClassParser.java:726)at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorHandler.process(ConfigurationClassParser.java:697)at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:182)at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:415)at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:287)at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344)at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:115)at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:779)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:597)- locked <0x12a7> (a java.lang.Object)at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:733)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:435)at org.springframework.boot.SpringApplication.run(SpringApplication.java:311)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290)at com.dashu.AlibabaApplication.main(AlibabaApplication.java:10)
调用到这个方法里边:AutoConfigurationImportSelector#getCandidateConfigurations最后查到核心组件的配置文件。这样加载到SpringSecurity的核心文件。最终调用到上边的方法,导致所有的方法都得进行登录认证。
二:默认认证方式的条件限制
1:@ConditionalOnDefaultWebSecurity
此注解显示了要想使用下面SpringSecurity的默认认证方式是有条件的。
也就是说,并不是所有的Http请求都必须走默认认证。进入这个注解:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultWebSecurityCondition.class)
public @interface ConditionalOnDefaultWebSecurity {}
这个注解上边还有一个注解:@Conditional(DefaultWebSecurityCondition.class)
class DefaultWebSecurityCondition extends AllNestedConditions {DefaultWebSecurityCondition() {super(ConfigurationPhase.REGISTER_BEAN);}@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })static class Classes {}@ConditionalOnMissingBean({ SecurityFilterChain.class })static class Beans {}}
在这个类当中我们定义了两种鉴权规则。第一种是基于Class,他是基于类作用,也就是说当前在classpath下如果有上述两个class的话,就可以走默认的认证方式。在SpringSecurity中肯定是有的,这也就是在引入SpringSecurity依赖之后就会走默认的配置。
第二种是基于丢失Bean的情况,如果丢失了,那么我们可以走默认的认证规则了。也就是说,如果没有了SecurityFilterChain这个对象的话,那么就不在使用默认的认证规则了。
相关文章:
SpringSecurity6从入门到上天系列第六篇:解决这个问题为什么在引入SpringSecurity之后所有的请求都需要先做登录认证才可以进行访问呢
文章目录 问题引入 1:问题阐述 2:问题分析 一:从SpringBoot的自动装配 1:SpringBootApplication介绍 2:自动装配的核心方法 3:核心方法的调用路径 4:SpringSecurity核心配置 5…...
Mac M3 芯片安装 Nginx
Mac M3 芯片安装 Nginx 一、使用 brew 安装 未安装 brew 的可以参考 【Mac 安装 Homebrew】 或者 【Mac M2/M3 芯片环境配置以及常用软件安装-前端】 二、查看 nginx 信息 通过命令行查看 brew info nginx可以看到 nginx 还未在本地安装,显示 Not installed …...
浏览器怎么更新?4个高效设置方法!
“我在使用浏览器时,有时候会提示说浏览器版本太低,需要更新后才能使用。有什么方法可以更新浏览器呢?快给我支支招吧!” 在快速发展的科技时代,浏览器更新是确保网络安全和性能优化的关键步骤。如果浏览器的版本太低&…...
settings.json配置
settings.json配置 {"editor.tabSize": 2,"git.ignoreWindowsGit27Warning": true,"workbench.editor.untitled.hint": "hidden","security.workspace.trust.untrustedFiles": "open","[vue]": {"…...
Mysql中的JDBC编程
JDBC编程 1.JDBC的数据库编程2.JDBC工作原理3.JDBC使用3.1JDBC开发案例3.2JDBC使用步骤总结 4.JDBC API4.1数据库连接Connection4.2 Statement对象4.3 ResultSet对象4.4 释放 5.Java代码操作数据库 1.JDBC的数据库编程 JDBC,即Java Database Connectivity࿰…...
媒体行业的3D建模:在影视中创造特效纹理
在线工具推荐: 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 在本文中,我们将探讨 3D 建模在媒体行业中的作用,特别是它在影视特效创作…...
Kafka从安装使用到集成Springboot详细教程
“不积跬步,无以至千里。” 1. 引言 在当今高度互联的技术领域,消息队列成为分布式系统中不可或缺的一部分。Apache Kafka作为一个高性能、持久化、分布式的消息队列系统,备受开发者推崇。这篇文章将从安装到集成Spring的全方位介绍Kafka的使…...
【giszz笔记】产品设计标准流程【4】
(续上回) 我们继续把扩展考虑UX环节的产品打造标准流程,来进行梳理。 一千个人心中有一千个哈姆雷特,本文将日常大家耳熟能详,但是又未必人人心中成体系的产品打造标准流程,进行总结。 考虑了两种项目&a…...
图论16-拓扑排序
文章目录 1 拓扑排序2 拓扑排序的普通实现2.1 算法实现 - 度数为0入队列2.2 拓扑排序中的环检测 3 深度优先遍历的后续遍历3.1 使用环检测类先判断是否有环3.2 调用无向图的深度优先后续遍历方法,进行DFS 1 拓扑排序 对一个有向无环图G进行拓扑排序,是将…...
SecureCRT 9.4.2最新终端SSH工具
SecureCRT是一款终端SSH工具,它提供了类似于Telnet和SSH等协议的远程访问功能。SecureCRT软件特色包括: 支持SSH(SSH1和SSH2)的终端仿真程序,能在Windows下登录UNIX或Linux服务器主机。SecureCRT支持SSH,同…...
基于python+django的美食餐厅点餐订餐网站
运行环境 开发语言:Python python框架:django 软件版本:python3.7 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:PyCharm/vscode 前端框架:vue.js 项目介绍 本论文主要论述了如何使用python语言开发…...
Moka人事:实现无代码开发的API连接,打通电商平台与用户运营系统
无代码开发的API连接:Moka人事的核心优势 Moka人事,是北京希瑞亚斯科技有限公司于2015年推出的一款数据驱动的智能化HR SaaS产品。这款产品的主要优势在于其无需进行API开发即可实现系统的连接和集成,这不仅大大提升了企业的工作效率&#x…...
【Spring】超详细讲解AOP(面向切面编程)
文章目录 1. 前言2. 什么是AOP3. AOP快速入门4. AOP的核心概念5. 切点表达式6. 切点函数7. 通知8. 总结 1. 前言 本文围绕AOP进行讲解,AOP可以做什么,涉及到了哪些注解,以及各个注解运行的时机,以及Around相较于其它注解有什么不同,并且如果要执行目标方法需要怎么做 2. 什么…...
界面组件DevExpress Reporting v23.1亮点 - 全新升级报表查看器
DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表 界面组件DevExpress Reporting v23.1已经发布一段…...
电容容量换算电池容量,以及RTC持续时间计算
依据 公式1:QI*t 公式2:QC*U 其中: Q: 电荷量 (库仑) I: 电流 (安培) t: 时间 (秒) C: 电容量 (法拉…...
【BIM入门实战】高程点无法放置的解决方法
文章目录 一、问题提出二、解决办法1. 检查模型图形样式2. 高程点可以放置的图元一、问题提出 在平面图中添加高程点时有时会遇到无法在楼板等平面构件上放置高程点,应如何设置才能使高程点正常放置? 如下图所示,楼板上无法放置高程点: 二、解决办法 1. 检查模型图形样式…...
CRM系统对科技企业有哪些帮助
随着国家政策的倾斜和5G等相关基础技术的发展,中国人工智能产业在各方的共同推动下进入爆发式增长阶段,市场发展潜力巨大。CRM客户管理系统作为当下最热门的企业应用,同样市场前景广阔。那么,CRM系统对科技企业有哪些帮助…...
用excel计算一个矩阵的转置矩阵
假设我们的原矩阵是一个3*3的矩阵: 125346789 现在求它的转置矩阵: 鼠标点到一个空白的地方,用来存放结果: 插入-》函数: 选择TRANSPOSE,这个就是求转置矩阵的函数: 点击“继续”:…...
WPF 中的 ControlTemplate 和 DataTemplate 有什么区别
在WPF中,ControlTemplate和DataTemplate都是模板,它们都可以用来定义一段可重复使用的XAML标记。然而,它们的用途和应用场景有很大的不同。 ControlTemplate: ControlTemplate是用来定义控件的外观和视觉行为的。每个WPF控件都有…...
3D重建相关
目录 <font colorblue>整个3D重建的过程是怎样的<font colorblue>体素、网格、点云之间的关系是什么<font colorblue>点云中的颜色怎么处理成最终3D模型上的颜色<font colorblue>点云还原的3D模型的颜色怎么处理,点云有颜色数据?…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
云安全与网络安全:核心区别与协同作用解析
在数字化转型的浪潮中,云安全与网络安全作为信息安全的两大支柱,常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异,并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全:聚焦于保…...
flow_controllers
关键点: 流控制器类型: 同步(Sync):发布操作会阻塞,直到数据被确认发送。异步(Async):发布操作非阻塞,数据发送由后台线程处理。纯同步(PureSync…...
