springboot监听器模式源码精讲

1.前言

很多时候我们看源码的时候看不下去,其中一个原因是系统往往使用了许多设计模式,如果你不清楚这些设计模式,这无疑增加了你阅读源码的难度。

springboot中就大量使用了设计模式,本文主要介绍其中的一种监听器模式,这是观察者模式中的一种。

作者利用空闲时间去阅读了一下相关的源码,然后决定分享出来,大家相互学习。

这篇文章主要分为3个部分:

第一个部分主要讲下监听模式的几个步骤以及一个简单的例子,这样我们在阅读源码的时候就知道是怎么回事了。

第二个部分是这篇文章的核心,也就是springboot中是如何使用监听模式的。

第三个部分主要是在spingboot中自定义实现监听功能

2.监听模式

实现监听模式的四个步骤:

  • 创建事件
  • 创建事件的监听器
  • 创建广播器
  • 发布事件

我们就按照上面的步骤实现一个监听功能

2.1新建工程

创建一个springboot工程,主要的依赖是这个

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

我们的springboot版本是2.7.17

2.2创建事件

先创建一个基础的模板事件

package com.example.springbootcode.order.event;

public abstract class OrderEvent {
    abstract void desc();
}

这是一个抽象类,里面定义了抽象方法,接下里就创建具体的事件

订单开始创建事件

package com.example.springbootcode.order.event;

public class OrderCreateStartingEvent extends OrderEvent{
    @Override
    public void desc() {
        System.out.print("第一步:开始创建订单,……");
    }
}

订单创建完成事件

package com.example.springbootcode.order.event;

public class OrderCreateFinishEvent extends OrderEvent{
    @Override
    public void desc() {
        System.out.print("最后一步:订单创建完成,……");
    }
}

这里定义了两个事件,都继承了OrderEvent,实现了里面的desc方法

2.3创建监听器

创建一个监听器接口

package com.example.springbootcode.order.event;

public interface OrderListener {
    void onOrderEvent(OrderEvent event);
}

定义了一个监听事件的方法,其参数就是事件,接下来我们实现具体的监听器

订单创建过程监听器

public class OrderLogListener implements OrderListener{
    @Override
    public void onOrderEvent(OrderEvent event) {
        if (event instanceof OrderCreateStartingEvent) {
            event.desc();
        } else if (event instanceof OrderCreateFinishEvent){
            event.desc();
        }
    }
}

监听事件,并调用事件里面的desc()方法

2.4创建广播器

定义事件广播器接口

package com.example.springbootcode.order.event;

public interface EventMulticaster {
    void multicasterEvent(OrderEvent event);

    void addListener(OrderListener listener);

    void removeListener(OrderListener listener);
}

广播器我们可以这样理解:主要复杂监听器的管理,并将事件广播给所有监听器,如果该监听器对广播的事件感兴趣,那么就会处理事件

接下来实现一下具体的广播器

package com.example.springbootcode.order.event;


import java.util.ArrayList;
import java.util.List;

public class OrderEventMulticaster implements EventMulticaster{
    List<OrderListener> listenerList = new ArrayList<>();

    @Override
    public void multicasterEvent(OrderEvent event) {
        listenerList.forEach(listener -> listener.onOrderEvent(event));
    }

    @Override
    public void addListener(OrderListener listener) {
        listenerList.add(listener);
    }

    @Override
    public void removeListener(OrderListener listener) {
        listenerList.remove(listener);
    }
}

这里主要实现了3个方法,事件广播、新增监听器、删除监听器

2.5发布事件

发布事件,其实说白了就是在项目中调用OrderEventMulticaster

@SpringBootApplication
public class SpringbootCodeApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootCodeApplication.class, args);

        OrderEventMulticaster orderEventMulticaster = new OrderEventMulticaster();
        orderEventMulticaster.addListener(new OrderLogListener());
        orderEventMulticaster.multicasterEvent(new OrderCreateStartingEvent());
    }
}

加了两个监听器,然后我们发布了一个订单开始创建事件,控制台会打印"第一步:开始创建订单,……"

到这里为止,一个简单的监听模式就实现了,接下来我们根据这个例子去阅读一下springboot源码中关于监听器的部分。

3.监听模式源码精讲

由于springboot有太多地方使用了监听模式,我们不可能去阅读所有代码,这也不是我写这篇文章的目的。我们只需了解其中的一个事件、一个监听器就知道springboot大概是怎么实现事件的监听和发布。

下面我们以应用程序启动为例,看看其是怎么实现事件的监听和发布。

3.1启动事件

在讲启动事件之前,我们先来看看springboot跟生命周期相关的事件脉络
在这里插入图片描述

由图可以发现,启动程序开始到完成会发布ApplicationStartingEventApplicationPreparedEventApplicationReadyEventApplicationStartedEvent……这些事件。

其实由名字大概就知道它们的意思,这里我们以 ApplicationStartingEvent 这个开始启动事件为例,去看了解其实现过程。

EventObject

是所有事件状态对象的根类, 这是 java.util 包中的 EventObject 类的源代码,里面有个方法 getSource() 用于返回事件最初发生的对象。

ApplicationEvent

提供了一个基本的事件模型,为应用程序中的事件提供了一种通用的表示方式 。

SpringApplicationEvent

提供了一个 与 Spring Boot 应用程序的启动过程相关的事件通用的基类,这里就是类似于我们前面例子中的OrderEvent事件

public abstract class SpringApplicationEvent extends ApplicationEvent {

	private final String[] args;

	public SpringApplicationEvent(SpringApplication application, String[] args) {
		super(application);
		this.args = args;
	}

	public SpringApplication getSpringApplication() {
		return (SpringApplication) getSource();
	}

	public final String[] getArgs() {
		return this.args;
	}
}

总得来说就是获取事件的源和事件相关的参数

ApplicationStartingEvent

这个就是启动事件了,类似于我们例子中的OrderCreateStartingEvent,我们看看这个启动事件的代码

public class ApplicationStartingEvent extends SpringApplicationEvent {

	private final ConfigurableBootstrapContext bootstrapContext;

	public ApplicationStartingEvent(ConfigurableBootstrapContext bootstrapContext, SpringApplication application,
			String[] args) {
		super(application, args);   // Ⅰ
		this.bootstrapContext = bootstrapContext; // Ⅱ
	}

	public ConfigurableBootstrapContext getBootstrapContext() {
		return this.bootstrapContext;
	}

}

它干了下面这些事:

Ⅰ: 调用父类的构造函数,将 application 设置为事件的源,将参数数组 args 设置为事件的相关参数

**Ⅱ:**初始化一个上下文接口,并通过getBootstrapContext()来获取该对象

总得来说这个事件是比较简单的。

3.2监听器

还是一样,我们先上图,先了解一下关于启动程序事件监听器的实现脉络

在这里插入图片描述

EventListener

这是一个标记接口 ,在 Java 编程中常用于为一组类提供一个共同的标记,而不包含任何方法。标记接口通常用于指示类具有某种特定的性质、行为或能力。

ApplicationListener

这是 Spring Framework 中的 ApplicationListener 接口,用于定义应用程序事件监听器,这里就类似于上面例子中的OrderListener

@FunctionalInterface //Ⅰ
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	
	void onApplicationEvent(E event); // Ⅱ

	static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
		return event -> consumer.accept(event.getPayload());
	}

}

Ⅰ: @FunctionalInterface 是 Java 注解,用于标记一个接口,指示它是一个函数式接口。函数式接口是只包含一个抽象方法的接口,通常用于支持 Lambda 表达式和函数引用。

Ⅱ:onApplicationEvent(E event)处理事件,其参数是一个泛型,但必须是继承ApplicationEvent

SmartApplicationListenerGenericApplicationListener主要是为了扩展ApplicationListener,这里就不深入讲解了。springboot这样设计的目的其实就是后续的扩展,而不是把所有东西都写在ApplicationListener,基本上所有的系统都是这么个原则。

LoggingApplicationListener

最后我们的主角登场了,从名字大概可以猜到这是一个跟启动日志记录监听器,它会在启动过程的某个阶段记录日志,下面是核心代码

public class LoggingApplicationListener implements GenericApplicationListener {
    // 省略……
    
	@Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationStartingEvent) {
            onApplicationStartingEvent((ApplicationStartingEvent) event);
        }
        else if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
        }
        else if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent((ApplicationPreparedEvent) event);
        }
        else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
        else if (event instanceof ApplicationFailedEvent) {
            onApplicationFailedEvent();
        }
    }
    // 省略……
}

从代码中可以看到它是监听启动过程的所有事件,其中第一个就是我们上面讲到的ApplicationStartingEvent开始启动事件,这里类似于我们上面例子中的OrderLogListener

3.3广播器

跟上面一样,我们还是先上图

在这里插入图片描述

ApplicationEventMulticaster

package org.springframework.context.event;

import java.util.function.Predicate;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;

public interface ApplicationEventMulticaster {

	void addApplicationListener(ApplicationListener<?> listener);

	void addApplicationListenerBean(String listenerBeanName);

	void removeApplicationListener(ApplicationListener<?> listener);

	void removeApplicationListenerBean(String listenerBeanName);

	void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);

	void removeApplicationListenerBeans(Predicate<String> predicate);

	void removeAllListeners();

	void multicastEvent(ApplicationEvent event);

	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

定义事件广播器接口,类似于前面例子中的EventMulticaster

AbstractApplicationEventMulticaster

public abstract class AbstractApplicationEventMulticaster
		implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
    
    //省略……
    
    @Override
	public void addApplicationListener(ApplicationListener<?> listener) {
		synchronized (this.defaultRetriever) {
			// Explicitly remove target for a proxy, if registered already,
			// in order to avoid double invocations of the same listener.
			Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
			if (singletonTarget instanceof ApplicationListener) {
				this.defaultRetriever.applicationListeners.remove(singletonTarget);
			}
			this.defaultRetriever.applicationListeners.add(listener);
			this.retrieverCache.clear();
		}
	}
    
    //省略……
    
    @Override
	public void removeApplicationListener(ApplicationListener<?> listener) {
		synchronized (this.defaultRetriever) {
			this.defaultRetriever.applicationListeners.remove(listener);
			this.retrieverCache.clear();
		}
	}
    
}

这是一个抽象类,实现了监听器的新增和删除的具体逻辑,这样做的目的就是封装一些通用的功能,至于说要不要加一个这样的抽象类完全就是根据你的实际业务情况而定。

SimpleApplicationEventMulticaster

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    
    //省略……
    
    @Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

	@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);
			}
		}
	}
    
    //省略……
    
    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()) ||
					(event instanceof PayloadApplicationEvent &&
							matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception.
				Log loggerToUse = this.lazyLogger;
				if (loggerToUse == null) {
					loggerToUse = LogFactory.getLog(getClass());
					this.lazyLogger = loggerToUse;
				}
				if (loggerToUse.isTraceEnabled()) {
					loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}
}

这个类继承AbstractApplicationEventMulticaster,并且实现了multicastEvent()广播事件,这类似于例子中的OrderEventMulticaster

3.4发布事件

前面所有东西都准备好了,看看程序启动时是如何监听事件的。我们从启动类开始跟跟踪

@SpringBootApplication
public class SpringbootCodeApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootCodeApplication.class, args);
    }
}

进入run方法

public class SpringApplication {
    // 省略代码
    
    
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		// Ⅰ:SpringApplication  Ⅱ:run
        return new SpringApplication(primarySources).run(args); 
	}
    
    
    public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}
	
    // Ⅲ
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // Ⅳ
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
    
    // Ⅴ
    public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
		this.listeners = new ArrayList<>(listeners);
	}
    
}

:我们先进入SpringApplication构造函数,最后我们发现会来到Ⅰ->Ⅲ->Ⅳ->Ⅴthis.listeners存储了启动时需要加载的监听器。

我们看看this.listeners存储了哪些监听器

在这里插入图片描述

我们可以发现LoggingApplicationListener监听器也保存在里面,接下来就是怎么把这些监听器加载到广播器中

构造函数实际上时初始化一些变量

还记得上面**Ⅱ:**run这个注释吗,构造函数执行完后,开始执行run方法,我们进入run方法

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // Ⅵ
    listeners.starting(bootstrapContext, this.mainApplicationClass);
 	// 省略代码
    
    return context;
}

**Ⅵ:**这里就是发布一个启动开始事件了,进入starting方法

package org.springframework.boot;
class SpringApplicationRunListeners {

	// Ⅰ
	void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
		doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
				(step) -> {
					if (mainApplicationClass != null) {
						step.tag("mainApplicationClass", mainApplicationClass.getName());
					}
				});
	}
	// 省略代码

}

这里都是发布启动相关的事件,starting里面调用了doWithListeners方法,其中最主要的是第二个参数,是一个表达式,我们进去看看,最后我们来到这里

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
	//省略代码
    
    // 前面已经被初始化过
	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
        // Ⅰ new广播器
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
            // Ⅱ 添加监听器,application.getListeners()获取的就是前面的this.listeners
			this.initialMulticaster.addApplicationListener(listener);
		}
	}
	//省略代码
    
	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {

		this.initialMulticaster
            // Ⅲ 广播事件
			.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}

    //省略代码
}

这里的步骤就像我们上面例子中的一样

@SpringBootApplication
public class SpringbootCodeApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootCodeApplication.class, args);

        OrderEventMulticaster orderEventMulticaster = new OrderEventMulticaster();
        orderEventMulticaster.addListener(new OrderLogListener());
        orderEventMulticaster.multicasterEvent(new OrderCreateStartingEvent());
    }
}

new一个广播器,然后添加监听器,最后是广播事件。

4.自定义监听器

了解了spingboot的监听模式后,接下来我们看看如何在springboot项目中使用它。

4.1创建事件

package com.example.springbootcode.my;

import org.springframework.context.ApplicationEvent;

public class UserMsgEvent extends ApplicationEvent {

    public UserMsgEvent(Object source) {
        super(source);
    }
	
    // 自定义自己的逻辑方法
    public void sendMsg(){
        System.out.print("向用户发送消息");
    }
}

这里需要注意的是我们继承的是ApplicationEvent,不继承SpringApplicationEvent主要是因此其跟启动相关的,我们定义的事件明显跟应用启动没啥关系。

4.2创建监听器

package com.example.springbootcode.my;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class UserListener implements ApplicationListener<UserMsgEvent> {

    @Override
    public void onApplicationEvent(UserMsgEvent event) {
        event.sendMsg();
    }
}

这里继承ApplicationListener

4.3自定义触发事件

package com.example.springbootcode.my;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final ApplicationEventPublisher eventPublisher;

    public UserService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void send(){
        eventPublisher.publishEvent(new UserMsgEvent(this));
    }
}

通过构造函数注入 ApplicationEventPublisher , 这是一个Spring框架提供的接口,用于发布事件

最后在需要调用的地方调用UserService里面的send方法即可

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

谈谈 .NET8 平台中对 LiteDB 的 CRUD 操作

哪个啥&#xff01;纯 C# 编写的 LiteDB 你还不会操作&#xff1f; LiteDB 简介LiteDB 安装1、同步版 LiteDB2、异步版 LiteDB.Async LiteDB StudioLiteDB CRUD 操作举例1、.net cli 命令创建项目2、项目添加相关 nuget 包3、改造项目结构4、改造项目代码 LiteDB vs SQLite 对比…

泳道图绘制全攻略,一图胜千言,快速上手

泳道图是一种流程图的形式&#xff0c;通过在不同的泳道中展示不同的参与者&#xff0c;帮助我们更好地理解和分析流程。它是一种非常有用的工具&#xff0c;可以帮助我们在团队协作、流程管理和问题解决等方面取得更好的效果。 1. 泳道图的定义 泳道图是一种以泳道为基础的流程…

postgresql从入门到精通 - 第37讲:postgres物理备份和恢复概述

PostgreSQL从小白到专家&#xff0c;是从入门逐渐能力提升的一个系列教程&#xff0c;内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容&#xff0c;希望对热爱PG、学习PG的同学们有帮助&#xff0c;欢迎持续关注CUUG PG技术大讲堂。 第37讲&#…

提高工厂能源效率的关键:工厂能耗监测平台

工业做为能源消耗的重要场所&#xff0c;所以节能减排对工业来讲是一个亟需解决的问题。除了对设备进行更新换代外&#xff0c;还需要能源管理消耗监测平台&#xff0c;帮助企业实现节能减排的目标。 工厂能源消费量非常庞大&#xff0c;能源比较难以监测与控制。传统能源的管…

路径规划之RRT算法

系列文章目录 路径规划之Dijkstra算法 路径规划之Best-First Search算法 路径规划之A *算法 路径规划之D *算法 路径规划之PRM算法 路径规划之RRT算法 路径规划之RRT算法 系列文章目录前言一、RRT算法1.起源2.流程3. 优缺点3.1 优点3.2 缺点 4. 实际效果 前言 PRM方法相比于传…

正则表达式(3):入门

正则表达式&#xff08;3&#xff09;&#xff1a;入门 小结 本博文转载自 从这篇文章开始&#xff0c;我们将介绍怎样在Linux中使用”正则表达式”&#xff0c;如果你想要学习怎样在Linux中使用正则表达式&#xff0c;这些文章就是你所需要的。 在认识”正则表达式”之前&am…

图像处理之把模糊的图片变清晰

1.图片如果是有雾化效果的对图像产生影响的,要先进行图形增强,Retinex是基于深度神经网络了,我在之前图形处理的文章一路从神经网络(概率统计)—>积卷神经网络(对区域进行概率统计,对图片进行切割多个识别对象)–>深度积卷神经网络(RetinexNet也是模拟人脑的处理过程,增加…

挑选分支中某一个提交进行合并

复制提交的哈希(sha-1)值 挑选提交 git cherry-pick 复制过来的哈希值 若有冲突&#xff0c;解决冲突&#xff0c;没有冲突&#xff0c;即合并完成

C语言普里姆(Prim)算法实现计算国家建设高铁运输网最低造价的建设方案

背景&#xff1a; 描述&#xff1a;为促进全球更好互联互通&#xff0c;亚投行拟在一带一路沿线国家建设高铁运输网&#xff0c;请查阅相关资料 画出沿线国家首都或某些代表性城市的连通图&#xff0c;为其设计长度最短或造价最低的高铁建设方案。 要求&#xff1a;抽象出的图…

Linux-进程之间的通信

目录 ​编辑 一.什么是进程之间的通信 二.进程之间的通信所访问的数据 三.进程之间的通信是如何做到的 四.基于内存文件级别的通信方式——管道 1.什么是管道 2.管道的建立过程——匿名管道 a.什么是匿名管道 b.匿名管道特点&#xff1a; c.使用匿名管道的…

Peter算法小课堂—贪心算法

课前思考&#xff1a;贪心是什么&#xff1f;贪心如何“贪”&#xff1f; 课前小视频&#xff1a;什么是贪心算法 - 知乎 (zhihu.com) 贪心 贪心是一种寻找最优解问题的常用方法。 贪心一般将求解过程分拆成若干个步骤&#xff0c;自顶向下&#xff0c;解决问题 太戈编程第…

邮政单号查询,邮政快递物流查询,并进行提前签收分析

批量查询邮政快递单号的物流信息&#xff0c;并将提前签收件分析筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 邮政快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的朋友记得先注册&#xff0c…

关于svn如何上传一个完整的项目

注意&#xff1a;请一定要按照该步骤进行操作&#xff0c;请上传新项目时将项目名称进行规范命名 例如原始文件是arrange_v2 将此项目需要注入新的医院 则命名为 arrange_某医院名称_门诊或者医技或者药房_v2 重新命名文件夹名称快捷键 &#xff08;F12&#xff09; 一 &…

【Linux】公网远程访问AMH服务器管理面板

目录 1. Linux 安装AMH 面板2. 本地访问AMH 面板3. Linux安装Cpolar4. 配置AMH面板公网地址5. 远程访问AMH面板6. 固定AMH面板公网地址 AMH 是一款基于 Linux 系统的服务器管理面板&#xff0c;它提供了一系列的功能&#xff0c;包括网站管理、FTP 管理、数据库管理、DNS 管理、…

UI自动化测试工具的定义及重要性

UI自动化测试工具在现代软件开发中起着不可或缺的作用。它们能够提高测试效率、减少人为错误、提供全面的测试覆盖&#xff0c;并支持持续集成。通过有效使用UI自动化测试工具&#xff0c;开发团队可以提高软件质量&#xff0c;提供更可靠的应用程序&#xff0c;满足用户的需求…

Jsoup爬取HTTPS页面数据资源,并导入数据库(Java)

一、实现思路 示例页面&#xff1a; 2020年12月中华人民共和国县以上行政区划代码 忽略https请求的SSL证书通过Jsoup获取页面标签遍历行标签&#xff0c;分别获取每个行标签的第二个和第三个列标签将获取到的行政代码和单位名称分别插入sql语句占位符执行sql语句&#xff0c…

掌汇云 | 全场景数据追踪,多维了解用户偏好,提高运营效率

掌汇云拥有黄金“三件套”&#xff1a;掌头条、汇互动、云品牌。群硕借助这些功能套件&#xff0c;面向细分领域如&#xff1a;会展&#xff0c;食品饮料、医药以及工业等&#xff0c;定制综合性信息服务平台&#xff0c;提供资讯、商机、企业人脉、上下游资源、活动等高质量服…

<软考>软件设计师-3程序设计语言基础(总结)

(一) 程序设计语言概述 1 程序设计语言的基本概念 1-1 程序设计语言的目的 程序设计语言是为了书写计算机程序而人为设计的符号语言&#xff0c;用于对计算过程进行描述、组织和推导。 1-2 程序语言分类 低级语言 : 机器语言&#xff08;计算机硬件只能识别0和1的指令序列)&…

docker网络【重点】

一、网络知识 1、桥接模式&#xff1a;用于链接两个不同网络段的设备&#xff0c;是共享通信的一种方式 2、桥接设备&#xff1a;工作在OSI模型的第二层&#xff08;数据链路层&#xff09;。根据MAC地址转发数据帧&#xff0c;类似于交换机&#xff0c;只能转发同一网段&…

使用Inno Setup 打包程序文件 怎么把其中一个文件安装时复制到指定系统文件夹

环境: Inno Setup 6.6 Win10 专业版 问题描述: 使用Inno Setup 打包程序文件 怎么把其中一个文件安装时复制到指定系统文件夹 将文件api-ms-win-shcore-scaling-l1-1-1.dll复制到system32里面 解决方案: 1.由于安全和权限的限制,直接在Inno Setup脚本中复制文件到C:\…