当前位置: 首页 > news >正文

SpringSecurity 初始化解析

文章目录

  • 前言
  • 加载SpringSecurity配置
  • 解析配置
  • SpringSecurity 解析器
  • security:http 解析
  • FilterChainProxy的注册过程
  • 创建 SpringSecurity 过滤器
  • 总结

前言

通过上文分析知道了SpringSecurity对一个请求的具体处理流程。不知道大家是否跟我一样都有几个疑问:

  1. FilterChainProxy什么时候创建的?
  2. 过滤器链和对应的过滤器什么时候创建的?
  3. 怎么把自定义的过滤器添加到过滤器链中?
  4. 请求和过滤器的匹配规则是什么?

如果有的话,本文将为你解答或消除它们。

加载SpringSecurity配置

上文提到Spring的初始化会加载解析SpringSecurity的配置文件,现在来分析下。

首先系统启动的时候会触发在 web.xml中配置的ContextLoaderListener监听器

image.png

然后会执行对应的initWebApplicationContext方法

image.png

然后进入configureAndRefreshWebApplicationContext方法中。

image.png

refresh()方法

	@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing./*** * 1、设置容器的启动时间* 2、设置活跃状态为true* 3、设置关闭状态为false* 4、获取Environment对象,并加载当前系统的属性值到Environment对象中* 5、准备监听器和事件的集合对象,默认为空的集合*/prepareRefresh();// Tell the subclass to refresh the internal bean factory.// 创建容器对象:DefaultListableBeanFactory// 加载xml配置文件的属性值到当前工厂中,最重要的就是BeanDefinitionConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.// beanFactory的准备工作,对各种属性进行填充prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 子类覆盖方法做额外的处理,此处我们自己一般不做任何扩展工作,但是可以查看web中的代码,是有具体实现的postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.// 调用各种beanFactory处理器invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.// 注册bean处理器,这里只是注册功能,真正调用的是getBean方法registerBeanPostProcessors(beanFactory);// Initialize message source for this context.// 为上下文初始化message源,即不同语言的消息体,国际化处理,在springmvc的时候通过国际化的代码重点讲initMessageSource();// Initialize event multicaster for this context.// 初始化事件监听多路广播器initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.// 留给子类来初始化其他的beanonRefresh();// Check for listener beans and register them.// 在所有注册的bean中查找listener bean,注册到消息广播器中registerListeners();// Instantiate all remaining (non-lazy-init) singletons.// 初始化剩下的单实例(非懒加载的)finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.// 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.// 为防止bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件beandestroyBeans();// Reset 'active' flag.// 重置active标志cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}}}

配置文件的加载解析需要进入obtainFreshBeanFactory()方法中加载配置文件。

在这里插入图片描述

解析配置

最终会进入registerBeanDefinitions方法解析配置文件

在这里插入图片描述

parseDefaultElement方法会完成Spring中提供的默认方法解析,具体如下:

image.png

而SpringSecurity的解析是先进入import中,然后进入到parseCustomElement()方法来解析。

image.png

SpringSecurity 解析器

在SpringSecurity的配置文件中使用了几个标签。

image.png

每个标签都有对应的解析器。

在这里插入图片描述

在SecurityNamespaceHandler中的 parsers中保存的就是节点对应的解析器。

image.png

security:http 解析

解析器会先解析security:http标签了,下面的逻辑也很清晰:

  • 先判断是否合法
  • 然后获取标签名称
  • 根据标签名称获取对应的解析器
  • 然后通过解析器来解析标签

image.png

进入HttpSecurityBeanDefinitionParser中看看解析http标签做了什么事情。

	@Overridepublic BeanDefinition parse(Element element, ParserContext pc) {// CompositeComponentDefinition  保存内嵌的BeanDefinitionCompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));// compositeDef定义保存在了 父容器中pc.pushContainingComponent(compositeDef);// 完成FilterChainProxy的注册registerFilterChainProxyIfNecessary(pc, pc.extractSource(element));// Obtain the filter chains and add the new chain to itBeanDefinition listFactoryBean = pc.getRegistry().getBeanDefinition(BeanIds.FILTER_CHAINS);List<BeanReference> filterChains = (List<BeanReference>) listFactoryBean.getPropertyValues().getPropertyValue("sourceList").getValue();// createFilterChain(element, pc) 创建对应的过滤器并添加到了filterChains这个过滤器链中filterChains.add(createFilterChain(element, pc));pc.popAndRegisterContainingComponent();return null;}

上面代码的几个关键点:

  • CompositeComponentDefinition保存配置文件中的嵌套的BeanDefinition信息
  • 完成了FilterChainProxy的注册
  • 完成了处理请求的过滤器和过滤器链的处理

FilterChainProxy的注册过程

image.png

SpringSecurity在BeanId中定义了相关的固定beanId值。

public abstract class BeanIds {private static final String PREFIX = "org.springframework.security.";/*** The "global" AuthenticationManager instance, registered by the* <authentication-manager> element*/public static final String AUTHENTICATION_MANAGER = PREFIX + "authenticationManager";/** External alias for FilterChainProxy bean, for use in web.xml files */public static final String SPRING_SECURITY_FILTER_CHAIN = "springSecurityFilterChain";public static final String CONTEXT_SOURCE_SETTING_POST_PROCESSOR = PREFIX+ "contextSettingPostProcessor";public static final String USER_DETAILS_SERVICE = PREFIX + "userDetailsService";public static final String USER_DETAILS_SERVICE_FACTORY = PREFIX+ "userDetailsServiceFactory";public static final String METHOD_ACCESS_MANAGER = PREFIX+ "defaultMethodAccessManager";public static final String FILTER_CHAIN_PROXY = PREFIX + "filterChainProxy";public static final String FILTER_CHAINS = PREFIX + "filterChains";public static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX+ "methodSecurityMetadataSourceAdvisor";public static final String EMBEDDED_APACHE_DS = PREFIX+ "apacheDirectoryServerContainer";public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource";public static final String DEBUG_FILTER = PREFIX + "debugFilter";
}

创建 SpringSecurity 过滤器

接下来看看SpringSecurity中默认的过滤器是如何创建

image.png

private BeanReference createFilterChain(Element element, ParserContext pc) {// 判断是否需要Security拦截boolean secured = !OPT_SECURITY_NONE.equals(element.getAttribute(ATT_SECURED));if (!secured) {// 如果没配置pattern属性并且配置了request-matcher-ref为空 添加错误信息if (!StringUtils.hasText(element.getAttribute(ATT_PATH_PATTERN)) && !StringUtils.hasText(ATT_REQUEST_MATCHER_REF)) {pc.getReaderContext().error("The '" + ATT_SECURED + "' attribute must be used in combination with" + " the '" + ATT_PATH_PATTERN + "' or '" + ATT_REQUEST_MATCHER_REF + "' attributes.", pc.extractSource(element));}for (int n = 0; n < element.getChildNodes().getLength(); n++) {// 如果有子节点则添加错误信息if (element.getChildNodes().item(n) instanceof Element) {pc.getReaderContext().error("If you are using <http> to define an unsecured pattern, " + "it cannot contain child elements.", pc.extractSource(element));}}// 创建过滤器链return createSecurityFilterChainBean(element, pc, Collections.emptyList());}// portMapper、portResolver主要提供给SSL相关类使用final BeanReference portMapper = createPortMapper(element, pc);final BeanReference portResolver = createPortResolver(portMapper, pc);// 新建一个空的authenticationProviders集合 ManagedList<BeanReference> authenticationProviders = new ManagedList<BeanReference>();// 通过空的authenticationProviders集合产生一个AuthenticationManager的bean定义BeanReference authenticationManager = createAuthenticationManager(element, pc, authenticationProviders);// 是否全采用默认配置boolean forceAutoConfig = isDefaultHttpConfig(element);// 看下面HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, forceAutoConfig, pc, portMapper, portResolver, authenticationManager);// 看下面AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc, httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager, httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());// 配置logoutHandlershttpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());httpBldr.setEntryPoint(authBldr.getEntryPointBean());httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());// 向AuthenticationProviders中添加provider  authenticationProviders.addAll(authBldr.getProviders());List<OrderDecorator> unorderedFilterChain = new ArrayList<OrderDecorator>();// 向FilterChain链中添加filters  unorderedFilterChain.addAll(httpBldr.getFilters());unorderedFilterChain.addAll(authBldr.getFilters());// 添加自定义的Filter,也就是custom-filter标签定义的Filter  unorderedFilterChain.addAll(buildCustomFilterList(element, pc));// 对过滤器进行排序Collections.sort(unorderedFilterChain, new OrderComparator());// 校验过滤器是否有效checkFilterChainOrder(unorderedFilterChain, pc, pc.extractSource(element));// The list of filter beansList<BeanMetadataElement> filterChain = new ManagedList<BeanMetadataElement>();for (OrderDecorator od : unorderedFilterChain) {filterChain.add(od.bean);}// 创建SecurityFilterChain return createSecurityFilterChainBean(element, pc, filterChain);
}

先看HttpConfigurationBuilder的构造方法

public HttpConfigurationBuilder(Element element, boolean addAllAuth, ParserContext pc, BeanReference portMapper, BeanReference portResolver, BeanReference authenticationManager) {this.httpElt = element;this.addAllAuth = addAllAuth;this.pc = pc;this.portMapper = portMapper;this.portResolver = portResolver;this.matcherType = MatcherType.fromElement(element);// 获取子标签intercept-urlinterceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);for (Element urlElt : interceptUrls) {// 判断子标签intercept-url是否配置了filters属性// 如果配置了filters属性添加错误消息,因为Security已经不再支持filters属性了if (StringUtils.hasText(urlElt.getAttribute(ATT_FILTERS))) {pc.getReaderContext().error("The use of \"filters='none'\" is no longer supported. Please define a" + " separate <http> element for the pattern you want to exclude and use the attribute" + " \"security='none'\".", pc.extractSource(urlElt));}}// 获取标签create-session属性String createSession = element.getAttribute(ATT_CREATE_SESSION);if (StringUtils.hasText(createSession)) {sessionPolicy = createPolicy(createSession);} else {// 默认策略sessionPolicy = SessionCreationPolicy.IF_REQUIRED;}// 创建一系列过滤器createCsrfFilter();createSecurityContextPersistenceFilter();createSessionManagementFilters();createWebAsyncManagerFilter();createRequestCacheFilter();createServletApiFilter(authenticationManager);createJaasApiFilter();createChannelProcessingFilter();createFilterSecurityInterceptor(authenticationManager);createAddHeadersFilter();
}

然后进入AuthenticationConfigBuilder中来查看,发向其实也创建了很多的过滤器

public AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc, SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager, BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {this.httpElt = element;this.pc = pc;this.requestCache = requestCache;// 是否自动配置autoConfig = forceAutoConfig | "true".equals(element.getAttribute(ATT_AUTO_CONFIG));// 是否允许sessionthis.allowSessionCreation = sessionPolicy != SessionCreationPolicy.NEVER && sessionPolicy != SessionCreationPolicy.STATELESS;this.portMapper = portMapper;this.portResolver = portResolver;this.csrfLogoutHandler = csrfLogoutHandler;// 创建一系列过滤器createAnonymousFilter();createRememberMeFilter(authenticationManager);createBasicFilter(authenticationManager);createFormLoginFilter(sessionStrategy, authenticationManager);createOpenIDLoginFilter(sessionStrategy, authenticationManager);createX509Filter(authenticationManager);createJeeFilter(authenticationManager);createLogoutFilter();createLoginPageFilterIfNeeded();createUserDetailsServiceFactory();createExceptionTranslationFilter();
}

创建SecurityFilterChain

image.png

总结

通过以上的分析可以知道,在Spring初始化的时候根据SpringSecurity的相关配置按照其解析器将相关的过滤器加载到了Spring Bean中,在此后的请求中就可以使用到SpringSecurity相关的过滤器。

相关文章:

SpringSecurity 初始化解析

文章目录 前言加载SpringSecurity配置解析配置SpringSecurity 解析器security:http 解析FilterChainProxy的注册过程创建 SpringSecurity 过滤器总结 前言 通过上文分析知道了SpringSecurity对一个请求的具体处理流程。不知道大家是否跟我一样都有几个疑问&#xff1a; Filte…...

ip netns网络空间使用

SNAT 源地址转发 执行ip netns exec route_br_ens192_0 iptables -nL POSTROUTING -t nat --line-numbers 输出如下&#xff1a; Chain POSTROUTING (policy ACCEPT) num target prot opt source destination 1 SNAT all -- 0.0.0.0/…...

解决 Cannot read property ‘key‘ of undefined

目录 问题解决1解决2最终 问题 现场环境分页查询某些条件项查询时&#xff0c;分页接口获取成功但是数据不渲染&#xff0c;页面像是卡住了&#xff1a; 报错 Cannot read property key of undefined 解决1 有人说 使用的el-pagination在格式化代码的时候layout属性的参数会多加…...

「聊设计模式」之工厂方法模式(Factory Method)

&#x1f3c6;本文收录于《聊设计模式》专栏&#xff0c;专门攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;早日登顶&#x1f680;&#xff0c;欢迎持续关注&&收藏&&订阅&#xff01; 前言 设计模式是指在软件设计中&#xff0c;经过总结和提炼的&#…...

局部变量,全局变量与内存

本文会使用IDA分析局部变量&#xff0c;全局变量在内存的存储 目录 使用IDA分析局部变量 使用IDA分析全局变量 总结 使用IDA分析局部变量 #include <stdio.h>int main() {int nNum 1;float fNum 2.5;char ch A;printf("int %d, float %f, char %c", nNu…...

Python爬虫异常处理实用技巧分享

当我们编写爬虫程序时&#xff0c;经常会遇到各种各样的异常情况&#xff0c;比如网络连接失败、页面解析错误、请求被拒绝等等。这些异常情况可能导致程序中断或者无法正常运行&#xff0c;给我们的数据采集工作带来一定的困扰。所以&#xff0c;掌握一些实用的异常处理技巧对…...

【性能测试】Jmeter —— jmeter计数器

jmeter计数器 如果需要引用的数据量较大&#xff0c;且要求不能重复或者需要递增&#xff0c;那么可以使用计数器来实现 如&#xff1a;新增功能&#xff0c;要求名称不能重复 1&#xff0c;新增计数器 计数器&#xff1a;允许用户创建一个在线程组之内都可以被引用的计数器…...

Python 布尔类型和比较运算符

视频版教程 Python3零基础7天入门实战视频教程 布尔( bool&#xff09;表达现实生活中的逻辑&#xff0c;即真和假&#xff0c;True表示真&#xff0c;False表示假。 实例&#xff1a; # 布尔类型定义 b1 True b2 False print(f"b1{b1},类型是{type(b1)}") prin…...

蓝牙核心规范(V5.4)10.1-BLE 入门笔记(1)

ble 规范 深入了解蓝牙LE需要熟悉相关的规格。蓝牙LE的架构、程序和协议由一项关键规范完全定义,称为蓝牙核心规范。产品如何使用蓝牙以实现互操作性由两种特殊类型称为配置文件和服务的规范集合所涵盖。图1展示了BLE规范类型及其相互关系。 1.1 蓝牙核心规范 蓝牙核心规范是…...

Java高级之泛型、自定义泛型、通配符的使用

泛型与File 文章目录 一、为什么要有泛型&#xff1f;1.1、什么是泛型&#xff1f;1.2、泛型的设计背景1.3、泛型的概念 二、在集合中使用泛型三、自定义泛型结构2.1、泛型方法的使用 四、泛型在继承上的体现五、通配符的使用5.1、通配符的使用5.2、有限制条件的通配符的使用 …...

代码随想录二刷day32

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、力扣122. 买卖股票的最佳时机 II二、力扣55. 跳跃游戏三、力扣45. 跳跃游戏 II 前言 一、力扣122. 买卖股票的最佳时机 II class Solution {public int ma…...

linux基础篇

文章目录 linux基础篇1.Linux文件系统结构:2.常用的Linux指令&#xff1a;3.Shell指令&#xff1a;4.Linux服务管理&#xff1a;5.Linux磁盘挂载&#xff1a;其他 linux基础篇 1.Linux文件系统结构: 根目录 /bin目录&#xff1a;二进制可执行文件存放处boot目录&#xff1a;启…...

文心一言插件开发全流程,ERNIE-Bot-SDK可以调用文心一言的能力

文心一言插件开发 前言插件插件是什么工作原理申请开发权限 开始第一步&#xff1a;安装python第二步&#xff1a;搭建项目manifest 描述文件&#xff1a;ai-plugin.json插件服务描述文件&#xff1a;openapi.yaml开发自己的plugin-server 第三步&#xff1a;上传插件 SDK相关链…...

Keepalived+LVS负载均衡

Keepalived 是一个用于实现高可用性的开源软件&#xff0c;它基于 VRRP&#xff08;Virtual Router Redundancy Protocol&#xff09;协议&#xff0c;允许多台服务器协同工作&#xff0c;以确保在某个服务器出现故障时服务的连续性。Keepalived 的核心思想是将多台服务器配置成…...

接口测试学习

1、curl 命令 无参&#xff1a;curl -X POST -H"Authorization: abcdefghijklmn" https://xxx.xxxxx.com/xxxx 有参&#xff1a;curl -X POST -H"Authorization:abcdefghijklmn " -H"Content-Type:application/json" https://xxx.xxxxx.com/…...

怎么用外网访问自己的网站?快解析内网端口映射来实现

想要访问服务器上的网站需要直接或间接访问服务器IP地址&#xff0c;但是如果服务器没有公网IP地址&#xff0c;那么就需要借助外网进行访问。当我们需要远程访问内网的Web服务器时&#xff0c;我们需要使用一些技术来实现此目的。这就需要通过使用类似快解析内网端口映射方式进…...

zabbix学习1--zabbix6.x单机

文章目录 1. 环境2. MYSQL8.02.1 单节点2.2 配置主从 3. 依赖组件4. zabbix-server5. agent5.1 yum5.2 编译 附录my.cnfJDK默认端口号 1. 环境 进入官网查看所需部署环境配置以及应用版本要求https://www.zabbix.com/documentation/current/zh/manual/installation/requiremen…...

Flink 的 Kafka Table API Connector

Flink datastream connectors 和 Flink table api connectors 的区别&#xff1a; Flink DataStream Connectors和Table API Connectors是Flink中用于连接外部数据源的两种不同的连接器。 1. Flink DataStream Connectors: - Flink DataStream Connectors是用于将外部数据源连…...

tcpdump 命令

一、TCPDUMP指定IP 在网络流量分析过程中&#xff0c;我们经常需要对指定的IP进行抓取和分析。使用TCPDUMP指定IP非常简单&#xff0c;只需要通过命令行参数-i指定需要抓取的网卡&#xff0c;并使用host参数指定目标IP地址即可&#xff1a;tcpdump -i eth0 host 192.168.0.1 上…...

哪些测试项目可以使用自动化测试?

通常&#xff0c;软件测试v的测试方式分为人工测试和自动化测试&#xff0c;人工测试是由测试人员编写并执行测试用例&#xff0c;然后观察测试结果与预期结果是否一致的过程;自动化测试是通过测试工具来代替或辅助人工去验证系统功能是否有问题的过程。 采用自动化测试需要满…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

6.9-QT模拟计算器

源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...