springboot监听器模式源码精讲

news2024/11/26 4:54:14

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方法即可

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

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

相关文章

mybatis 的快速入门以及基于spring boot整合mybatis

MyBatis基础 MyBatis是一款非常优秀的持久层框架&#xff0c;用于简化JDBC的开发 准备工作&#xff1a; 1&#xff0c;创建sprong boot工程&#xff0c;引入mybatis相关依赖2&#xff0c;准备数据库表User&#xff0c;实体类User3&#xff0c; 配置MyBatis&#xff08;在applic…

流媒体方案之FFmpeg——实现物联网视频监控项目

目录 前言 一、FFmpeg介绍 二、FFmpeg简易理解 三、FFmpeg的重要概念 四、软硬件准备 五、移植、运行FFmpeg 六、运行FFmpeg 前言 最近想做一个安防相关的项目&#xff0c;所以跟着韦东山老师的视频来学习视频监控方案的相关知识&#xff0c;韦东山老师讲的课非常好&…

消息中间件之间的区别

一.单机吞吐量 ActiveMQ&#xff1a;万级&#xff0c;吞吐量比RocketMQ和Kafka要低了一个数量级 RabbitMQ&#xff1a;万级&#xff0c;吞吐量比RocketMQ和Kafka要低了一个数量级 RocketMQ&#xff1a;10万级&#xff0c;RocketMQ也是可以支撑高吞吐的一种MQ Kafka&#xff…

当发送“Hello,World”时,channel发生了什么?

一、Netty概述 1.Netty是什么&#xff1f; Netty 是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于快速开发可维护、高性能的网络服务器和客户端。 2.Netty的地位怎么样&#xff1f; Netty 在 Java 网络应用框架中的地位就好比&#xff1a;Spring 框架在 JavaEE …

5个超实用GPT技巧,包括绩效总结、头脑风暴、营销策略等(内附提示词)

今天和大家分享5个用于工作上的GPT技巧&#xff0c;例如进行绩效总结、自我评估、头脑风暴&#xff0c;还是制作PPT方案等等&#xff0c;最大化提升你工作效率&#xff0c;本期内容对于大家来说都非常受用&#xff0c;记得收藏起来哦&#xff01; 那么接下来就直接进入正题吧&a…

力扣题:字符的统计-12.5

力扣题-12.5 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;551. 学生出勤记录 I 解题思想&#xff1a;进行字符的统计即可 class Solution(object):def checkRecord(self, s):""":type s: str:rtype: bool"""fla…

网工内推 | 上市公司初级网工,HCIP认证优先,14薪,享企业年金

01 易佰网络 招聘岗位&#xff1a;初级网络工程师 职责描述&#xff1a; 1.电脑周边设备&#xff08;打印机、扫描仪、传真机、复印机、投影仪等&#xff09;安装与维护&#xff1b; 2.局域网维护&#xff1b;无线网WLAN维护&#xff1b;监控系统维护&#xff1b; 3.固资维护管…

如何使用 Docker 安装 Node-RED

安装 Node-RED 使用 Docker 是一种简便的方式&#xff0c;以下是基本的步骤&#xff1a; 安装 Docker&#xff1a; 确保已在系统上安装 Docker。可从 Docker 官方网站 或 Windows Docker 安装教程 获取安装指南。 拉取运行 Node-RED 镜像&#xff1a; 打开终端或命令行界面&am…

【MySQL】基本安装配置

1 基础知识 1.1 MySQL安装 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 1.1.1 安装过程 配置环境变量&#xff08;和配置Java8的环境变量如出一辙&#xff09;在MySQL解压文件夹下&#xff0c;导入my.ini文件&#xff0c;与bin目录同级&#xff0c;具体文…

关于inline函数声明和定义为什么不可以分离

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 前言 inline函数在使用时声明和定义不可以分离&#xff0c;接下来我们将会解释为什么是这样的。 我们从程序的编译链接说起。 首先我们先介绍一下程序的翻译环境&#xff1a; 编译分以下几个步骤&#xff0c;预处理&#x…

sort by modulus of a complex number

描述 复数E包含实部x和虚部y, Exyi;E的模为: 输入n(<1000)和n对(x,y); 按模数升序对复合体进行排序&#xff0c;如果模数相等&#xff0c;则按输入顺序排序。 排序后输出n行of (x_i,y_i,mod_i)&#xff0c;保留2个十进制小数。 输入 输入n和n对(x,y); 输出 输出排序后的n行(…

Android : AndroidStudio开发工具优化

1.开启 gradle 单独的守护进程 Windows: 进入目录 C:\Users\Administrator\.gradle 创建文件&#xff1a; gradle.properties # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Settings specified in this file will override any Gradle s…

在python的Scikit-learn库中,可以使用train_test_split函数来划分训练集和测试集。

文章目录 一、在Scikit-learn库中&#xff0c;可以使用train_test_split函数来划分训练集和测试集总结 一、在Scikit-learn库中&#xff0c;可以使用train_test_split函数来划分训练集和测试集 在Scikit-learn库中&#xff0c;可以使用train_test_split函数来划分训练集和测试…

深度观察|近视防控乱象下,角膜塑形镜撬动百亿市场的背后…

前不久&#xff0c;“晒眼皮”莫名上了热搜。顾名思义&#xff0c;就是在太阳底下闭眼&#xff0c;让眼皮享受日光浴。 不少近视患者相信&#xff0c;这种做法可以延缓近视&#xff0c;甚至可以让近视度数“清零”。毫无疑问&#xff0c;这是一个收割智商税的做法&#xff0c;…

数字双胞胎:低代码与工业4.0的崭新融合

引言 随着工业4.0的快速发展&#xff0c;数字化转型已经成为制造业的关键课题。在这个数字化浪潮中&#xff0c;"数字双胞胎"技术崭露头角&#xff0c;它将实际世界与数字模拟相连接&#xff0c;提供了更好的生产过程理解、监控和优化方法。但数字双胞胎的构建和维护…

CoreDNS实战(七)-日志处理

本文主要用于介绍CoreDNS用来记录日志的几种方式以及在生产环境中遇到的一些问题和解决方案。 1 log插件 coredns的日志输出并不如nginx那么完善&#xff08;并不能在配置文件中指定输出的文件目录&#xff0c;但是可以指定日志的格式&#xff09;&#xff0c;默认情况下不论…

算法笔记:样条插值

1 什么是样条 样条来源于早期工程制图&#xff0c;为了将一些固定点连成一条光滑的曲线&#xff0c;采用具有弹性的木条固定在这些点上通过样条画出来的曲线不仅经过各固定点&#xff0c;而且连续光滑 2 样条函数 数学上定义成一个分段多项式函数 每两个点之间用一个多项式来…

手机大厂必备测试技能有哪些?CTS 兼容测试首当其冲

这么多手机厂商都要改造成自己的UI&#xff0c;甚至要对Android底层进行改动&#xff0c;难免不会改出什么问题。 所以Google为了这些定制的系统能够符合统一的规范、兼容Android底层的API&#xff0c;就引入了CTS测试。 01 何为CTS 相信小伙伴们都有用过各种款式的Android手…

CUDA简介——编程模式

1. 引言 前序博客&#xff1a; CUDA简介——基本概念 CPU是用于控制的。即&#xff0c;host控制整个程序流程&#xff1a; 1&#xff09;程序以Host代码main函数开始&#xff0c;然后顺序执行。 Host代码是顺序执行的&#xff0c;并执行在CPU之上。Host代码会负责Launch ke…

解析 Smilee Finance:基于无偿损失的链上期权方案

“有了 Smilee Finance&#xff0c;无偿损失或许不再是一种损失&#xff0c;它也更可能是一种可组合性的收益” 无偿损失 流动性挖矿是引燃 DeFi Summer 的导火索&#xff0c;在 AMM DEX 中&#xff0c;它允许用户将资产按照比例添加到 AMM 流动性池中成为 LP&#xff0c;以为交…