异步编程 - 09 Spring框架中的异步执行_@Async注解异步执行原理源码解析
文章目录
- 概述
- 小结
- 好文推荐
概述
在Spring中调用线程将在调用含有@Async注释的方法时立即返回,Spring是如何做到的呢?其实是其对标注@Async注解的类做了代理,比如下面的类Async-AnnotationExample。
public class AsyncAnnotationExample {@Asyncpublic CompletableFuture<String> doSomething() {// 1.创建futureCompletableFuture<String> result = new CompletableFuture<String>();// 2.模拟任务执行try {Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + "doSomething");} catch (Exception e) {e.printStackTrace();}result.complete("done");// 3.返回结果return result;}
}
由于AsyncAnnotationExample类中方法doSomething被标注了@Async注解,所以Spring框架在开启异步处理后会对AsyncAnnotationExample的实例进行代理,代理后的类代码框架如下所示。
public class AsyncAnnotationExampleProxy {public AsyncAnnotationExample getAsyncTask() {return asyncTask;}public void setAsyncAnnotationExample(AsyncAnnotationExample asyncTask) {this.asyncTask = asyncTask;}private AsyncAnnotationExample asyncTask;private TaskExecutor executor = new SimpleAsyncTaskExecutor();public CompletableFuture<String> dosomthingAsyncFuture() {return CompletableFuture.supplyAsync(new Supplier<String>() {@Overridepublic String get() {try {return asyncTask.dosomthing().get();} catch (Throwable e) {throw new CompletionException(e);}}},executor);}
}
如上代码所示,Spring会对AsyncAnnotationExample类进行代理,并且会把AsyncAnnotationExample的实例注入AsyncAnnotationExampleProxy内部,当我们调用AsyncAnnotationExample的dosomthing方法时,实际调用的是AsyncAnnotation ExampleProxy的dosomthing方法,后者使用CompletableFuture.supplyAsync开启了一个异步任务(其马上返回一个CompletableFuture对象),并且使用默认的SimpleAsync TaskExecutor线程池作为异步处理线程,然后在异步任务内具体调用了AsyncAnnotationExample实例的dosomthing方法。
默认情况下,Spring框架是使用Cglib对标注@Async注解的方法进行代理的,具体拦截器是AnnotationAsyncExecutionInterceptor
,我们看看其invoke方法。
public Object invoke(final MethodInvocation invocation) throws Throwable {//1.被代理的目标对象Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);//2. 获取被代理的方法Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);//3. 判断使用哪个执行器执行被代理的方法AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);if (executor == null) {throw new IllegalStateException("No executor specified and no default executor set on AsyncExecutionInterceptor either");}//4. 使用Callable包装要执行的方法Callable<Object> task = () -> {try {Object result = invocation.proceed();if (result instanceof Future) {return ((Future<?>) result).get();}}catch (ExecutionException ex) {handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());}catch (Throwable ex) {handleError(ex, userDeclaredMethod, invocation.getArguments());}return null;};//5. 提交包装的Callable任务到指定执行器执行return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
·代码1获取被代理的目标对象的Class对象,本例中为class:com.artisan.async.AsyncProgram.AsyncAnnotationExample的Class对象;
·代码2获取被代理的方法,本例中为public java.util.concurrent.CompletableFuture:com.artisan.async.AsyncProgram.AsyncAnnotationExample.dosomthing();
·代码3根据规则获取使用哪个执行器TaskExecutor执行被代理的方法,其代码如下所示。
private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<>(16);
protected AsyncTaskExecutor determineAsyncExecutor(Method method) {//4.1获取对应方法的执行器AsyncTaskExecutor executor = this.executors.get(method);//4.2不存在则按照规则查找if (executor == null) {//4.2.1 如果注解@Async中指定了执行器名称Executor targetExecutor;String qualifier = getExecutorQualifier(method);if (StringUtils.hasLength(qualifier)) {targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);}//4.2.2 获取默认执行器else {targetExecutor = this.defaultExecutor;if (targetExecutor == null) {synchronized (this.executors) {if (this.defaultExecutor == null) {this.defaultExecutor = getDefaultExecutor(this.beanFactory);}targetExecutor = this.defaultExecutor;}}}//4.2.3if (targetExecutor == null) {return null;}//4.2.4 添加执行器到缓存executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));this.executors.put(method, executor);}//4.3返回查找的执行器return executor;
}
代码4.1从缓存executors中尝试获取method方法对应的执行器,如果存在则直接执行代码4.3返回;否则执行代码4.2.1判断方法的注解@Async中是否指定了执行器名称,如果有则尝试从Spring的bean工厂内获取该名称的执行器的实例,否则执行代码4.2.2获取默认的执行器(SimpleAsyncTaskExecutor),然后代码4.2.4把执行器放入缓存。
到这里就探讨完成了AnnotationAsyncExecutionInterceptor的invoke方法内代码3是如何确定那个执行器,然后在invoke方法中的代码4使用Callable包装要执行的方法,代码5提交包装的Callable任务到指定执行器。
到这里所有的执行使用的都是调用线程,调用线程提交异步任务到执行器后就返回了,异步任务真正执行的是具体执行器中的线程。下面我们看看代码5 doSubmit的代码。
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {//5.1判断方法返回值是否为CompletableFuture类型或者是其子类if (CompletableFuture.class.isAssignableFrom(returnType)) {return CompletableFuture.supplyAsync(() -> {try {return task.call();}catch (Throwable ex) {throw new CompletionException(ex);}}, executor);}//5.2判断返回值类型是否为ListenableFuture类型或者是其子类else if (ListenableFuture.class.isAssignableFrom(returnType)) {return ((AsyncListenableTaskExecutor) executor).submitListenable(task);}//5.3判断返回值类型是否为ListenableFuture类型或者是其子类else if (Future.class.isAssignableFrom(returnType)) {return executor.submit(task);}//5.4其他情况下没有返回值else {executor.submit(task);return null;}
}
·代码5.1判断方法返回值是否为CompletableFuture类型或者是其子类,如果是则把任务使用CompletableFuture.supplyAsync方法提交到线程池executor执行,该方法会马上返回一个CompletableFuture对象。
·代码5.2判断方法返回值是否为ListenableFuture类型或者是其子类,如果是则把任务提交到线程池executor执行,该方法会马上返回一个ListenableFuture对象。
·代码5.3判断方法返回值是否为Future类型或者是其子类,如果是则把任务提交到线程池executor执行,该方法会马上返回一个Future对象。
·代码5.4说明方法不需要返回值,直接提交任务到线程池executor后返回null。
上面我们讲解了代理拦截器AnnotationAsyncExecutionInterceptor的invoke方法如何对标注@Async的方法进行处理,实现异步执行的。其实还有一部分还没讲,前面说了要开始异步处理,必须使用@EnableAsync注解或者task:annotation-driven/来开启异步处理,那么这两个部分背后到底做了什么呢?下面我们就来一探究竟。
首先我们看看添加@EnableAsync注解后发生了什么?在Spring容器启动的过程中会有一系列扩展接口对Bean的元数据定义、初始化、实例化做拦截处理,也存在一些处理器类可以动态地向Spring容器添加一些框架需要使用的Bean实例。其中ConfigurationClassPostProcessor处理器类则是用来解析注解类,并把其注册到Spring容器中的,其可以解析标注@Configuration、@Component、@ComponentScan、@Import、@ImportResource等的Bean。当我们使用context:annotation-config/或者context:component-scan/时,Spring容器会默认把ConfigurationClassPostProcessor处理器注入Spring容器。
而@EnableAsync的定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
...
}
所以我们添加了@EnableAsync注解后,ConfigurationClassPostProcessor会解析其中的@Import(AsyncConfigurationSelector.class),并把AsyncConfigurationSelector的实例注入Spring容器,其代码如下所示。
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME ="org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";@Override@Nullablepublic String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:return new String[] {ProxyAsyncConfiguration.class.getName()};case ASPECTJ:return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};default:return null;}}
}
AsyncConfigurationSelector实现了ImportSelector接口的selectImports方法,根据AdviceMode参数返回需要导入到Spring容器的Bean的全路径包名。该方法会在ConfigurationClassPostProcessor中的ConfigurationClassParser类中调用。默认情况下的adviceMode为PROXY,所以会把ProxyAsyncConfiguration的实例注入Spring容器。
ProxyAsyncConfiguration的代码如下所示。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public AsyncAnnotationBeanPostProcessor asyncAdvisor() {Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();bpp.configure(this.executor, this.exceptionHandler);Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {bpp.setAsyncAnnotationType(customAsyncAnnotation);}bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTarget
Class"));bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));return bpp;}}
如上代码ProxyAsyncConfiguration的asyncAdvisor方法添加了@Bean注解,所以该方法返回的Bean也会被注入Spring容器。该方法创建了AsyncAnnotationBean PostProcessor处理器,所以AsyncAnnotationBeanPostProcessor的一个实例会被注入Spring容器中,由于其实现了BeanFactoryAware接口,所以Spring框架会调用其setBeanFactory(BeanFactory beanFactory)方法把Spring BeanFactory(存放bean的容器)注入该Bean,setBeanFactory方法代码如下所示。
public void setBeanFactory(BeanFactory beanFactory) {super.setBeanFactory(beanFactory);AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);if (this.asyncAnnotationType != null) {advisor.setAsyncAnnotationType(this.asyncAnnotationType);}advisor.setBeanFactory(beanFactory);this.advisor = advisor;
}
如上代码创建了一个AsyncAnnotationAdvisor的实例并保存到了AsyncAnnotation BeanPostProcessor的advisor变量。Spring中每个AsyncAnnotationAdvisor都包含一个Advice(切面逻辑)和一个PointCut(切点),也就是会对符合PointCut的方法使用Advice进行功能增强,对应Advice和PointCut是在AsyncAnnotationAdvisor构造函数内创建的。
public AsyncAnnotationAdvisor(@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {//6.1.异步注解类型Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);asyncAnnotationTypes.add(Async.class);try {asyncAnnotationTypes.add((Class<? extends Annotation>)ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));}catch (ClassNotFoundException ex) {}//6.2创建切面逻辑this.advice = buildAdvice(executor, exceptionHandler);//6.3创建切点this.pointcut = buildPointcut(asyncAnnotationTypes);
}
如上代码6.1收集注解@Async和@javax.ejb.Asynchronous到asyncAnnotationTypes,代码6.2则创建Advice,其代码如下所示。
protected Advice buildAdvice(@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);interceptor.configure(executor, exceptionHandler);return interceptor;
}
由上述代码可知,这里创建了AnnotationAsyncExecutionInterceptor拦截器作为切面逻辑。下面看看代码6.3如何创建切点。
protected Pointcut buildPointcut(Set<Class<? extends Annotation>> asyncAnnotationTypes) {ComposablePointcut result = null;for (Class<? extends Annotation> asyncAnnotationType : asyncAnnotationTypes) {Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);Pointcut mpc = new AnnotationMatchingPointcut(null, asyncAnnotationType, true);if (result == null) {result = new ComposablePointcut(cpc);}else {result.union(cpc);}result = result.union(mpc);}return (result != null ? result : Pointcut.TRUE);
}
在上述代码中使用收集的注解集合asyncAnnotationTypes,并在每个注解处创建了一个AnnotationMatchingPointcut作为切点,AnnotationMatchingPointcut内部的AnnotationClassFilter的方法matches则作为某个方法是否满足切点的条件,具体代码如下所示。
public boolean matches(Class<?> clazz) {return (this.checkInherited ? AnnotatedElementUtils.hasAnnotation(clazz, this.annotationType) :clazz.isAnnotationPresent(this.annotationType));
}
由如上代码可知,判断方法通过是否有注解@Async为依据来判断方法是否符合切点。
到此我们把AsyncAnnotationBeanPostProcessor的setBeanFactory(BeanFactory bean-Factory)方法逻辑讲解完毕了,其内部保存了一个AsyncAnnotationAdvisor对象用来对Spring容器中符合条件(这里为含有@Async注解的方法的Bean)的Bean的方法进行功能增强,下面我们看看AsyncAnnotationBeanPostProcessor的postProcess AfterInitialization方法是如何对这些符合条件的Bean进行代理的。
public Object postProcessAfterInitialization(Object bean, String beanName) {...if (isEligible(bean, beanName)) {//7.1ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);if (!proxyFactory.isProxyTargetClass()) {evaluateProxyInterfaces(bean.getClass(), proxyFactory);}//7.2 设置拦截器proxyFactory.addAdvisor(this.advisor);customizeProxyFactory(proxyFactory);//7.3 获取代理类return proxyFactory.getProxy(getProxyClassLoader());}// No proxy needed.return bean;
}
如上代码7.1使用prepareProxyFactory创建了代理工厂,其代码如下所示。
protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);proxyFactory.setTarget(bean);return proxyFactory;
}
代码7.2则设置在其setBeanFactory方法内创建的AsyncAnnotationAdvisor对象作为Advisor,代码7.3从代理工厂获取代理后的Bean实例并返回到Spring容器,所以当我们调用含有@Async注解的Bean的方法时候,实际调用的是被代理后的Bean。
当我们调用被代理的类的方法时,代理类内部会先使用AsyncAnnotationAdvisor中的PointCut进行比较,看其是否符合切点条件(方法是否含有@Async)注解,如果不符合则直接调用被代理的对象的原生方法,否则调用AsyncAnnotationAdvisor内部的AnnotationAsyncExecutionInterceptor进行拦截异步处理。
在了解添加@EnableAsync注解后会发生什么后,下面我们来看看当添加标签<task:annotation-driven/>
开启异步处理时,背后又发生了什么?在Spring中对于标签<XXX:/>
总是会存在名称为XXXTaskNamespaceHandler的处理器负责该标签的解析,所以对于标签,自然存在TaskNamespaceHandler处理器负责其解析,其代码如下所示。
public class TaskNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());this.registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());this.registerBeanDefinitionParser("scheduled-tasks", new ScheduledTasksBeanDefinitionParser());this.registerBeanDefinitionParser("scheduler", new SchedulerBeanDefinitionParser());}
}
由如上代码可知,<task:annotation-driven/>
是使用AnnotationDrivenBeanDefinitionParser来进行解析的,下面我们看看其parse方法。
public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
...@Override@Nullablepublic BeanDefinition parse(Element element, ParserContext parserContext) {Object source = parserContext.extractSource(element);...//8.1 String mode = element.getAttribute("mode");if ("aspectj".equals(mode)) {// mode="aspectj"registerAsyncExecutionAspect(element, parserContext);}else {//8.2 mode="proxy"if (registry.containsBeanDefinition(TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)) {parserContext.getReaderContext().error("Only one AsyncAnnotationBeanPostProcessor may exist within the context.", source);}else {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor");builder.getRawBeanDefinition().setSource(source);String executor = element.getAttribute("executor");if (StringUtils.hasText(executor)) {builder.addPropertyReference("executor", executor);}String exceptionHandler = element.getAttribute("exception-handler");if (StringUtils.hasText(exceptionHandler)) {builder.addPropertyReference("exceptionHandler", exceptionHandler);}if (Boolean.valueOf(element.getAttribute(AopNamespaceUtils.PROXY_TARGET_CLASS_ATTRIBUTE))) {builder.addPropertyValue("proxyTargetClass", true);}registerPostProcessor(parserContext, builder, TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME);}}//8.3 Finally register the composite component.parserContext.popAndRegisterContainingComponent();return null;}
}
由如上代码可知,其主要是用来创建AsyncAnnotationBeanPostProcessor在Spring容器中的元数据定义,并注册到Spring容器中,剩下的流程就与基于@EnableAsync注解开启异步处理的流程一样了。
小结
我们梳理如何使用Spring框架中的@Async进行异步处理,以及其内部如何使用代理的方式来实现,并且可知使用@Async实现异步编程属于声明式编程,一般情况下不需要我们显式创建线程池并提交任务到线程池,这大大减轻了的负担
好文推荐
一文彻底讲透@Async注解的原理和使用方法
相关文章:
异步编程 - 09 Spring框架中的异步执行_@Async注解异步执行原理源码解析
文章目录 概述小结好文推荐 概述 在Spring中调用线程将在调用含有Async注释的方法时立即返回,Spring是如何做到的呢?其实是其对标注Async注解的类做了代理,比如下面的类Async-AnnotationExample。 public class AsyncAnnotationExample {As…...
django-项目
一、RESTful设计风格 基础概念 全称:Representational State Transfer 1.资源 网络上的一个实体,每个资源都有一个独一无二的URL与之对应;获取资源-直接访问URL即可 2.表现层 资源的表现形式 如HTML、xml、JPG、json等 3.状态转化 …...
红日靶场五(vulnstack5)渗透分析
环境搭建 win7 192.168.111.132(仅主机) 192.168.123.212(桥接) .\heart p-0p-0p-0win2008 ip: 192.168.111.131(仅主机) sun\admin 2020.comkali ip: 192.168.10.131(nat)vps&…...
掌握Gitflow的一些进阶用法
1、自定义分支命名约定: 默认情况下,GitFlow使用一套分支命名约定,如feature/、release/和hotfix/等前缀。然而,你可以根据项目的需求自定义分支名称。例如,你可以在分支名称中包含项目、功能或团队成员的信息&#x…...
算法随笔:各种经典最短路算法的简要比较总结
有多种最短路径的应用场景,它们需要用到不同的算法来解决。除了贪心最优搜索之外,其他都是最优性算法,即得到的解都是最短路径。其中m是边的数量,n是点的数量。 问题边权算法时间复杂度一个起点,一个终点非负数&#…...
concrt140.dll怎么下载,concrt140.dll修复工具(修复精灵下载)一键修复问题
今天,我将为大家介绍一个非常常见的问题:由于找不到concrt140.dll,无法继续执行代码怎么办。这个问题可能会让很多网友感到头疼,但是别担心,我会为大家提供5种最全详细的恢复方法。在接下来我将详细介绍这些问题及其解决方法。希望…...
自行实现字符串转浮点数函数atof()
【重复造轮子的原因】 尽管atof是标准C中自带的函数,用于将字符串转为浮点数,但是在某些环境下有可能没法使用的(例如CUDA环境中,没有atof函数,但是math.h可以使用),因此自行实现。 【通过的测试用例】 【实现的代码】 #include <stdio.h> #include <math.h…...
Windows平台Fortran编程入门
Fortran,原意为 Formula Translation(公式翻译器);一门出现很早的面向科学计算的高级语言,在数值计算领域仍然使用; 免费 Fortran 的编译器包括 GCC 的 GFortran,Intel 的 Intel Fortran Compi…...
05-Mysql夺命三连问:什么是索引下推?什么是索引覆盖?什么是回表?【Java面试总结】
Mysql夺命三连问:什么是索引下推?什么是索引覆盖?什么是回表? 索引下推是mysql5.6 提出的一个查询优化方案,主要的目的是减少数据或查询中不必要的读取和计算,它的原理是将查询条件尽可能的推送到索引层面…...
晨启,MSP430开发板,51开发板,原理图,PCB图
下载:https://github.com/xddun/blog_code_search...
Notepad++ 的安装及配置
由于电脑重装了Win11系统,干脆重头开始,重新安装每一个软件~~~ 很多博客或者博主都会推荐notepad的官网:https://notepad-plus-plus.org/ 但大家亲自点开就会发现是无响应,如下图 同时,也会有很多博主直接给网盘地址…...
✔ ★算法基础笔记(Acwing)(一)—— 基础算法(20道题)【java版本】
基础算法 一、快速排序1. 快速排序例题2. 第k个数( 快速选择 ) ✔ ✔1.31★快排二刷总结( 4点 ) 二、归并排序1. 归并排序模板题 ✔ ✔1.31★二刷总结 ★2. 逆序对的数量 ✔ ✔1.31★二刷总结 三、二分1. 数的范围 ✔1.31★二刷总结(mid > x 则是 输出最左边一个)第一个大于…...
简单记录下gin中使用中间件记录操作日志
1、直接定义中间件package middlewareimport ("bytes""encoding/json""fmt""github.com/gin-gonic/gin""go.uber.org/zap""io""strconv""strings" )func LoggerMiddleWare() gin.HandlerFunc…...
基于Matlab利用IRM和RRTstar实现无人机路径规划(附上源码+数据+说明+报告+PPT)
无人机路径规划是无人机应用领域中的关键问题之一。本文提出了一种基于IRM(Informed RRTstar Method)和RRTstar(Rapidly-exploring Random Tree star)算法的无人机路径规划方法,并使用Matlab进行实现。该方法通过结合I…...
uniapp使用@microsoft/signalr(报错“ReferenceError: require is not defined“)
后台老哥要用微软的signalr,总结了一些经验和问题 引入方法 1、npm npm i microsoft/signalr 2、下载他的js或者cdn <script src"https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>在uniapp中&…...
CloudCompare 二次开发(9)——半径滤波
目录 一、概述二、代码集成三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 使用CloudCompare与PCL的混合编程实现点云半径滤波。半径滤波的算法原理见:PCL 半径滤波器。基于PCL将半径滤波集成到Cl…...
ElementUI浅尝辄止29:Breadcrumb 面包屑
显示当前页面的路径,快速返回之前的任意页面。 1.如何使用? 在el-breadcrumb中使用el-breadcrumb-item标签表示从首页开始的每一级。Element 提供了一个separator属性,在el-breadcrumb标签中设置它来决定分隔符,它只能是字符串&am…...
ABB MPRC086444-005数字输入模块
ABB MPRC086444-005 是一款数字输入模块,通常用于工业自动化和控制系统中,用于接收和处理数字信号。以下是这种类型的数字输入模块通常可能具备的一般功能和特点: 数字输入接口:MPRC086444-005 模块通常配备多个数字输入通道&…...
stable diffusion实践操作-常见lora模型介绍
系列文章目录 本文专门开一节写Lora相关的内容,在看之前,可以同步关注: stable diffusion实践操作 文章目录 系列文章目录前言一、什么是lora?1.1 lora 定义1.2 lora的基本原理1.2 通过分层控制lora 二、作用:2.1 复刻人物特征2…...
kubeadm 安装k8s
目录 安装k8s 环境准备 所有节点,关闭防火墙规则,关闭selinux,关闭swap交换(必须关闭swap分区) //修改主机名 //所有节点修改hosts文件 //调整内核参数 所有节点安装docker 所有节点安装kubeadm,kube…...
选择最适合您的Bug管理软件:市场比较与推荐
“Bug管理软件哪家好?市场上有许多优秀的Bug管理系统品牌如:Zoho Projects、JIRA、Redmine、Bugzilla、MantisBT。” 一款高效的Bug管理系统可以帮助团队更有效地发现、记录和解决软件中的问题,从而提高产品质量和用户满意度。本文将为您介绍…...
Spring MVC的常用注解及用法
Spring MVC的执行流程: 1.用户的请求首先到Controller 2.Controller将请求转发给Model 3.Model处理业务并将数据结果给Controller 4.Controller会将数据给View引擎 5.View转换数据生成最终的页面给用户。 常用注解: 1.requestMapping:…...
HTTP和HTTPS的区别、 HTTPS运行原理
HTTP 一种用于web浏览器和web服务器基于数据传递的协议基于TCP/IP协议的应用层传送,用于客户端和服务器之间的数据交互 HTTPS 是HTTP的安全版,基于SSL或TLS协议,对客户端和服务器之间加密和身份验证,使得数据之间传递具有了安全…...
统计封闭岛屿的数目
1254. 统计封闭岛屿的数目 关于岛屿的相似题目: 岛屿数量 – 二维矩阵的dfs算法封闭岛屿数量 – 二维矩阵的dfs算法统计封闭岛屿的数目统计子岛屿不同岛屿的数量 class MaxAreaOfIsland:"""floodFill 算法1254. 统计封闭岛屿的数目https://leetcod…...
【数据结构与算法系列4】长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0 。 示例 1: 输入&…...
问道管理:底部渐渐抬高 今年反弹时刻或已来临
快速探底后,两市呈现分解走势。 沪指周三低开震动,指数在20日均线取得支撑后小幅上升,最终以红盘报收。深成指走势弱于沪指,尽管午后指数有所上升,但最终未能翻红。到收盘,沪指报收3158.08点,上…...
正规黄金代理的三大要素
对于现货黄金投资来说,寻找一个正规的黄金代理是十分重要的问题。在目前的现货黄金投资市场中,现货黄金代理的数量很多,他们都致力于耕耘现货黄金投资市场。当越来越多的专业人士加入到现货黄金投资的市场中当中时,这个市场将会越…...
Spine2D骨骼动画播放器 - 微信小程序版
Spine2D骨骼动画播放器 - 微信小程序版 简介平台支持 界面预览使用说明演示视频 版本笨笨的小目标(废话)参考资料测试文件百度盘分享 相关文档 简介 本播放器是SpinePlayer的微信小程序版。由于官方并没有提供现成的运行库,只能自己改造。 设…...
蓝天转债,双良转债上市价格预测
蓝天转债111017 基本信息 转债名称:蓝天转债,评级:AA,发行规模:8.7亿元。 正股名称:蓝天燃气,今日收盘价:9.74元,转股价格:10.13元。 当前转股价值 转债面值…...
python-爬虫-xpath方法-批量爬取王者皮肤图片
import requests from lxml import etree获取NBA成员信息 # 发送的地址 url https://nba.hupu.com/stats/players # UA 伪装 google header {User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.3…...
用jquery做的书籍网站/n127网推广
有人是为了从做报表中解放出来,有人是为了实现更业务化的个性化报表,有人只懂得用Excel且确实通用性强,有人是因为无BI系统支持(或BI不够业务化)……确实有n种理由,最终导向了这个选择--自己用Excel来制作数据分析模板用一个平凡的…...
网站编辑如何做原创/营销网络是什么
计算机系统基础知识一、计算机系统硬件基本组成二、CPU的功能与组成1、CPU的功能2、CPU的组成①运算器②控制器③寄存器组3、多核CPU三、数据表示四、校验码一、计算机系统硬件基本组成 计算机系统硬件{运算器控制器存储器输入设备输出设备计算机系统硬件 \begin{cases} 运算器…...
外星人做的网站/百度收录快的发帖网站
设计人员往往忽略高容量、多层陶瓷电容(MLCC)随其直流电压变化的特性。所有高介电常数或II类电容(B/X5R R/X7R和F/Y5V特性)都存在这种现象。然而,不同类型的MLCC变化量区别很大。Mark Fortunato曾经写过一篇关于该主题的文章,给出的结论是:您…...
08服务器做网站/免费制作网站的软件
一、条件语句1. if语句if 语句用于控制程序的执行,基本形式为:if 判断条件1:执行语句elif 判断条件2:执行语句else:执行语句判断条件成立时,则执行后面的语句。执行内容可以多行,以缩进来区分表…...
东营微信开发网站建设/网站维护合同
20145118 《Java程序设计》第6周学习总结 教材学习内容总结 1.数据依靠串流在目的地与来源地之间传输,无论来去如何,只要取得InputStream或OutputStream的实例,其余操作都是一致的. 2.数据传输时即使不知道来去也可传输,依靠通用的dump()方法.结束时用close()方法关闭串流. 3.I…...
建设一个网站需要什么/直播营销策略有哪些
1.android的标题栏进度条的范围是[0,10000] 2.setSecondaryProgress的颜色浅,setProgress的颜色深,所以使setSecondaryProgress的值大于setProgress 3.this.requestWindowFeature(Window.FEATURE_PROGRESS); this.setProgressBarVisibility(true); 在setContentV…...