Spring Boot - 扩展点 EnvironmentPostProcessor源码分析及真实案例

news2024/11/14 8:26:15

文章目录

  • 概述
  • EnvironmentPostProcessor 作用
  • EnvironmentPostProcessor 实现和注册
    • 创建类并实现接口
    • 注册到 Spring Boot
    • 常见应用场景
  • 源码分析
    • 1. `EnvironmentPostProcessor` 接口定义
    • 2. 扩展点加载流程
    • 3. 加载 `EnvironmentPostProcessor` 实现类
    • 4. `EnvironmentPostProcessor` 执行时机
    • 5. 环境属性源的扩展示例
    • 6. `EnvironmentPostProcessor` 应用场景
  • 真实案例
  • 小结

在这里插入图片描述


概述

EnvironmentPostProcessor 是 Spring Boot 提供的一个扩展点,用于在应用环境初始化过程中执行一些额外的处理。该接口允许开发者在 Spring Environment 初始化完成后、应用上下文加载之前,自定义和调整环境变量,这为配置和条件化应用设置提供了极大的灵活性。


EnvironmentPostProcessor 作用

EnvironmentPostProcessor 作为一个接口,允许在 Spring Environment 对象被创建和配置后进行扩展操作。通过它,可以在应用启动时添加、修改或删除 Environment 中的属性值,比如注入额外的配置源、动态设置配置项、调整日志级别等。

使用场景

  • 自定义配置源,例如从数据库或远程服务动态加载配置。
  • 修改现有的配置值,或基于环境动态调整配置。
  • 执行一些特定逻辑,如根据某些条件禁用部分功能或切换日志级别。

EnvironmentPostProcessor 实现和注册

创建类并实现接口

实现 EnvironmentPostProcessor 接口的类,需要重写其 postProcessEnvironment 方法,在该方法中注入或修改环境配置。

import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;

public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        Map<String, Object> customProperties = new HashMap<>();
        customProperties.put("custom.property", "customValue");
        environment.getPropertySources().addFirst(new MapPropertySource("customProperties", customProperties));
    }
}

注册到 Spring Boot

CustomEnvironmentPostProcessor 注册为一个 EnvironmentPostProcessor,通过在 META-INF/spring.factories 文件中添加如下配置:

org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.CustomEnvironmentPostProcessor

Spring Boot 会自动扫描 spring.factories 中的定义,并在环境初始化时加载 CustomEnvironmentPostProcessor


常见应用场景

  • 动态配置加载:可在启动时通过读取外部配置源(如数据库、远程服务)来加载配置。
  • 条件化配置注入:基于环境条件,动态插入某些配置项。
  • 日志配置:在应用启动时根据外部条件设置日志级别或日志输出位置。
  • 多环境支持:可以根据当前环境(如 dev、prod)注入不同的默认配置。

源码分析

1. EnvironmentPostProcessor 接口定义

EnvironmentPostProcessor 是一个非常简单的接口,仅包含一个方法 postProcessEnvironment。下面是其接口的定义:

package org.springframework.boot.env;

import org.springframework.boot.SpringApplication;
import org.springframework.core.env.ConfigurableEnvironment;

@FunctionalInterface
public interface EnvironmentPostProcessor {
    void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}
  • 参数解释

    • ConfigurableEnvironment environment:当前 Spring 应用的 Environment 对象,通过该参数可以访问和操作环境中的属性源。
    • SpringApplication application:Spring 应用的入口,提供对应用的一些基本信息和配置信息的访问。
  • 注解@FunctionalInterface 表明它是一个函数式接口,可以用 lambda 表达式实现。


2. 扩展点加载流程

在 Spring Boot 应用启动时,SpringApplication 类负责初始化并加载一系列的配置扩展点,EnvironmentPostProcessor 也是在此阶段被加载的。SpringApplication 会在初始化过程中,使用 SpringFactoriesLoaderMETA-INF/spring.factories 文件中加载所有注册的 EnvironmentPostProcessor 实现。

关键代码在 SpringApplication 类的 configureEnvironment 方法中:

// SpringApplication.java 中
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    // 创建并配置环境
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    
    // 配置 Environment,包括加载 EnvironmentPostProcessor
    this.configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    
    // 绑定环境属性并返回
    return environment;
}

configureEnvironment 方法中,会调用 applyInitializers 方法,加载所有的 ApplicationContextInitializerEnvironmentPostProcessor 实现。

3. 加载 EnvironmentPostProcessor 实现类

SpringFactoriesLoader 是 Spring 的一个通用加载工具,用于从 META-INF/spring.factories 文件中读取配置类并实例化。Spring Boot 使用 SpringFactoriesLoader 来加载 EnvironmentPostProcessor 实现类,并在应用启动时逐一执行它们的 postProcessEnvironment 方法。

// SpringApplication.java 中
private void applyInitializers(ConfigurableApplicationContext context) {
    List<ApplicationContextInitializer<?>> initializers = getInitializers();
    for (ApplicationContextInitializer<?> initializer : initializers) {
        if (initializer instanceof EnvironmentPostProcessor) {
            ((EnvironmentPostProcessor) initializer).postProcessEnvironment(context.getEnvironment(), this);
        }
    }
}

排序执行: Spring Boot 会将所有找到的 EnvironmentPostProcessor 实现按照 @Order 注解或实现 Ordered 接口的方式进行排序,然后依次执行它们的 postProcessEnvironment 方法


4. EnvironmentPostProcessor 执行时机

EnvironmentPostProcessorpostProcessEnvironment 方法会在应用上下文 ApplicationContext 初始化之前被调用。也就是说,在加载应用的 @Bean 和其他上下文配置之前,EnvironmentPostProcessor 已经对环境进行了处理。

此调用顺序确保开发者可以在应用加载所有属性和资源文件之前,修改环境变量或添加新的属性源。这对动态配置源或在运行时确定属性值的场景特别有用。

修改 Environment: 在 postProcessEnvironment 方法中,可以通过 ConfigurableEnvironment 对象操作应用的环境。最常见的操作是添加自定义的 PropertySource,例如从数据库或配置文件中读取属性

5. 环境属性源的扩展示例

假设我们希望在环境中加入一个自定义配置,可以通过 EnvironmentPostProcessor 来实现:

public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        // 创建一个自定义属性源
        Map<String, Object> customProperties = new HashMap<>();
        customProperties.put("custom.property", "customValue");

        // 将自定义属性源添加到环境中
        environment.getPropertySources().addFirst(new MapPropertySource("customProperties", customProperties));
    }
}

6. EnvironmentPostProcessor 应用场景

  • 动态属性注入:在应用启动时从数据库或外部服务获取配置,并将其添加到 Environment 中。
  • 基于环境的配置:根据当前环境变量(如 devprod)动态设置不同的配置。
  • 日志配置:通过 EnvironmentPostProcessor 可以在应用启动前修改日志级别或日志文件路径。

真实案例

多个配置文件

在这里插入图片描述

CustomEnvironmentPostProcessor

@Component
@Slf4j
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        // 从环境变量设置JasyptKey
        String jasyptKey = environment.getProperty("jasypt.encryptor.key");
        if (StringUtils.isNotBlank(jasyptKey)) {
            JasyptUtil.setJasyptKey(jasyptKey);
            log.info("set JasyptUtil jasyptKey success");
        }

        Map<String, Object> properties = new HashMap<>(10);

        // nacos参数设置,从environment中获取属性值
        String nacosUserName = environment.getProperty("spring.cloud.nacos.discovery.username");
        if (StringUtils.isNotBlank(nacosUserName)) {
            // 重新设置值
            properties.put("spring.cloud.nacos.discovery.username", JasyptUtil.jasyptDecrypt4EndPoint(nacosUserName));
            log.info("set spring.cloud.nacos.discovery.username success");
        }
        String nacosPassword = environment.getProperty("spring.cloud.nacos.discovery.password");
        if (StringUtils.isNotBlank(nacosPassword)) {
            properties.put("spring.cloud.nacos.discovery.password", JasyptUtil.jasyptDecrypt4EndPoint(nacosPassword));
            log.info("set spring.cloud.nacos.discovery.password success");
        }

        // redis参数设置,从environment中获取属性值
        String redisPwd = environment.getProperty("spring.redis.password");
        if (StringUtils.isNotBlank(redisPwd)) {
            // 重新设置值
            properties.put("spring.redis.password", JasyptUtil.jasyptDecrypt4EndPoint(redisPwd));
            log.info("set spring.redis.password success");
        }

        // es参数设置,从environment中获取属性值
        String esUserName = environment.getProperty("spring.elasticsearch.bboss.elasticUser");
        if (StringUtils.isNotBlank(esUserName)) {
            // 重新设置值
            properties.put("spring.elasticsearch.bboss.elasticUser", JasyptUtil.jasyptDecrypt4EndPoint(esUserName));
            log.info("set spring.elasticsearch.bboss.elasticUser success");
        }

        String esPwd = environment.getProperty("spring.elasticsearch.bboss.elasticPassword");
        if (StringUtils.isNotBlank(esPwd)) {
            // 重新设置值
            properties.put("spring.elasticsearch.bboss.elasticPassword", JasyptUtil.jasyptDecrypt4EndPoint(esPwd));
            log.info("set spring.elasticsearch.bboss.elasticPassword success");
        }


        //Kafka 参数设置
        String kafkaTurstStorePassword = environment.getProperty("spring.kafka.ssl.trust-store-password");
        if (StringUtils.isNotBlank(kafkaTurstStorePassword)) {
            // 重新设置值
            properties.put("spring.kafka.ssl.trust-store-password", JasyptUtil.jasyptDecrypt4EndPoint(kafkaTurstStorePassword));
            log.info("set spring.kafka.ssl.trust-store-password success");
        }

        String kafkaJaasConfig = environment.getProperty("spring.kafka.properties.sasl.jaas.config");
        if (StringUtils.isNotBlank(kafkaJaasConfig)) {
            // 重新设置值
            properties.put("spring.kafka.properties.sasl.jaas.config", JasyptUtil.jasyptDecrypt4EndPoint(kafkaJaasConfig));
            log.info("set spring.kafka.properties.sasl.jaas.config success");
        }

        // 加载到环境变量中
        environment.getPropertySources().addFirst(new MapPropertySource("customProperties", properties));

        //Windows系统暂时移除关于kafka的认证
        if (System.getProperty("os.name").toLowerCase().contains("win")) {
            environment.getPropertySources().remove("spring.kafka.ssl.trust-store-location");
            environment.getPropertySources().remove("spring.kafka.ssl.trust-store-password");
            environment.getPropertySources().remove("spring.kafka.security.protocol");
            environment.getPropertySources().remove("spring.kafka.properties.sasl.mechanism");
            environment.getPropertySources().remove("spring.kafka.properties.sasl.jaas.config");
            environment.getPropertySources().remove("spring.kafka.properties.ssl.endpoint.identification.algorithm");
        }

        // monitor
        String monitorUserName = environment.getProperty("ko-time.user-name");
        if (StringUtils.isNotBlank(monitorUserName)) {
            // 重新设置值
            properties.put("ko-time.user-name", JasyptUtil.jasyptDecrypt4EndPoint(monitorUserName));
            log.info("set ko-time.user-name success");
        }

        String monitorPwd = environment.getProperty("ko-time.password");
        if (StringUtils.isNotBlank(monitorPwd)) {
            // 重新设置值
            properties.put("ko-time.password", JasyptUtil.jasyptDecrypt4EndPoint(monitorPwd));
            log.info("set ko-time.password success");
        }

    }
}


import lombok.extern.slf4j.Slf4j;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.util.password.BasicPasswordEncryptor;
import org.jasypt.util.text.BasicTextEncryptor;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 * <p>
 */
@Slf4j
public class JasyptUtil {
    /**
     * JasyptKey
     */
    private static String jasyptKey = "xxxxxxxxxxxxx";
    // 创建BasicTextEncryptor实例用于密码加密和解密
    private static final BasicTextEncryptor basicTextEncryptor = new BasicTextEncryptor();
    // 创建BasicPasswordEncryptor实例用于密码加密和验证
    private static final BasicPasswordEncryptor basicPasswordEncryptor = new BasicPasswordEncryptor();
    // 创建PooledPBEStringEncryptor并配置加密器,使用池化技术以支持多线程加密
    private static final PooledPBEStringEncryptor pooledPBEStringEncryptor = new PooledPBEStringEncryptor();

    public static void setJasyptKey(String key) {
        JasyptUtil.jasyptKey = key;
    }

    static {
        //基础加解密实例参数初始化
        basicTextEncryptor.setPassword(jasyptKey);

        //多线程加解密实例参数初始化
        // 设置线程池大小为6,即同时最多有6个线程执行加密操作
        pooledPBEStringEncryptor.setPoolSize(6);
        // 设置加密使用的密码
        pooledPBEStringEncryptor.setPassword(jasyptKey);
        // 设置加密算法
        pooledPBEStringEncryptor.setAlgorithm("PBEWithMD5AndTripleDES");

    }

    /**
     * @param encryptText 密文
     * @return java.lang.String 解密后文本
     * @description 项目中使用JasyptUtil通用解密方法
     * @date 2024/6/6 9:46
     */
    public static String jasyptDecrypt4EndPoint(String encryptText) {
        //以ENCRYPTOR_PREFIX常量值开头的便是密文需要解密
        //否则认为是明文不再进行处理
        if (encryptText.startsWith("enc`")) {
            encryptText = encryptText.replace("enc`", "");
            encryptText = JasyptUtil.basicDecrypted(encryptText);
        }
        return encryptText;
    }

    /**
     * @param encryptText 待加密文本
     * @return java.lang.String 加密后文本
     * @description 使用BasicTextEncryptor对文本进行加密
     * @date 2024/6/5 17:24
     */
    public static String basicEncrypt(String encryptText) {
        try {
            // 加密文本信息
            return basicTextEncryptor.encrypt(encryptText);
        } catch (Exception e) {
            // 处理加密/解密过程中可能出现的异常
            log.error("Error during encryption: ", e);
        }
        return null;
    }

    /**
     * @param decryptedText 待解密文本
     * @return java.lang.String 解密后文本
     * @description 使用BasicTextEncryptor对文本进行解密
     * @date 2024/6/5 17:24
     */
    public static String basicDecrypted(String decryptedText) {
        try {
            // 解密已加密的文本信息
            return basicTextEncryptor.decrypt(decryptedText);
        } catch (Exception e) {
            // 处理加密/解密过程中可能出现的异常
            log.error("Error during decryption: ", e);
        }
        return null;
    }

    /**
     * @param encryptText 待加密文本
     * @return java.lang.String 加密后文本
     * @description 使用BasicPasswordEncryptor对文本进行加密
     * @date 2024/6/5 17:24
     */
    public static String basicPasswordEncrypt(String encryptText) {
        try {
            // 加密文本
            return basicPasswordEncryptor.encryptPassword(encryptText);
        } catch (Exception e) {
            // 处理加密/解密过程中可能出现的异常
            log.error("Error during encryption: ", e);
        }
        return null;
    }

    /**
     * @param plainPassword     明文(如前端输入密码)
     * @param encryptedPassword 密文(如数据库存储密码)
     * @return boolean 校验通过返回true否则返回false
     * @description 使用BasicPasswordEncryptor实例对输入密码进行验证
     * @date 2024/6/5 17:45
     */
    private static boolean oneWayPasswordCheck(String plainPassword, String encryptedPassword) {
        // 检查密码 plainPassword 是否与加密后的密码匹配
        return basicPasswordEncryptor.checkPassword(plainPassword, encryptedPassword);
    }

    /**
     * @param encryptText 待加密文本
     * @return java.lang.String 加密后文本
     * @description 使用PooledPBEStringEncryptor对文本进行加密
     * @date 2024/6/5 17:24
     */
    private static String multiThreadEncrypted(String encryptText) {
        // 加密明文字符串
        return pooledPBEStringEncryptor.encrypt(encryptText);
    }

    /**
     * @param decryptedText 加密文本
     * @return java.lang.String 解密后文本
     * @description 使用PooledPBEStringEncryptor对文本进行解密
     * @date 2024/6/5 17:24
     */
    private static String multiThreadDecrypted(String decryptedText) {
        // 解密密文字符串
        return pooledPBEStringEncryptor.decrypt(decryptedText);
    }

    /**
     * 该示例演示如何改变加密算法。
     * 该方法不接受参数且无返回值。
     * 主要步骤包括创建加密器、设置密码和算法、加密数据以及解密数据。
     * <p>
     * 自定义地使用不同的算法进行加密解密
     */
    private static void changeAlgorithmExample() {
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        System.out.println("---------");
        // 设置加密器的密码
        //encryptor.setPassword(generateSecurePassword());
        encryptor.setPassword("xxxxxxxxxxx");
        // 设置加密器使用的加密算法
        encryptor.setAlgorithm("PBEWithMD5AndTripleDES");

        // 使用加密器对文本进行加密
        String encryptedText = encryptor.encrypt("xxxxxxxxxxxxxxxx*");
        System.out.println("encryptedText:" + encryptedText);

        // 使用加密器对加密后的文本进行解密
        String decryptedText = encryptor.decrypt(encryptedText);
        System.out.println("decryptedText:" + decryptedText);
    }

    public static void main(String[] args) {
        final String s = basicEncrypt("artisan");
        System.out.println(s);
        System.out.println(basicDecrypted(s));

    }
}
    

org.springframework.boot.env.EnvironmentPostProcessor

在这里插入图片描述

部分配置文件 样例
在这里插入图片描述
在这里插入图片描述


小结

Spring Boot 的 EnvironmentPostProcessor 提供了在应用启动过程中的一个灵活扩展点,适用于有定制配置需求的场景。通过实现该接口,开发者可以在 Spring 应用上下文创建前调整环境配置,提供更强的动态控制和灵活性。

  • EnvironmentPostProcessor 是 Spring Boot 中的一个早期扩展点,允许开发者在 Environment 初始化之后、ApplicationContext 初始化之前操作环境。
  • Spring Boot 使用 SpringFactoriesLoaderMETA-INF/spring.factories 文件中加载 EnvironmentPostProcessor 实现。
  • 此扩展点适合用于动态加载配置、基于环境修改配置等场景。

通过 EnvironmentPostProcessor,开发者可以在 Spring Boot 应用启动的早期阶段,实现对应用环境的自定义配置,以满足特定的需求。

在这里插入图片描述

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

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

相关文章

解决表格出现滚动条样式错乱问题

自定义表格出现滚动条时&#xff0c;会因为宽度不对等导致样式错乱&#xff1b; 解决思路&#xff1a; 监听表格数据的变化&#xff0c;当表格出现滚动条时&#xff0c;再调用更新宽度的方法updateWidth&#xff0c;去改变表格头部的宽度&#xff0c;最终保持表格头部和内容对…

.NET中通过C#实现Excel与DataTable的数据互转

在.NET框架中&#xff0c;使用C#进行Excel数据与DataTable之间的转换是数据分析、报表生成、数据迁移等操作中的常见需求。这一过程涉及到将Excel文件中的数据读取并加载至DataTable中&#xff0c;以便于利用.NET提供的丰富数据处理功能进行操作&#xff0c;同时也包括将DataTa…

albert模型实现微信公众号虚假新闻分类

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

java的JJWT 0.91在jdk21中报错的解决方法

参考了很多其他人的办法&#xff0c;只有这种方式可以解决问题 JSON Web Token&#xff08;缩写 JWT&#xff09; 目前最流行、最常见的跨域认证解决方案&#xff0c;前端后端都需要会使用的东西 如果根据黑马的视频&#xff0c;导入了阿里云OSS的相关依赖&#xff0c;自然不会…

最高提升20倍吞吐量!豆包大模型团队发布全新 RLHF 框架,现已开源!

文章来源&#xff5c;豆包大模型团队 强化学习&#xff08;RL&#xff09;对大模型复杂推理能力提升有关键作用&#xff0c;然而&#xff0c;RL 复杂的计算流程以及现有系统局限性&#xff0c;也给训练和部署带来了挑战。传统的 RL/RLHF 系统在灵活性和效率方面存在不足&#x…

云计算:定义、类型及对企业的影响

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 云计算&#xff1a;定义、类型及对企业的影响 云计算&#xff1a;定义、类型及对企业的影响 云计算&#xff1a;定义、类型及对企…

RSTP的配置

RSTP相对于STP在端口角色、端口状态、配置BPDU格式、配置BPDU的处理方式、快速收敛机制、拓扑变更机制和4种保护特性方面的详细改进说明&#xff1a; 端口角色&#xff1a; STP中定义了三种端口角色&#xff1a;根端口&#xff08;Root Port&#xff09;、指定端口&#xff0…

Fortran安装(vscode+gcc+Python)

编写时间&#xff1a; 2024年11月7日 环境配置&#xff1a; gcc VScode Python 条件&#xff1a; Windows 10 x64 VMware虚拟机 前言 这是我出的第2个关于Fortran安装的教程&#xff0c;由于上一个方法&#xff08;你可以在本专栏里找到&#xff09;对储存空间的要求比较…

Ubuntu18.04+ROS环境+moveit UR5机械臂仿真

目录 仿真环境&#xff1a; 1.下载Universal Robots机器人功能包 2.运行 Gazebo中的仿真模型 3.运行moveit运动规划 4.运行rviz并进行轨迹规划 仿真环境&#xff1a; 本文使用版本&#xff1a;Ubuntu18.04 ros版本&#xff1a;ros-melodic-desktop-full&#xff0c;安装…

华为路由策略配置

一、AS_Path过滤 要求&#xff1a; AR1与AR2、AR2与AR3之间建立EBGP连接 AS10的设备和AS30的设备无法相互通信 1.启动设备 2.配置IP地址 3.配置路由器的EBGP对等体连接&#xff0c;引入直连路由 [AR1]bgp 10 [AR1-bgp]router-id 1.1.1.1 [AR1-bgp]peer 200.1.2.2 as-nu…

echarts引入自定义字体不起作用问题记录

echarts引入自定义字体不起作用问题记录 1、问题描述 初始化界面字体不作用&#xff0c;当界面更新后字体样式正常显示 2、原因描述 这通常是由于字体文件加载延迟导致的。ECharts 在初始化时可能还没有加载完字体文件&#xff0c;因此无法正确应用字体样式 3、解决方案 …

Sql server 备份还原方法

备份 方法1&#xff0c;选择对应的数据库名-------》右键 任务---------》备份 默认备份类型 完整 文件后缀 .bak 方法2,选择对应的数据库名-------》右键 任务----------》生成脚本 选择要编写的数据库对象(表&#xff0c;视图&#xff0c;存储过程等) 选择对应的 服…

QT鼠标事件

QT鼠标事件 1.概述 这篇文章介绍如何使用事件和获取事件的信号 2.创建项目 创建一个widget类型项目&#xff0c;在widget.ui文件中添加一个label控件 然后在项目名称上右键选择Add new... 添加文件&#xff0c;选择 C Class 自定义类名Mylabel&#xff0c;选择基类Base …

【Android】webview常用方法和使用

文章目录 前言一、常见用法二、基础属性webView的常用方法WebViewClient的常用方法WebChromeClient的常用方法WebSettings的相关方法 三、加载流程和事件回调四、webview和JS之间的互相调用总结 五、参考链接 前言 最近项目又用到了webview&#xff0c;在回顾复习一次webview相…

malloc calloc和realloc区别和联系

malloc malloc是一个⼀个动态内存开辟的函数&#xff1a; 它的函数原型是void* malloc (size_t size);这个函数向内存申请⼀块连续可⽤的空间&#xff0c;并返回指向这块空间的指针。 1.如果开辟成功&#xff0c;则返回⼀个指向开辟好空间的指针。 2.如果如果开辟失败&#xf…

Android studio中关于printf和print和println的区别

print:为一般输出&#xff0c;同样不能保留精度格式转化&#xff0c;也不能换行输出&#xff0c;输出需要加上换行符printf:常用于格式转换&#xff0c;但需要注意不是换行输出&#xff0c;只用于精度转换&#xff0c;跟C语言的printf一样的&#xff0c;输出需要加上换行符prin…

计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议

文章目录 一、TCP/IP五层模型&#xff08;重要&#xff09;二、应用层常见的协议三、TCP与UDP3.1 TCP、UDP的区别&#xff08;重要&#xff09;3.2 运行于TCP、UDP上的协议3.3 TCP的三次握手、四次挥手3.3.1 TCP的三次握手3.3.2 TCP的四次挥手3.3.3 随机生成序列号的原因 四、T…

分布式——BASE理论

简单来说&#xff1a; BASE&#xff08;Basically Available、Soft state、Eventual consistency&#xff09;是基于CAP理论逐步演化而来的&#xff0c;核心思想是即便不能达到强一致性&#xff08;Strong consistency&#xff09;&#xff0c;也可以根据应用特点采用适当的方…

【RabbitMQ】07-业务幂等处理

1. 方式一 序列化设置唯一Id。 Beanpublic MessageConverter messageConverter() {Jackson2JsonMessageConverter jjmc new Jackson2JsonMessageConverter();jjmc.setCreateMessageIds(true);return jjmc;}RabbitListener(bindings QueueBinding(value Queue(name "d…

【多线程奇妙屋】你听说过设计模式吗?软件开发中可全局访问一个对象的设计模式——单例模式,工作常用, 建议收藏 ! ! !

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…