SpringSecurity原理解析(五):HttpSecurity 类处理流程
1、SpringSecurity 在spring boot中与SSM项目中基于配置文件的区别
通过前边的笔记我们可以知道,在传统的SSM项目中 SpringSecurity的使用是基于配置文件
的,然后spring 容器初始化的时候将 SpringSecurity 中的各种标签解析成对应的Bean对象,
SpringSecurity 配置文件如下所示:
<!-- SpringSecurity配置文件 --><!--auto-config:表示自动加载SpringSecurity的配置文件use-expressions:表示使用Spring的EL表达式--><security:http auto-config="true" use-expressions="true"><!--定义匿名访问,跳转到登录页面 --><security:intercept-url pattern="/login.jsp" access="permitAll()"/><!--拦截资源pattern="/**" 拦截所有的资源access="hasAnyRole('ROLE_USER')" 表示只有ROLE_USER 这个角色可以访问资源--><security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')" ></security:intercept-url><!--配置认证(用户登录)信息,覆盖security 默认的登录页面login-page:登录页面地址login-processing-url:登录的请求urldefault-target-url:登录成功皇后的目标地址authentication-failure-url:登录校验失败后的地址--><security:form-login login-page="/login.jsp" login-processing-url="/login" default-target-url="/home.jsp" authentication-failure-url="/error.jsp"/><!--开启csrf校验 --><security:csrf disabled="true"/><!--开启“记住我” 登录用户缓存功能,该功能默认是关闭的,需要手动开启remember-me-parameter 是登录页面配置的 “记住我” 功能的属性名称,如login.jsp 中的 "remember-me"token-validity-seconds 设置 “记住我” 登录的数据保存的超时时间,注意:当前 这种配置只是把“登录数据” 临时保存在页面的Cookie(token) 中,保存在页面中的数据安全性很差,很容故意被盗取;为了解决这个问题 spring security提供了把“记住我” 功能的数据保存到数据库中,需要在 <security:remember-me>中添加配置属性 data-source-ref,并 指定数据源,如:data-source-ref="dataSource"--><security:remember-metoken-validity-seconds="1200"data-source-ref="dataSource"remember-me-parameter="remember-me"/><!--自定义错误页面 --><security:access-denied-handler error-page="error.jsp"/></security:http><!--向IOC容器注入一个bean--><bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="bCryptPasswordEncoder"/><!-- 认证用户信息 --><security:authentication-manager><security:authentication-provider user-service-ref="userServiceImpl"><!--指向自己定义的认证service,在service 中根据登录用户与数据库的数据进行用户认证处理,这样保密性比较好 --><!--<security:user-service >设置一个账号 zhangsan 密码123 {noop} 表示不加密 具有的角色是 ROLE_USER<security:user name="zhangsan" authorities="ROLE_USER" password="{noop}123" ></security:user><security:user name="lisi" authorities="ROLE_USER" password="{noop}123456" ></security:user></security:user-service>--><!--引入用户认证密码加密方式 --><security:password-encoder ref="bCryptPasswordEncoder"></security:password-encoder></security:authentication-provider></security:authentication-manager>
也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息,但是在
SpringBoot项目中,我们慢慢脱离了xml配置文件的方式,通过配置类的方式来配置
SpringSecurity。SpringSecurity配置类需要继承类 WebSecurityConfigurerAdapter,并重写
configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http) 方法;
SpringSecurity 配置类如下所示:
/*** SpringSecurity的配置文件* WebSecurityCofniguration中 @Bean注解 把 FilterChainProxy 注入到了容器中 而且名称为springSecurityFilterChain* 而 FilterChainProxy 对象是通过 WebSecurity 构建的** @EnableWebSecurity*/
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@Autowiredprivate BCryptPasswordEncoder bCryptPasswordEncoder;@Autowiredprivate PersistentTokenRepository persistentTokenRepository;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService)// 数据库认证,绑定需要执行用户认证操作的service.passwordEncoder(bCryptPasswordEncoder); // 设置加密处理的方式//auth.inMemoryAuthentication().withUser("root").password("123")}/*** 容器中注入 BCryptPasswordEncoder* @return*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** HttpSecurity 相当于 SpringSecurity配置文件中 http 标签** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//匿名访问资源.antMatchers("/login.html","/css/**","/img/**")// 配置需要放过的资源,表示前边 antMatchers 配置的资源都需要放过.permitAll().antMatchers("/**")//认证的资源及所具备的权限.hasAnyRole("USER").anyRequest()//表示所有请求都需要认证.authenticated()//需要认证//and() 返回一个HttpSecurity.and()// 配置登录表单相关的信息.formLogin()// 指定自定义的登录页面//todo 注意:// 对于前后端分离的项目,不需要指定跳转的页面,只需要 loginProcessingUrl// 设置请求资源url就行了.loginPage("/login.html") //认证表单相关信息.loginProcessingUrl("/login") // 表单提交的登录地址.defaultSuccessUrl("/home.html")//表示上边与form表单提交得资源都要放过.permitAll().and().rememberMe() // 放开 记住我 的功能.tokenRepository(persistentTokenRepository) // 持久化.and()//csrf设置.csrf().disable();HttpSecurity http1 = http.authorizeRequests()// 配置需要放过的资源.antMatchers("/login.html", "/css/**", "/img/**").permitAll()//表示放过前边 antMatchers 配置得资源.antMatchers("/**").hasAnyRole("USER").anyRequest().authenticated().and();}/*** 向Spring容器中注入 PersistentTokenRepository 对象* @param dataSource* @return*/@Beanpublic PersistentTokenRepository persistentTokenRepository(DataSource dataSource){JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();//绑定数据源tokenRepository.setDataSource(dataSource);return tokenRepository;}public static void main(String[] args) {BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String password = "admin";// 每次都会生成一个随机的salt,同一个明文加密多次编码得到的密文其实都不一样System.out.println(encoder.encode(password));System.out.println(encoder.encode(password));System.out.println(encoder.encode(password));}
}
在SpringSecurity中提供了HttpSecurity等工具类,这里HttpSecurity就等同于我们在配置
文件中定义的<security:http>标签,而。
通过代码结果来看和配置文件的效果是一样的。基于配置文件的方式我们之前分析过,是通过
标签对应的handler来解析处理的,那么HttpSecurity这块是如何处理的呢?
2、HttpSecurity 类的处理过程
2.1、HttpSecurity 类图:
由该类图可以清晰得发现,HttpSecurity 继承了父类 AbstractConfiguredSecurityBuilder
并实现了接口 SecurityBuilder 和 HttpSecurityBuilder
HttpSecurity 类的定义如下:
2.2、SecurityBuilder 接口
SecurityBuilder 定义如下:
public interface SecurityBuilder<O> {//构建 SecurityBuilder 指定泛型类型的对象O build() throws Exception;
}
由接口 SecurityBuilder 的定义可以发现,SecurityBuilder 接口只提供了一个 build() 方法,用
来构建 SecurityBuilder 泛型指定类型的bean对象。
结合 HttpSecurity 类中实现 SecurityBuilder 接口时的泛型是什么,就知道在 HttpSecurity 类
中 SecurityBuilder 是用来创建什么对象,HttpSecurity 定义如下:
由 HttpSecurity 的定义可以发现,在 HttpSecurity 类中 SecurityBuilder 指定的泛型是
DefaultSecurityFilterChain,DefaultSecurityFilterChain 是拦截器链SecurityFilterChain
一个默认实现,所以 DefaultSecurityFilterChain 是一个拦截器链,所以在 HttpSecurity
中,SecurityBuilder 是用来创建拦截器链的。
2.2.1、SecurityBuilder.build() 方法的实现
下面看下 SecurityBuilder.build() 的实现过程,及连接器链的创建过程
SecurityBuilder 的默认实现是类 AbstractSecurityBuilder
SecurityBuilder.build() 方法的实现如下:
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {//CAS类型private AtomicBoolean building = new AtomicBoolean();//返回创建的O的对象private O object;public AbstractSecurityBuilder() {}//创建泛型O的对象public final O build() throws Exception {//基于CAS,保证在整个环境中O只被创建一次if (this.building.compareAndSet(false, true)) {//真正创建泛型O的对象this.object = this.doBuild();return this.object;} else {throw new AlreadyBuiltException("This object has already been built");}}//返回O的对象public final O getObject() {if (!this.building.get()) {throw new IllegalStateException("This object has not been built");} else {return this.object;}}//抽象方法,由子类实现protected abstract O doBuild() throws Exception;
}
由 AbstractSecurityBuilder 的定义可以发现,真正创建泛型O(在 HttpSecurity 中O是
拦截器链 DefaultSecurityFilterChain )的对象是在doBuild 发给发中完成,而doBuild是
一个抽象方法,由子类 AbstractConfiguredSecurityBuilder 实现,
doBuild 方法实现如下:
protected final O doBuild() throws Exception {synchronized(this.configurers) {this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;this.beforeInit();//执行当前类的init方法进行初始化操作this.init();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;this.beforeConfigure();this.configure();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;//获取构建的对象,上面的方法可以先忽略O result = this.performBuild();this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;return result;}}
performBuild() 是一个抽象方法,由AuthenticationManagerBuilder、HttpSecurity、
WebSecurity 三个子类实现,在这里我们应该看 HttpSecurity 中的实现
HttpSecurity.performBuild() 方法的实现如下:
@Overrideprotected DefaultSecurityFilterChain performBuild() {//filters:保存所有的过滤器// 对所有的过滤器做排序this.filters.sort(OrderComparator.INSTANCE);List<Filter> sortedFilters = new ArrayList<>(this.filters.size());for (Filter filter : this.filters) {sortedFilters.add(((OrderedFilter) filter).filter);}// 然后生成 DefaultSecurityFilterChainreturn new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);}
DefaultSecurityFilterChain 构造方法如下:
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {if (!filters.isEmpty()) {logger.info(LogMessage.format("Will not secure %s", requestMatcher));} else {logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));}//绑定请求匹配规则this.requestMatcher = requestMatcher;//绑定过滤器集合this.filters = new ArrayList(filters);}
在HttpSecurity构造方法中绑定了对应的请求匹配器和过滤器集合。
对应的请求匹配器则是 AnyRequestMatcher 匹配所有的请求。当然我们会比较关心
默认的过滤器链中的过滤器是哪来的,这块儿我们继续来分析
2.3、AbstractConfiguredSecurityBuilder
AbstractConfiguredSecurityBuilder 是一个抽象类,也可以看成是 SecurityBuilder 接口的
实现,AbstractConfiguredSecurityBuilder 类图如下所示:
类型 | 功能 |
SecurityBuilder | 声明了build方法 |
AbstractSecurityBuilder | 提供了获取对象的方法以及控制一个对象只能build一次 |
AbstractConfiguredSecurityBuilder | 除了提供对对象细粒度的控制外还扩展了对configurer的操作 |
AbstractConfiguredSecurityBuilder 对应的三个实现类,如下所示:
2.3.1、BuildState
AbstractConfiguredSecurityBuilder 中定义了一个枚举类BuildState,将整个构建过程
分为 5 种状态,也可 以理解为构建过程生命周期的五个阶段,通过这些阶段来管理需
要构建的对象的不同阶段 如下:
private enum BuildState {/*** 还没开始构建*/UNBUILT(0),/*** 构建中*/INITIALIZING(1),/*** 配置中*/CONFIGURING(2),/*** 构建中*/BUILDING(3),/*** 构建完成*/BUILT(4);private final int order;BuildState(int order) {this.order = order;}public boolean isInitializing() {return INITIALIZING.order == this.order;}/*** Determines if the state is CONFIGURING or later* @return*/public boolean isConfigured() {return this.order >= CONFIGURING.order;}}
2.3.2、AbstractConfiguredSecurityBuilder 常见方法
2.3.2.1、add() 方法
add 方法,这相当于是在收集所有的配置类。将所有的 xxxConfigure 收集起来存储到
configurers 中,将来再统一初始化并配置,configurers 本身是一个 LinkedHashMap ,
key 是配置类的 class, value 是一个集合,集合里边放着 xxxConfigure 配置类。当
需要对这些配置类进行集中配置的时候, 会通过 getConfigurers 方法获取配置类,
这个获取过程就是把 LinkedHashMap 中的 value 拿出来, 放到一个集合中返回。
add 方法代码如下:
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {Assert.notNull(configurer, "configurer cannot be null");//configurer必须是 SecurityConfigurer 的子类Class<? extends SecurityConfigurer<O, B>> clazz = configurer.getClass();synchronized(this.configurers) {if (this.buildState.isConfigured()) {throw new IllegalStateException("Cannot apply " + configurer + " to already built object");} else {List<SecurityConfigurer<O, B>> configs = null;if (this.allowConfigurersOfSameType) {configs = (List)this.configurers.get(clazz);}List<SecurityConfigurer<O, B>> configs = configs != null ? configs : new ArrayList(1);((List)configs).add(configurer);this.configurers.put(clazz, configs);if (this.buildState.isInitializing()) {this.configurersAddedInInitializing.add(configurer);}}}}//获取指定的配置类
@SuppressWarnings("unchecked")public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(Class<C> clazz) {List<C> configs = (List<C>) this.configurers.get(clazz);if (configs == null) {return new ArrayList<>();}return new ArrayList<>(configs);}
2.3.2.2、doBuild()方法
@Overrideprotected final O doBuild() throws Exception {synchronized (this.configurers) {this.buildState = BuildState.INITIALIZING;beforeInit(); //是一个预留方法,没有任何实现init(); // 就是找到所有的 xxxConfigure,挨个调用其 init 方法进行初始化,完成默认过滤器的初始化this.buildState = BuildState.CONFIGURING;beforeConfigure(); // 是一个预留方法,没有任何实现configure(); // 就是找到所有的 xxxConfigure,挨个调用其 configure 方法进行配置。this.buildState = BuildState.BUILDING;O result = performBuild();
// 是真正的过滤器链构建方法,但是在 AbstractConfiguredSecurityBuilder中 performBuild 方法只是一个抽象方法,具体的实现在 HttpSecurity 中this.buildState = BuildState.BUILT;return result;}}
init方法:完成所有相关过滤器的初始化
private void init() throws Exception {Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();for (SecurityConfigurer<O, B> configurer : configurers) {configurer.init((B) this); // 初始化对应的过滤器}for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {configurer.init((B) this);}}
configure方法:完成HttpSecurity和对应的过滤器的绑定。
private void configure() throws Exception {Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();for (SecurityConfigurer<O, B> configurer : configurers) {configurer.configure((B) this);}}
2.4、HttpSecurity
HttpSecurity 做的事情,就是对各种各样的 xxxConfigurer 进行配置;
HttpSecurity 部分方法列表如下:
HttpSecurity 中有大量类似的方法,过滤器链中的过滤器就是这样一个一个配置的。我们
就不一一介绍 了。每个配置方法的结尾都会调用一次 getOrApply 方法,getOrApply
方法是做什么的?
getOrApply 方法如下:
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)throws Exception {C existingConfig = (C) getConfigurer(configurer.getClass());if (existingConfig != null) {return existingConfig;}return apply(configurer);}
getConfigurer 方法是在它的父类 AbstractConfiguredSecurityBuilder 中定义的,目的就
是去查看当前 这个 xxxConfigurer 是否已经配置过了。
如果当前 xxxConfigurer 已经配置过了,则直接返回,否则调用 apply 方法,这个 apply
方法最终会调 用到 AbstractConfiguredSecurityBuilder#add 方法,将当前配置
configurer 收集起来 HttpSecurity 中还有一个 addFilter 方法.
addFilter 方法如下所示:
@Overridepublic HttpSecurity addFilter(Filter filter) {Integer order = this.filterOrders.getOrder(filter.getClass());if (order == null) {throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");}this.filters.add(new OrderedFilter(filter, order));return this;}
这个 addFilter 方法的作用,主要是在各个 xxxConfigurer 进行配置的时候,会调用到这
个方法, (xxxConfigurer 就是用来配置过滤器的),把 Filter 都添加到 fitlers 变量中。
3、总结
这就是 HttpSecurity 的一个大致工作流程。把握住了这个工作流程,剩下的就只是一些简
单的重 复的 xxxConfigurer 配置了
相关文章:

SpringSecurity原理解析(五):HttpSecurity 类处理流程
1、SpringSecurity 在spring boot中与SSM项目中基于配置文件的区别 通过前边的笔记我们可以知道,在传统的SSM项目中 SpringSecurity的使用是基于配置文件 的,然后spring 容器初始化的时候将 SpringSecurity 中的各种标签解析成对应的Bean对象,…...

C++系列-匿名对象
匿名对象 💢什么是匿名对象💢匿名对象的创建方式及作用域💢匿名对象的对象类型💢💢匿名的基本数据类型对象💢💢匿名的自定义的类类型对象💢💢匿名的标准库的类对象 &…...

tofixed和math.round什么区别
1、floor 返回不大于的最大整数(向下取整) 2、round 则是4舍5入的计算,入的时候是到大于它的整数(当-1.5时可见,四舍五入后得到的结果不是我们期待的,解决办法是先对他取绝对值,然后在用round方…...

OPENAIGC开发者大赛高校组金奖 | 基于混合大语言模型与多模态的全过程通用AI Agent
在第二届拯救者杯OPENAIGC开发者大赛中,涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到,我们特意开设了优秀作品报道专栏,旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者,希望能带给您…...

MySql批量迁移数据库
导出数据库 将指定数据库实例(MYSQL_HOST、MYSQL_PORT、MYSQL_USER、MYSQL_PASSWORD)中的所有数据库(表结构、数据)导出到指定目录(BACKUP_DIR)下的多个单独的SQL脚本,每个SQL脚本名称即为数据…...

一、selenium自动化简介selenium工具集
文章目录 一、简介二、组成部分三、selenium工具集3.1 Selenium IDE3.2 Selenium WebDriver3.3 Selenium Grid3.4 Appium 一、简介 官方网站 Selenium 是支持 web 浏览器自动化的一系列工具和库的综合项目。 它提供了扩展来模拟用户与浏览器的交互,用于扩展浏览器分…...

CCF推荐B类会议和期刊总结:(计算机网络领域)
CCF推荐B类会议和期刊总结(计算机网络领域) 在计算机网络领域,中国计算机学会(CCF)推荐的B类会议和期刊代表了该领域的较高水平。以下是对所有B类会议和期刊的总结,包括全称、出版社、dblp文献网址以及所属…...

[Web安全 网络安全]-文件包含漏洞
文章目录: 一:前言 1.什么是文件包含漏洞 2.文件包含漏洞的成因 3.文件包含漏洞的分类 4.文件包含漏洞的防御策略 5.文件包含函数(触发点Sink) 6.环境 6.1 靶场 6.2 其他工具 二:文件包含LFI labs靶场实验…...

使用soui4实现一个拾色器
拾色器类 #pragma once class CClrPickerCtrl : public SWindow {DEF_SOBJECT(SWindow, L"clrpicker") public:CClrPickerCtrl(void);~CClrPickerCtrl(void);//跟solider控件设置色调void SetSliderPos(int nPos);//获取选取位置的颜色COLORREF GetColor(); protect…...

Thinkphp5 + Swoole实现邮箱异步通知
在 ThinkPHP 中实现邮箱异步通知的常见做法是通过队列系统来处理异步任务,结合 Swoole 来处理异步发送邮件的请求。这样可以避免同步处理邮件发送导致的阻塞,提高响应速度。 以下是基于 ThinkPHP5 框架和 Swoole 的异步邮件通知实现步骤: 一…...

LLM - 理解 多模态大语言模型 (MLLM) 的预训练与相关技术 (三)
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/142063880 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 完备(F…...

工具篇之Joda-Time
在Java应用程序开发中,处理日期和时间是一项常见且复杂的任务。尽管Java标准库提供了基本的日期和时间操作类,但它们的使用常常不够直观和灵活。Joda-Time 是一个强大的日期和时间库,提供了丰富的API,用于简化日期和时间的操作。本…...

架构师应该懂得东西,软考应该具备的
架构师应该懂得知识 架构师作为软件系统设计和开发的关键角色,需要掌握广泛的知识和技能。具体来说,他们应该懂得以下几方面的知识: 编程语言:掌握至少一种编程语言,如Java、C、Python等,以便于进行系统设…...

图论篇--代码随想录算法训练营第五十一天打卡| 99. 岛屿数量(深搜版),99. 岛屿数量(广搜版),100. 岛屿的最大面积
99. 岛屿数量(深搜版) 题目链接:99. 岛屿数量 题目描述: 给定一个由 1(陆地)和 0(水)组成的矩阵,你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而…...

什么是API网关(API Gateway)?
1. 什么是API网关(API Gateway)? 在微服务体系结构中,客户端可能与多个前端服务进行交互。 API 网关位于客户端与服务之间。 它充当反向代理,将来自客户端的请求路由到服务。 它还可以执行各种横切任务,例…...

对话:LLC磁集成能否成为充电桩模块电源常态产品?
编者按:在终端需求疲软的影响下,前两年火热的新能源汽车、光伏、储能等新能源领域也掀起了价格战,储能已正式进入0.5元时代,新能源汽车领域价格战更是一轮接一轮,成本管控成为2024年企业绕不开的话题。 接下来我们将围…...

基于SSM的二手物品交易管理系统的设计与实现 (含源码+sql+视频导入教程+文档+PPT)
👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的二手物品交易管理系统7拥有两种角色 管理员:用户管理、分类管理、商品管理、订单管理、系统管理等 用户:登录注册、充值、收货、评价、收藏、购物车、订…...

视觉语言模型中的人脸社会感知
本文研究了视觉语言模型CLIP在处理人脸图像时的社会感知能力及其潜在偏见。研究者们构建了一个名为CausalFace的合成人脸数据集,通过系统地独立变化年龄、性别、人种、面部表情、照明和姿势等六个维度来评估模型的社会感知。他们发现,尽管CLIP是在多样化…...

JAVA学习-练习试用Java实现“最小覆盖子串”
问题: 给定一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。 注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。 示例 1&…...

关于axios同步获取数据的问题
axios同步获取数据 Axios介绍问题代码修改 总结 Axios介绍 Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 X…...

java-在ANTLR中,如何从java文件中提取类名和方法名0.1.8
java-在ANTLR中,如何从java文件中提取类名和方法名0.1.0 目标java源文件java的g4文件生成antlr代码最终代码调测结果阶段性总结 2024年9月12日11:16:01----0.1.8 目标 从一个java文件中提取出类名和方法名 java源文件 文件名是main.java,具体内容如下…...

十大护眼灯钢琴灯品牌是智商税吗?十大钢琴灯品牌排行榜
十大护眼灯钢琴灯品牌是智商税吗?不良的光线不仅会使得孩子在读写用眼时眼睛不舒服,还会引起视觉疲劳伤眼视力健康,这个时候要能有一台可靠的护眼灯钢琴灯,那真是再好不过了。但是市面上护眼灯钢琴灯的种类太多,盲目挑…...

搜维尔科技:CyberGlove将实时捕捉运动信号和触觉反馈,将其重新定位到人形机器人进行驱动
CyberGlove将实时捕捉运动信号和触觉反馈,然后将其重新定位到人形机器人上。 这款18个传感器(有18节点和22节点两个型号,22节点早期用于美国军事方面,支持无线通信、蓝牙、WiFi、射频)数据手套的每个手指上有两个弯曲…...

数据结构:堆的算法
目录 一堆的向上调整算法二堆的向下调整算法三堆的应用:堆排序四TOPK问题 一堆的向上调整算法 我们在堆中插入一个数据一般实在堆的最后插入然后可以一步一步与上层结点(父结点进行比较),继而进行交换,完成二叉树的结构࿰…...

python画图|3D直方图基础教程
前述已经完成了直方图和3D图的基本学习,链接如下: 直方图:python画图|水平直方图绘制-CSDN博客 3D图:python画图|水平直方图绘制-CSDN博客 现在我们尝试把二者结合,画3D直方图。 【1】官网教程 首先,依…...

C语言中的函数,实参,形参,递归
1:什么是函数 2:定义带形式参数的函数和带实际参数的函数 3:递归 --------------------------------------------------------------------------------------------------------------------------------- 1:在 C 语言中&…...

ICM20948 DMP代码详解(15)
接前一篇文章:ICM20948 DMP代码详解(14) 上一回开始对icm20948_sensor_setup函数中第3段代码即inv_icm20948_initialize函数进行解析。为了便于理解和回顾,再次贴出其源码,在EMD-Core\sources\Invn\Devices\Drivers\IC…...

NC 和为K的连续子数组
系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 描述 给定一个无序…...

JS设计模式之装饰者模式:优雅的给对象增添“魔法”
引言 在前端开发中,我们经常会遇到需要在不修改已有代码的基础上给对象添加新的行为或功能的情况。而传统的继承方式并不适合这种需求,因为继承会导致类的数量急剧增加,且每一个子类都会固定地实现一种特定的功能扩展。 装饰者模式则提供了…...

准备好了吗?JAVA从业AI开发的学习路线详解
作为一个拥有扎实 Java 基础的人,想要涉足人工智能(AI)应用开发,你已经在编程能力方面打下了很好的基础。Java 是一种通用的、强类型的语言,非常适合于开发高性能的应用程序,尤其是在后端服务和大规模分布式…...