springBoot源码分析如何加载配置文件

news2024/11/23 21:38:13

前言:springBoot的版本是  2.2.4.RELEASE

一、入口

/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 主要看这个方法,准备环境
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

查看配置文件主要是看 prepareEnvironment() 这个方法,即下面这个方法

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

二、prepareEnvironment 方法实现

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                       ApplicationArguments applicationArguments) {
        // Create and configure the environment
        // 创建并配置相应的环境
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 根据用户配置,配置 environment 系统环境
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        // 启动对应的监听器,其中一个重要的监听器 ConfigFileApplicationListener 就是加载配置文件的监听器    
        //通过这个 ConfigFileApplicationListener 监听器就可以把 application.properties 或者application.yml 的配置信息给加载到容器中
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

2.1 第一行:getOrCreateEnvironment 方法实现

引:根据 webApplicationType 确定应用容器环境 Servlet

//根据 webApplicationType 确定应用容器环境 Servlet
    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        // 如果应用类型是 SERVLET,则实例化 StandardServletEnvironment
        switch (this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
        }
    }

2.2  第二行:configureEnvironment 方法实现

 /**
     * Template method delegating to
     * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
     * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
     * Override this method for complete control over Environment customization, or one of
     * the above for fine-grained control over property sources or profiles, respectively.
     * @param environment this application's environment
     * @param args arguments passed to the {@code run} method
     * @see #configureProfiles(ConfigurableEnvironment, String[])
     * @see #configurePropertySources(ConfigurableEnvironment, String[])
     */
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
        // 将 main 函数的 args 封装成 SimpleCommandLinePropertySource 加入环境中
        configurePropertySources(environment, args);
        // 激活相应的配置文件
        configureProfiles(environment, args); //跟项目的多个运行环境有关,如:test\dev\prod 等
    }

2.3 第三行: attach 方法实现

该方法不是重点,暂时不分析

2.4 第四行:environmentPrepared 方法实现,最重要的方法

引:监听器的遍历

void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

2.4.1  继续跟踪 environmentPrepared 方法实现

注:environmentPrepared 方法是  SpringApplicationRunListener 的一个默认接口

截图:

 代码:

/**
	 * Called once the environment has been prepared, but before the
	 * {@link ApplicationContext} has been created.
	 * @param environment the environment
	 */
	default void environmentPrepared(ConfigurableEnvironment environment) {
	}

查看该接口的具体实现类:EventPublishingRunListener

截图:

 代码:

@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

这里有个初始化传播性事件接口,接着继续跟踪这个 multicastEvent 方法的具体实现,该接口是

SimpleApplicationEventMulticaster的方法

截图:

 代码:

@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

接着继续查看 multicastEvent 方法实现

截图:

代码:

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

 继续查看 invokeListener 方法实现

/**
	 * Invoke the given listener with the given event.
	 * @param listener the ApplicationListener to invoke
	 * @param event the current event to propagate
	 * @since 4.1
	 */
	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}

在这个方法中可以看到 以 do 开头的方法,在 springBoot中以 do 开头的方法一般都是真正干苦力的方法,继续接着看 doInvokeListener 方法实现

@SuppressWarnings({"rawtypes", "unchecked"})
	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception and just log a debug message.
				Log logger = LogFactory.getLog(getClass());
				if (logger.isTraceEnabled()) {
					logger.trace("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

这里有个  onApplicationEvent 应用发布事件,这是一个接口,具体找到配置文件的实现类,启动相应的监听器,EventPubshiingListener通知其他的监听器,这里会通知ConfigFileApplicationListener这个监听器来加载环境中的配置文件

截图:

查看该方法有哪些实现类,找到 ConfigFileApplicationListener 这个具体的实现类

截图:

 ConfigFileApplicationListener 具体的实现类

查看第一个 onApplicationEnvironmentPreparedEvent 方法,应用程序环境准备事件

 继续查看红框 postProcessEnvironment 方法实现

 这个方法点过来是一个接口,继续看这个接口的具体实现,还是回到具体实现类 ConfigFileApplicationListener

截图:

 实现类代码:

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}

 继续跟踪  addPropertySources 方法实现

截图:

 代码:

/**
	 * Add config file property sources to the specified environment.
	 * @param environment the environment to add source to
	 * @param resourceLoader the resource loader
	 * @see #addPostProcessors(ConfigurableApplicationContext)
	 */
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        //添加与随机数相关的配置源
		RandomValuePropertySource.addToEnvironment(environment);
        //Load 类最终负责加载配置文件
		new Loader(environment, resourceLoader).load();
	}

 Loader类是 ConfigFileApplicationListener 内部类,查看下 Loader 的构造方法

截图:

代码:

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
			this.environment = environment;
			this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
			this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
            //从 spring.factories 文件中加载 PropertySourceLoader
            //PropertySourceLoader 有两个实现类:PropertiesPropertySourceLoader 和
            //YamlPropertySourceLoader,分别用于加载文件名后缀为 Properties 和 yaml 的文件
			this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
					getClass().getClassLoader());
		}

 接着看 load 方法的实现

截图:

 

代码:

void load() {
			FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
					(defaultProperties) -> {
						this.profiles = new LinkedList<>();
						this.processedProfiles = new LinkedList<>();
						this.activatedProfiles = false;
						this.loaded = new LinkedHashMap<>();
                        //initializePropfiles 从多个配置源加载设置的 profile,
                        //配置源可以是:环境变量、启动参数"--"设置、Environment对象设置等
                        //可以通过属性名 spring.profiles.include或 spring.profiles.active指定profile
                        //无论上述配置源有没有设置profile,都会在profiles属性中增加null,
                        //这是为了保证能首先处理默认的配置文件
						initializeProfiles();
                        //遍历 profile 
						while (!this.profiles.isEmpty()) {
							Profile profile = this.profiles.poll();
							if (isDefaultProfile(profile)) {
								addProfileToEnvironment(profile.getName());
							}
                            //读取配置文件,下面分析该方法
							load(profile, this::getPositiveProfileFilter,
									addToLoaded(MutablePropertySources::addLast, false));
							this.processedProfiles.add(profile);
						}
                        //读取application.properties 配置文件
                        //如果 application.properties 中没有spring.profiles属性,那么下面这个方法不会加载任何内容
						load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
                        //将配置文件作为配置源添加到 Environment 对象中
                        //以后获取配置可以通过 Environment 获取
						addLoadedPropertySources();
                        //将 profile 设置到 Environment 对象中
						applyActiveProfiles(defaultProperties);
					});
		}

 

接着看红框的方法 load 的实现

截图:

 

 

代码:

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
            //getSearchLocations()方法获得加载配置文件的路径
            //然后遍历这些路径
			getSearchLocations().forEach((location) -> {
				boolean isFolder = location.endsWith("/");
                //查找配置文件名,可以通过 spring.config.name 指定文件名
                //如果没有配置,使用默认名 application
				Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
                //接着load方法实现
				names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

 接着上面红框1中 getSearchLocations 方法的实现:

截图:

代码:

//获取加载配置文件的路径
//可以通过spring.config.location配置设置路径,如果没有配置,则使用默认
//默认路径由 DEFAULT_SEARCH_LOCATIONS 指定:
//String DEFAULT_SEARCH_LOCATIONS ="classpath:/,classpath:/config/,file:./,file:/config/"
private Set<String> getSearchLocations() {
			if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
				return getSearchLocations(CONFIG_LOCATION_PROPERTY);
			}
			Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
			locations.addAll(
					asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
			return locations;
		}

  接着上面红框2中load方法的实现:

截图:

 

 

 代码:

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
            //下面的if分支默认是不走的,除非我们设置spring.config.name为空或者null
            //或者是spring.config.location指定了配置文件的完整路径,也就是入参location的值
			if (!StringUtils.hasText(name)) {
				for (PropertySourceLoader loader : this.propertySourceLoaders) {
                    //检查配置文件名的后缀是否符合要求
                    //文件名后缀要求是properties、xml、yml或者yaml
					if (canLoadFileExtension(loader, location)) {
                        //加载location指定的文件,下面的load方法不做介绍
                        //其原理和下面将要调用的 loadForFileExtension 方法类似
						load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
						return;
					}
				}
				throw new IllegalStateException("File extension of config file location '" + location
						+ "' is not known to any PropertySourceLoader. If the location is meant to reference "
						+ "a directory, it must end in '/'");
			}
			Set<String> processed = new HashSet<>();
            //properSourceLoaders 属性是在 Load 类的构造方法中设置的,可以加载文件后缀为 properties、xml、yml或者yaml的配置文件
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
                //获取文件拓展名进行遍历
				for (String fileExtension : loader.getFileExtensions()) {
					if (processed.add(fileExtension)) {
                        //将路径、文件名、后缀名合起来形成完整文件名
						loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
								consumer);
					}
				}
			}
		}

接着看截图红框中的方法  loadForFileExtension的实现

截图:

 

代码:

private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,
				Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
			DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
			if (profile != null) {
                //在文件名上加上 profile 值,之后调用load方法加载配置文件,入参带有过滤器,可以防止重复加载
				// Try profile-specific file & profile section in profile file (gh-340)
				String profileSpecificFile = prefix + "-" + profile + fileExtension;
				load(loader, profileSpecificFile, profile, defaultFilter, consumer);
				load(loader, profileSpecificFile, profile, profileFilter, consumer);
				// Try profile specific sections in files we've already processed
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;
						load(loader, previouslyLoaded, profile, profileFilter, consumer);
					}
				}
			}
            //加载不带 profile 的配置文件
			// Also try the profile-specific section (if any) of the normal file
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}

 接着看红框的 load 实现方法

截图:

 

代码:

//加载配置文件
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
			try {
                //调用 Resource 类加载配置文件
				Resource resource = this.resourceLoader.getResource(location);
				if (resource == null || !resource.exists()) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped missing config ", location, resource,
								profile);
						this.logger.trace(description);
					}
					return;
				}
				if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped empty config extension ", location,
								resource, profile);
						this.logger.trace(description);
					}
					return;
				}
				String name = "applicationConfig: [" + location + "]";
                //读取配置文件内容,将其封装到 Document 类中,解析文件内容主要是找到
                //配置 spring.profiles.active 和 spring.profiles.include 的值
				List<Document> documents = loadDocuments(loader, name, resource);
                //如果文件没有配置数据,则跳过
				if (CollectionUtils.isEmpty(documents)) {
					if (this.logger.isTraceEnabled()) {
						StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
								profile);
						this.logger.trace(description);
					}
					return;
				}
				List<Document> loaded = new ArrayList<>();
                //遍历配置文件,处理里面配置的 profile
				for (Document document : documents) {
					if (filter.match(document)) {
                        //将配置文件中配置的 spring.profiles.active 和
                        //spring.profiles.include 的值写入集合 profiles 中
                        //上层调用方法会读取 profiles 集合中的值,并读取对应的配置文件
                        //addActiveProfiles 方法只在第一次调用时会其作用,里面有判断
						addActiveProfiles(document.getActiveProfiles());
						addIncludedProfiles(document.getIncludeProfiles());
						loaded.add(document);
					}
				}
				Collections.reverse(loaded);
				if (!loaded.isEmpty()) {
					loaded.forEach((document) -> consumer.accept(profile, document));
					if (this.logger.isDebugEnabled()) {
						StringBuilder description = getDescription("Loaded config file ", location, resource, profile);
						this.logger.debug(description);
					}
				}
			}
			catch (Exception ex) {
				throw new IllegalStateException("Failed to load property source from location '" + location + "'", ex);
			}
		}

 

 参考文章:https://www.cnblogs.com/fnlingnzb-learner/p/16800570.html

 

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

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

相关文章

DM5加密

MD5加密 概述 MD5&#xff08;Message Digest Algorithm 5&#xff09;是一种常用的哈希函数&#xff0c;能够将任意长度的消息压缩成一个128位的哈希值。它由Ronald Rivest在1991年设计&#xff0c;其设计目标是用来取代较早的MD4算法。 MD5算法通过多次处理分组数据来生成…

2023年5月青少年机器人技术等级考试理论综合试卷(三级)

青少年机器人技术等级考试理论综合试卷&#xff08;三级&#xff09;2023.06 分数&#xff1a; 100 题数&#xff1a; 30 一、 单选题(共 20 题&#xff0c; 共 80 分) 1.如图所示电路&#xff0c; 下列说法正确的是&#xff1f; &#xff08; &#xff09; A.电路中电阻 R3 和…

Kafka有几种消费者分区分配策略?

Range范围分配策略 Range范围分配策略是Kafka默认的分配策略&#xff0c;它可以确保每个消费者消费的分区数量是均衡的。 注意&#xff1a;Rangle范围分配策略是针对每个Topic的。 配置 配置消费者的partition.assignment.strategy为org.apache.kafka.clients.consumer.Ran…

JMeter如何从数据库中获取数据并作为变量使用?

目录 前言 1、JMeter连接MySQL数据库 2、线程组下新建一个 JDBC Connection Configuration 配置元件 3、实现数据库的查询-单值引用 4、实现数据库的查询-多值引用 总结&#xff1a; 前言 JMeter如何从数据库中获取数据并作为变量使用&#xff1f;这在我们使用JMeter做接…

Selenium Python教程第5章

5. 等待页面加载完成(Waits) 现在的大多数的Web应用程序是使用AJAX技术。当一个页面被加载到浏览器时&#xff0c;该页面内的元素可以在不同的时间点被加载。这使得定位元素变得困难。如果元素不再页面之中&#xff0c;会抛出 ElementNotVisibleException 异常。 使用 waits功能…

蓝桥杯单片机定时器不够用?PCA大力助你测距超声波!

在国赛的练习中遇到了定时器不够用的问题&#xff0c;也在网上有查阅到许多蓝桥杯单片机的用PCA定时器测距超声波的例子&#xff0c;但在移植实践运用了几个人的代码后总是各种各样的的问题不好用&#xff0c;因此深感有必要自己好好研究下&#xff0c;终于在一番摸爬中写出了用…

Shapr3d建模制图软件大学生教育优惠免费1年申请教程

前言介绍 shapr3d是iOS系统上一款专业的3D建模App&#xff0c;目前已经有window版本&#xff0c;主要搭配iPad Pro与Apple Pencil使用&#xff0c;它的特点是易学、方便与专业。我们可以使用它可以在几分钟内就可以把草图上的想法变成设计图。 从工程项目到珠宝设计&#xff…

ADAudit Plus:保护企业信息安全的强大内部审计解决方案

内部安全审计在现代企业中扮演着至关重要的角色。它是确保组织网络和系统安全的关键步骤&#xff0c;帮助企业发现和解决潜在的安全风险和漏洞。在这个信息技术高度发达的时代&#xff0c;保护企业的敏感数据和防范内部和外部威胁变得尤为重要。 内部安全审计 ADAudit Plus是一…

Windows本地账号数据迁移工具,可迁移本地账号数据到域账号,包括配置文件,桌面文件,浏览器收藏,聊天记录等。

Transwiz可以很容易地将您的个人数据和设置转移到新计算机: 用于备份和恢复用户配置文件的简单向导界面 下载链接 链接:https://pan.baidu.com/s/1LWmplUgHYg9ut3QLMnFslg?pwd=ogpx 提取码:ogpx 以下为Transwiz工具的使用教程实录: 创建一台虚拟机,我在上面简单模拟…

基于STL的 演讲比赛流程管理系统

目录 一、演讲比赛程序要求 1、比赛规则 2、程序功能 二、创建管理类 三、菜单功能 四、退出功能 五、演讲比赛功能 1、功能分析 2、创建选手类 3、进行比赛 4、保存分数 六、查看比赛记录 1、读取记录分数 2、查看记录功能 3、bug解决 七、清空功能 一、演讲比赛…

【Linux】信号(上)

文章目录 &#x1f4d5; 信号入门生活角度的信号技术角度的信号 &#x1f4d5; 信号产生认识 signal 函数键盘产生信号通过系统调用产生信号软件条件产生信号硬件异常产生信号 &#x1f4d5; 核心转储&#x1f4d5; 信号保存信号集函数 &#x1f4d5; 信号处理用户态与内核态处…

如何通过桥接模式重构代码?

文章目录 什么是桥接模式&#xff1f;UML结构图通用代码实现适用场景案例场景分析⽤⼀坨坨代码实现桥接模式重构代码代码实现⽀付类型桥接抽象类⽀付类型的实现定义⽀付模式接⼝测试 总结 同类的业务、同样的功能&#xff0c;怎么就你能写出来那么多if else。 很多时候你写出来…

【Web服务器】Tomcat的部署

文章目录 前言一、Tomcat 的概念1. Tomcat 核心组件1.1 什么是 servlet1.2 什么是 JSP 2. Tomcat 功能组件结构2.1 Container 结构分析 3. Tomcat 请求过程4. 配置文件4.1 安装目录4.2 conf 子目录 二、Tomcat 服务部署1. 下载并安装 JDK1.1 关闭防火墙&#xff0c;将安装 Tomc…

Milvus Lite 已交卷!轻量版 Milvus,主打就是一个轻便、无负担

想要体验世界上最快的向量数据库&#xff1f;缺少专业的工程师团队作为支撑&#xff1f;Milvus 安装环境受限&#xff1f; 别担心&#xff0c;轻量版 Milvus 来啦&#xff01; 在正式介绍 Milvus Lite 之前&#xff0c;先简单回顾一下 Milvus。Milvus 是一款开源的向量数据库&a…

logstash启动时默认连接本机节点elasticsearch问题

背景 今天在排查处理一个logstash读取kafka数据写入到hdfs的问题时候&#xff0c;发现在启动日志中多了个 logstash.outputs.elasticsearch 连接的地址是localhost:9200 部分日志如下&#xff1a; 排查过程说明 1、首先确认 logstash 启动的配置文件中的 output 配置&#x…

SonarQube Data Center Edition 10.0 Crack

使用{SonarQube}为团队和企业提供干净的代码 SonarQube Data Center Edition为开发团队提供深度集成到企业环境中的代码质量和安全解决方案;使您能够一致且可靠地部署干净的代码。 灵活性和治理&#xff1a;完美的企业代码质量工具 与您的企业环境深度集成 自我管理&#xff0…

【软件测试】软件测试的基本概念和开发模型

1. 前言 在进行软件测试的学习之前,我们要了解软件测试一些基本概念. 这些基本概念将帮助我们更加明确工作的目标以及软件测试到底要做什么. 2. 软件测试的基本概念 软件测试的基本概念有3个,分别是需求,测试用例和BUG. 2.1 需求 这里的需求还可以分为 用户需求和软件需求,用户…

【博客646】将消息队列放在Prometheus前以提高可靠性并不总是“好主意“

将队列放在Prometheus前以提高可靠性并不总是"好主意" 为了防止突发流量&#xff0c;而在prometheus前加上消息队列以达到削峰填谷的目的 架构如下&#xff1a; 应用程序将指标推送到某种形式的队列&#xff08;通常是 Kafka&#xff09;&#xff0c;暴露器二进制…

超详细IDEA创建Maven项目

文章目录 一、Maven概述二、创建Maven项目三、Maven项目简单介绍3.1 标准化的项目结构3.2 标准化的构建流程3.3 方便的依赖管理 一、Maven概述 Maven是一个专门用于管理和构建Java项目的工具。我们之所以要使用Maven&#xff0c;是因为Maven可以为我们提供一套标准化的项目结构…

Python单元测试框架《python 自动化框架 pytest》

​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; Pytest 简介 pytest 是python 的一种单元测试框架&#xff0c;不python 自带的unittest 测试框架类似&#xff0c;但是比 unittest 框架使用起来更简洁&#xff0c;效率更高。根据pyt…