Spring 异步调用,一行代码实现!舒服,不接受任何反驳~
本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-29 目录。
原创不易,给点个 Star 嘿,一起冲鸭!
1. 概述
在日常开发中,我们的逻辑都是同步调用,顺序执行。在一些场景下,我们会希望异步调用,将和主线程关联度低的逻辑异步调用,以实现让主线程更快的执行完成,提升性能。例如说:记录用户访问日志到数据库,记录管理员操作日志到数据库中。
异步调用,对应的是同步调用。
同步调用:指程序按照 定义顺序 依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;
异步调用:指程序在顺序执行时,不等待异步调用的语句返回结果,就执行后面的程序。
考虑到异步调用的可靠性,我们一般会考虑引入分布式消息队列,例如说 RabbitMQ、RocketMQ、Kafka 等等。但是在一些时候,我们并不需要这么高的可靠性,可以使用进程内的队列或者线程池。例如说示例代码如下:
public class Demo {public static void main(String[] args) {// 创建线程池。这里只是临时测试,不要扣艿艿遵守阿里 Java 开发规范,YEAHExecutorService executor = Executors.newFixedThreadPool(10);// 提交任务到线程池中执行。executor.submit(new Runnable() {@Overridepublic void run() {System.out.println("听说我被异步调用了");}});}}
友情提示:这里说进程内的队列或者线程池,相对不可靠的原因是,队列和线程池中的任务仅仅存储在内存中,如果 JVM 进程被异常关闭,将会导致丢失,未被执行。
而分布式消息队列,异步调用会以一个消息的形式,存储在消息队列的服务器上,所以即使 JVM 进程被异常关闭,消息依然在消息队列的服务器上。
所以,使用进程内的队列或者线程池来实现异步调用的话,一定要尽可能的保证 JVM 进程的优雅关闭,保证它们在关闭前被执行完成。
在 Spring Framework 的 Spring Task 模块,提供了 @Async
注解,可以添加在方法上,自动实现该方法的异步调用。
😈 简单来说,我们可以像使用 @Transactional
声明式事务,使用 Spring Task 提供的 @Async
注解,😈 声明式异步。而在实现原理上,也是基于 Spring AOP 拦截,实现异步提交该操作到线程池中,达到异步调用的目的。
如果胖友看过艿艿写的 《芋道 Spring Boot 定时任务入门》 文章,就会发现 Spring Task 模块,还提供了定时任务的功能。
下面,让我们一起遨游 Spring 异步任务的海洋。
2. 快速入门
示例代码对应仓库:lab-29-async-demo 。
本小节,我们会编写示例,对比同步调用和异步调用的性能差别,并演示 Spring @Async
注解的使用方式。
2.1 引入依赖
在 pom.xml
文件中,引入相关依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-29-async-demo</artifactId><dependencies><!-- 引入 Spring Boot 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- 方便等会写单元测试 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies></project>
因为 Spring Task 是 Spring Framework 的模块,所以在我们引入 spring-boot-web
依赖后,无需特别引入它。
2.2 Application
创建 Application.java
类,配置 @SpringBootApplication
注解。代码如下:
@SpringBootApplication
@EnableAsync // 开启 @Async 的支持
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}
在类上添加
@EnableAsync
注解,启用异步功能。
2.3 DemoService
在 cn.iocoder.springboot.lab29.asynctask.service
包路径下,创建 DemoService 类。代码如下:
// DemoService.java@Service
public class DemoService {private Logger logger = LoggerFactory.getLogger(getClass());public Integer execute01() {logger.info("[execute01]");sleep(10);return 1;}public Integer execute02() {logger.info("[execute02]");sleep(5);return 2;}private static void sleep(int seconds) {try {Thread.sleep(seconds * 1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
定义了
#execute01()
和#execute02()
方法,分别 sleep 10 秒和 5 秒,模拟耗时操作。同时在每个方法里,使用
logger
打印日志,方便我们看到每个方法的开始执行时间,和执行所在线程。
2.4 同步调用测试
创建 DemoServiceTest 测试类,编写 #task01()
方法,同步调用 DemoService 的上述两个方法。代码如下:
// DemoServiceTest.java@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class DemoServiceTest {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate DemoService demoService;@Testpublic void task01() {long now = System.currentTimeMillis();logger.info("[task01][开始执行]");demoService.execute01();demoService.execute02();logger.info("[task01][结束执行,消耗时长 {} 毫秒]", System.currentTimeMillis() - now);}}
运行单元测试,执行日志如下:
2019-11-30 14:03:35.820 INFO 64639 --- [ main] c.i.s.l.a.service.DemoServiceTest : [task01][开始执行]
2019-11-30 14:03:35.828 INFO 64639 --- [ main] c.i.s.l.asynctask.service.DemoService : [execute01]
2019-11-30 14:03:45.833 INFO 64639 --- [ main] c.i.s.l.asynctask.service.DemoService : [execute02]
2019-11-30 14:03:50.834 INFO 64639 --- [ main] c.i.s.l.a.service.DemoServiceTest : [task01][结束执行,消耗时长 15014 毫秒]
DemoService 的两个方法,顺序执行,一共消耗 15 秒左右。
DemoService 的两个方法,都在主线程中执行。
2.5 异步调用测试
修改 DemoService 的代码,增加 #execute01()
和 #execute02()
的异步调用。代码如下:
// DemoService.java@Async
public Integer execute01Async() {return this.execute01();
}@Async
public Integer execute02Async() {return this.execute02();
}
额外增加了
#execute01Async()
和#execute02Async()
方法,主要是不想破坏上面的「2.4 同步调用测试」哈。实际上,可以在#execute01()
和#execute02()
方法上,添加@Async
注解,实现异步调用。
修改 DemoServiceTest 测试类,编写 #task02()
方法,异步调用上述的两个方法。代码如下:
// DemoServiceTest.java@Test
public void task02() {long now = System.currentTimeMillis();logger.info("[task02][开始执行]");demoService.execute01Async();demoService.execute02Async();logger.info("[task02][结束执行,消耗时长 {} 毫秒]", System.currentTimeMillis() - now);
}
运行单元测试,执行日志如下:
2019-11-30 15:57:45.809 INFO 69165 --- [ main] c.i.s.l.a.service.DemoServiceTest : [task02][开始执行]
2019-11-30 15:57:45.836 INFO 69165 --- [ main] c.i.s.l.a.service.DemoServiceTest : [task02][结束执行,消耗时长 27 毫秒]2019-11-30 15:57:45.844 INFO 69165 --- [ task-1] c.i.s.l.asynctask.service.DemoService : [execute01]
2019-11-30 15:57:45.844 INFO 69165 --- [ task-2] c.i.s.l.asynctask.service.DemoService : [execute02]
DemoService 的两个方法,异步执行,所以主线程只消耗 27 毫秒左右。注意,实际这两个方法,并没有执行完成。
DemoService 的两个方法,都在异步的线程池中,进行执行。
2.6 等待异步调用完成测试
在 「2.5 异步调用测试」 中,两个方法只是发布异步调用,并未执行完成。在一些业务场景中,我们希望达到异步调用的效果,同时主线程阻塞等待异步调用的结果。
修改 DemoService 的代码,增加 #execute01()
和 #execute02()
的异步调用,并返回 Future 对象。代码如下:
// DemoService.java@Async
public Future<Integer> execute01AsyncWithFuture() {return AsyncResult.forValue(this.execute01());
}@Async
public Future<Integer> execute02AsyncWithFuture() {return AsyncResult.forValue(this.execute02());
}
相比 「2.5 异步调用测试」 的两个方法,我们额外增加调用
AsyncResult#forValue(V value)
方法,返回带有执行结果的 Future 对象。
修改 DemoServiceTest 测试类,编写 #task03()
方法,异步调用上述的两个方法,并阻塞等待执行完成。代码如下:
// DemoServiceTest.java@Test
public void task03() throws ExecutionException, InterruptedException {long now = System.currentTimeMillis();logger.info("[task03][开始执行]");// <1> 执行任务Future<Integer> execute01Result = demoService.execute01AsyncWithFuture();Future<Integer> execute02Result = demoService.execute02AsyncWithFuture();// <2> 阻塞等待结果execute01Result.get();execute02Result.get();logger.info("[task03][结束执行,消耗时长 {} 毫秒]", System.currentTimeMillis() - now);
}
<1>
处,异步调用两个方法,并返回对应的 Future 对象。这样,这两个异步调用的逻辑,可以并行执行。<2>
处,分别调用两个 Future 对象的#get()
方法,阻塞等待结果。
运行单元测试,执行日志如下:
2019-11-30 16:10:22.226 INFO 69641 --- [ main] c.i.s.l.a.service.DemoServiceTest : [task03][开始执行]2019-11-30 16:10:22.272 INFO 69641 --- [ task-1] c.i.s.l.asynctask.service.DemoService : [execute01]
2019-11-30 16:10:22.272 INFO 69641 --- [ task-2] c.i.s.l.asynctask.service.DemoService : [execute02]2019-11-30 16:10:32.276 INFO 69641 --- [ main] c.i.s.l.a.service.DemoServiceTest : [task03][结束执行,消耗时长 10050 毫秒]
DemoService 的两个方法,异步执行,因为主线程阻塞等待执行结果,所以消耗 10 秒左右。当同时有多个异步调用,并阻塞等待执行结果,消耗时长由最慢的异步调用的逻辑所决定。
DemoService 的两个方法,都在异步的线程池中,进行执行。
下面「2.7 应用配置文件」小节,是补充知识,建议看看。
2.7 应用配置文件
在 application.yml
中,添加 Spring Task 定时任务的配置,如下:
spring:task:# Spring 执行器配置,对应 TaskExecutionProperties 配置类。对于 Spring 异步任务,会使用该执行器。execution:thread-name-prefix: task- # 线程池的线程名的前缀。默认为 task- ,建议根据自己应用来设置pool: # 线程池相关core-size: 8 # 核心线程数,线程池创建时候初始化的线程数。默认为 8 。max-size: 20 # 最大线程数,线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUEkeep-alive: 60s # 允许线程的空闲时间,当超过了核心线程之外的线程,在空闲时间到达之后会被销毁。默认为 60 秒queue-capacity: 200 # 缓冲队列大小,用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。allow-core-thread-timeout: true # 是否允许核心线程超时,即开启线程池的动态增长和缩小。默认为 true 。shutdown:await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 trueawait-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置
在
spring.task.execution
配置项,Spring Task 调度任务的配置,对应 TaskExecutionProperties 配置类。Spring Boot TaskExecutionAutoConfiguration 自动化配置类,实现 Spring Task 的自动配置,创建 ThreadPoolTaskExecutor 基于线程池的任务执行器。本质上,ThreadPoolTaskExecutor 是基于 ThreadPoolExecutor 的封装,主要增加提交任务,返回 ListenableFuture 对象的功能。
注意,spring.task.execution.shutdown
配置项,是为了实现 Spring Task 异步任务的优雅关闭。我们想象一下,如果异步任务在执行的过程中,如果应用开始关闭,把异步任务需要使用到的 Spring Bean 进行销毁,例如说数据库连接池,那么此时异步任务还在执行中,一旦需要访问数据库,可能会导致报错。
所以,通过配置
await-termination = true
,实现应用关闭时,等待异步任务执行完成。这样,应用在关闭的时,Spring 会优先等待 ThreadPoolTaskScheduler 执行完任务之后,再开始 Spring Bean 的销毁。同时,又考虑到我们不可能无限等待异步任务全部执行结束,因此可以配置
await-termination-period = 60
,等待任务完成的最大时长,单位为秒。具体设置多少的等待时长,可以根据自己应用的需要。
3. 异步回调
示例代码对应仓库:lab-29-async-demo 。
😈 异步 + 回调,快活似神仙。所以本小节我们来看看,如何在异步调用完成后,实现自定义回调。
考虑到让胖友更加理解 Spring Task 异步回调是如何实现的,我们会在 「3.1 AsyncResult」 和 「3.2 ListenableFutureTask」小节进行部分源码解析,请保持淡定。如果不想看的胖友,可以直接看 「3.3 具体示例」 小节。
友情提示:该示例,基于 「2. 快速入门」 的 lab-29-async-demo 的基础上,继续改造。
3.1 AsyncResult
在 「2.6 等待异步调用完成测试」 中,我们看到了 AsyncResult 类,表示异步结果。返回结果分成两种情况:
执行成功时,调用
AsyncResult#forValue(V value)
静态方法,返回成功的 ListenableFuture 对象。代码如下:// AsyncResult.java@Nullableprivate final V value;public static <V> ListenableFuture<V> forValue(V value) {return new AsyncResult<>(value, null);}
执行异常时,调用
AsyncResult#forExecutionException(Throwable ex)
静态方法,返回异常的 ListenableFuture 对象。代码如下:// AsyncResult.java@Nullableprivate final Throwable executionException;public static <V> ListenableFuture<V> forExecutionException(Throwable ex) {return new AsyncResult<>(null, ex);}
同时,AsyncResult 实现了 ListenableFuture 接口,提供异步执行结果的回调处理。这里,我们先来看看 ListenableFuture 接口。代码如下:
// ListenableFuture.javapublic interface ListenableFuture<T> extends Future<T> {// 添加回调方法,统一处理成功和异常的情况。void addCallback(ListenableFutureCallback<? super T> callback);// 添加成功和失败的回调方法,分别处理成功和异常的情况。void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback);// 将 ListenableFuture 转换成 JDK8 提供的 CompletableFuture 。// 这样,后续我们可以使用 ListenableFuture 来设置回调// 不了解 CompletableFuture 的胖友,可以看看 https://colobu.com/2016/02/29/Java-CompletableFuture/ 文章。default CompletableFuture<T> completable() {CompletableFuture<T> completable = new DelegatingCompletableFuture<>(this);addCallback(completable::complete, completable::completeExceptionally);return completable;}}
看下每个接口方法上的注释。
因为 ListenableFuture 继承了 Future 接口,所以 AsyncResult 也需要实现 Future 接口。这里,我们再来看看 Future 接口。代码如下:
// Future.java
public interface Future<V> {// 获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。V get() throws InterruptedException, ExecutionException;// 获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的 timeout 时间,该方法将抛出异常。V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;// 如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回 true 。boolean isDone();// 如果任务完成前被取消,则返回 true 。boolean isCancelled();// 如果任务还没开始,执行 cancel(...) 方法将返回 false;// 如果任务已经启动,执行 cancel(true) 方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回 true ;// 当任务已经启动,执行c ancel(false) 方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回 false ;// 当任务已经完成,执行 cancel(...) 方法将返回 false 。// mayInterruptRunning 参数表示是否中断执行中的线程。boolean cancel(boolean mayInterruptIfRunning);}
如上注释内容,参考自 《Java 多线程编程:Callable、Future 和 FutureTask 浅析》 文章。
AsyncResult 对 ListenableFuture 定义的 #addCallback(...)
接口方法,实现代码如下:
// AsyncResult.java@Override
public void addCallback(ListenableFutureCallback<? super V> callback) {addCallback(callback, callback);
}@Override
public void addCallback(SuccessCallback<? super V> successCallback, FailureCallback failureCallback) {try {if (this.executionException != null) { // <1>failureCallback.onFailure(exposedException(this.executionException));} else { // <2>successCallback.onSuccess(this.value);}} catch (Throwable ex) { // <3>// Ignore}
}// 从 ExecutionException 中,获得原始异常。
private static Throwable exposedException(Throwable original) {if (original instanceof ExecutionException) {Throwable cause = original.getCause();if (cause != null) {return cause;}}return original;
}
ListenableFutureCallback 接口,同时继承 SuccessCallback 和 FailureCallback 接口。
<1>
处,如果是异常的结果,调用 FailureCallback 的回调。<2>
处,如果是正常的结果,调用 SuccessCallback 的回调。<3>
处,如果回调的逻辑发生异常,直接忽略。😈 所有,如果如果有多个回调,如果有一个回调发生异常,不会影响后续的回调。
(⊙o⊙)… 不过有点懵逼的是,不是应该在异步调用执行成功后,才进行回调么?!怎么这里一添加回调方法,就直接执行了?!不要着急,答案在 「3.2 ListenableFutureTask」 中解答。
实际上,AsyncResult 是作为异步执行的结果。既然是结果,执行就已经完成。所以,在我们调用 #addCallback(...)
接口方法来添加回调时,必然直接使用回调处理执行的结果。
AsyncResult 对 ListenableFuture 定义的 #completable(...)
接口方法,实现代码如下:
// AsyncResult.java@Override
public CompletableFuture<V> completable() {if (this.executionException != null) {CompletableFuture<V> completable = new CompletableFuture<>();completable.completeExceptionally(exposedException(this.executionException));return completable;} else {return CompletableFuture.completedFuture(this.value);}
}
直接将结果包装成 CompletableFuture 对象。
AsyncResult 对 Future 定义的所有方法,实现代码如下:
// AsyncResult.java@Override
public boolean cancel(boolean mayInterruptIfRunning) {return false; // 因为是 AsyncResult 是执行结果,所以直接返回 false 表示取消失败。
}@Override
public boolean isCancelled() {return false; // 因为是 AsyncResult 是执行结果,所以直接返回 false 表示未取消。
}@Override
public boolean isDone() {return true; // 因为是 AsyncResult 是执行结果,所以直接返回 true 表示已完成。
}@Override
@Nullable
public V get() throws ExecutionException {// 如果发生异常,则抛出该异常。if (this.executionException != null) {throw (this.executionException instanceof ExecutionException ?(ExecutionException) this.executionException :new ExecutionException(this.executionException));}// 如果执行成功,则返回该 value 结果return this.value;
}@Override
@Nullable
public V get(long timeout, TimeUnit unit) throws ExecutionException {return get();
}
胖友自己看看代码上的注释。
😈 看到这里,相信很多胖友会是一脸懵逼,淡定淡定。看源码这个事儿,总是柳暗花明又一村。
3.2 ListenableFutureTask
在我们调用使用 @Async
注解的方法时,如果方法返回的类型是 ListenableFuture 的情况下,实际方法返回的是 ListenableFutureTask 对象。
感兴趣的胖友,可以看看 AsyncExecutionInterceptor 类、《Spring 异步调用原理及Spring AOP 拦截器链原理》 文章。
ListenableFutureTask 类,也实现 ListenableFuture 接口,继承 FutureTask 类,ListenableFuture 的 FutureTask 实现类。
ListenableFutureTask 对 ListenableFuture 定义的 #addCallback(...)
方法,实现代码如下:
// ListenableFutureTask.javaprivate final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<T>();@Override
public void addCallback(ListenableFutureCallback<? super T> callback) {this.callbacks.addCallback(callback);
}@Override
public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {this.callbacks.addSuccessCallback(successCallback);this.callbacks.addFailureCallback(failureCallback);
}
暂存回调到 ListenableFutureCallbackRegistry 中先。😈 这样看起来,和我们想象中的异步回调有点像了。
ListenableFutureTask 对 FutureTask 已实现的 #done()
方法,进行重写。实现代码如下:
// ListenableFutureTask.java@Override
protected void done() {Throwable cause;try {// <1> 获得执行结果T result = get();// <2.1> 执行成功,执行成功的回调this.callbacks.success(result);return;} catch (InterruptedException ex) { // 如果有中断异常 InterruptedException ,则打断当前线程,并直接返回Thread.currentThread().interrupt();return;} catch (ExecutionException ex) { // 如果有 ExecutionException 异常,获得其真实的异常,并设置到 cause 中cause = ex.getCause();if (cause == null) {cause = ex;}} catch (Throwable ex) { // 设置异常到 cause 中cause = ex;}// 执行异常,执行异常的回调this.callbacks.failure(cause);
}
<1>
处,调用#get()
方法,获得执行结果。<2.1>
处,执行成功,执行成功的回调。<2.2>
处,执行异常,执行异常的回调。
这样一看,是不是对 AsyncResult 和 ListenableFutureTask 就有点感觉了。
3.3 具体示例
下面,让我们来写一个异步回调的示例。修改 DemoService 的代码,增加 #execute02()
的异步调用,并返回 ListenableFuture 对象。代码如下:
// DemoService.java@Async
public ListenableFuture<Integer> execute01AsyncWithListenableFuture() {try {return AsyncResult.forValue(this.execute02());} catch (Throwable ex) {return AsyncResult.forExecutionException(ex);}
}
根据执行的结果,包装出成功还是异常的 AsyncResult 对象。
修改 DemoServiceTest 测试类,编写 #task04()
方法,异步调用上述的方法,在塞等待执行完成的同时,添加相应的回调 Callback 方法。代码如下:
// DemoServiceTest.java@Test
public void task04() throws ExecutionException, InterruptedException {long now = System.currentTimeMillis();logger.info("[task04][开始执行]");// <1> 执行任务ListenableFuture<Integer> execute01Result = demoService.execute01AsyncWithListenableFuture();logger.info("[task04][execute01Result 的类型是:({})]",execute01Result.getClass().getSimpleName());execute01Result.addCallback(new SuccessCallback<Integer>() { // <2.1> 增加成功的回调@Overridepublic void onSuccess(Integer result) {logger.info("[onSuccess][result: {}]", result);}}, new FailureCallback() { // <2.1> 增加失败的回调@Overridepublic void onFailure(Throwable ex) {logger.info("[onFailure][发生异常]", ex);}});execute01Result.addCallback(new ListenableFutureCallback<Integer>() { // <2.2> 增加成功和失败的统一回调@Overridepublic void onSuccess(Integer result) {logger.info("[onSuccess][result: {}]", result);}@Overridepublic void onFailure(Throwable ex) {logger.info("[onFailure][发生异常]", ex);}});// <3> 阻塞等待结果execute01Result.get();logger.info("[task04][结束执行,消耗时长 {} 毫秒]", System.currentTimeMillis() - now);
}
<1>
处,调用DemoService#execute01AsyncWithListenableFuture()
方法,异步调用该方法,并返回 ListenableFutureTask 对象。这里,我们看下打印的日志。2019-11-30 19:17:51.320 INFO 77624 --- [ main] c.i.s.l.a.service.DemoServiceTest : [task04][execute01Result 的类型是:(ListenableFutureTask)]
<2.1>
处,增加成功的回调和失败的回调。<2.2>
处,增加成功和失败的统一回调。<3>
处,阻塞等待结果。执行完成后,我们会看到回调被执行,打印日志如下:2019-11-30 19:17:56.330 INFO 77624 --- [ task-1] c.i.s.l.a.service.DemoServiceTest : [onSuccess][result: 2]2019-11-30 19:17:56.331 INFO 77624 --- [ task-1] c.i.s.l.a.service.DemoServiceTest : [onSuccess][result: 2]
4. 异步异常处理器
示例代码对应仓库:lab-29-async-demo 。
在 《芋道 Spring Boot SpringMVC 入门》 的 「5. 全局异常处理」 中,我们实现了对 SpringMVC 请求异常的全局处理。那么,Spring Task 异步调用异常是否有全局处理呢?答案是有,通过实现 AsyncUncaughtExceptionHandler 接口,达到对异步调用的异常的统一处理。
友情提示:该示例,基于 「2. 快速入门」 的 lab-29-async-demo 的基础上,继续改造。
4.1 GlobalAsyncExceptionHandler
在 cn.iocoder.springboot.lab29.asynctask.core.async
包路径,创建 GlobalAsyncExceptionHandler 类,全局统一的异步调用异常的处理器。代码如下:
// GlobalAsyncExceptionHandler.java@Component
public class GlobalAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {private Logger logger = LoggerFactory.getLogger(getClass());@Overridepublic void handleUncaughtException(Throwable ex, Method method, Object... params) {logger.error("[handleUncaughtException][method({}) params({}) 发生异常]",method, params, ex);}}
类上,我们添加了
@Component
注解,考虑到胖友可能会注入一些 Spring Bean 到属性中。实现
#handleUncaughtException(Throwable ex, Method method, Object... params)
方法,打印异常日志。😈 这样,后续如果我们接入 ELK ,就可以基于该异常日志进行告警。
注意,AsyncUncaughtExceptionHandler 只能拦截返回类型非 Future 的异步调用方法。通过看 AsyncExecutionAspectSupport#handleError(Throwable ex, Method method, Object... params)
的源码,可以很容易得到这个结论,代码如下:
// AsyncExecutionAspectSupport.javaprotected void handleError(Throwable ex, Method method, Object... params) throws Exception {// 重点!!!如果返回类型是 Future ,则直接抛出该异常。if (Future.class.isAssignableFrom(method.getReturnType())) {ReflectionUtils.rethrowException(ex);} else {// 否则,交给 AsyncUncaughtExceptionHandler 来处理。// Could not transmit the exception to the caller with default executortry {this.exceptionHandler.obtain().handleUncaughtException(ex, method, params);} catch (Throwable ex2) {logger.warn("Exception handler for async method '" + method.toGenericString() +"' threw unexpected exception itself", ex2);}}
}
对了,AsyncExecutionAspectSupport 是 AsyncExecutionInterceptor 的父类哟。
所以哟,返回类型为 Future 的异步调用方法,需要通过「3. 异步回调」来处理。
4.2 AsyncConfig
在 cn.iocoder.springboot.lab29.asynctask.config
包路径,创建 AsyncConfig 类,配置异常处理器。代码如下:
// AsyncConfig.java@Configuration
@EnableAsync // 开启 @Async 的支持
public class AsyncConfig implements AsyncConfigurer {@Autowiredprivate GlobalAsyncExceptionHandler exceptionHandler;@Overridepublic Executor getAsyncExecutor() {return null;}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return exceptionHandler;}}
在类上添加
@EnableAsync
注解,启用异步功能。这样「2. Application」 的@EnableAsync
注解,也就可以去掉了。实现 AsyncConfigurer 接口,实现异步相关的全局配置。😈 此时此刻,胖友有没想到 SpringMVC 的 WebMvcConfigurer 接口。
实现
#getAsyncUncaughtExceptionHandler()
方法,返回我们定义的 GlobalAsyncExceptionHandler 对象。实现
#getAsyncExecutor()
方法,返回 Spring Task 异步任务的默认执行器。这里,我们返回了null
,并未定义默认执行器。所以最终会使用 TaskExecutionAutoConfiguration 自动化配置类创建出来的 ThreadPoolTaskExecutor 任务执行器,作为默认执行器。
4.3 DemoService
修改 DemoService 的代码,增加 #zhaoDaoNvPengYou(...)
的异步调用。代码如下:
// DemoService.java@Async
public Integer zhaoDaoNvPengYou(Integer a, Integer b) {throw new RuntimeException("程序员不需要女朋友");
}
直接给想要找女朋友的程序员,抛出该异常。
4.4 简单测试
修改 DemoServiceTest 测试类,编写 #testZhaoDaoNvPengYou()
方法,异步调用上述的方法。代码如下:
// DemoServiceTest.java@Test
public void testZhaoDaoNvPengYou() throws InterruptedException {demoService.zhaoDaoNvPengYou(1, 2);// sleep 1 秒,保证异步调用的执行Thread.sleep(1000);
}
运行单元测试,执行日志如下:
2019-11-30 09:22:52.962 ERROR 86590 --- [ task-1] .i.s.l.a.c.a.GlobalAsyncExceptionHandler : [handleUncaughtException][method(public java.lang.Integer cn.iocoder.springboot.lab29.asynctask.service.DemoService.zhaoDaoNvPengYou(java.lang.Integer,java.lang.Integer)) params([1, 2]) 发生异常]java.lang.RuntimeException: 程序员不需要女朋友
😈 异步调用的异常成功被 GlobalAsyncExceptionHandler 拦截。
5. 自定义执行器
示例代码对应仓库:lab-29-async-two 。
在 「2. 快速入门」 中,我们使用 Spring Boot TaskExecutionAutoConfiguration 自动化配置类,实现自动配置 ThreadPoolTaskExecutor 任务执行器。
本小节,我们希望两个自定义 ThreadPoolTaskExecutor 任务执行器,实现不同方法,分别使用这两个 ThreadPoolTaskExecutor 任务执行器。
友情提示:考虑到不破坏上面入门的示例,所以我们新建了 lab-29-async-two 项目。
5.1 引入依赖
在 pom.xml
文件中,引入相关依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-29-async-demo</artifactId><dependencies><!-- 引入 Spring Boot 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- 方便等会写单元测试 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies></project>
和 「2.1 引入依赖」 一致。
5.2 应用配置文件
在 application.yml
中,添加 Spring Task 定时任务的配置,如下:
spring:task:# Spring 执行器配置,对应 TaskExecutionProperties 配置类。对于 Spring 异步任务,会使用该执行器。execution-one:thread-name-prefix: task-one- # 线程池的线程名的前缀。默认为 task- ,建议根据自己应用来设置pool: # 线程池相关core-size: 8 # 核心线程数,线程池创建时候初始化的线程数。默认为 8 。max-size: 20 # 最大线程数,线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUEkeep-alive: 60s # 允许线程的空闲时间,当超过了核心线程之外的线程,在空闲时间到达之后会被销毁。默认为 60 秒queue-capacity: 200 # 缓冲队列大小,用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。allow-core-thread-timeout: true # 是否允许核心线程超时,即开启线程池的动态增长和缩小。默认为 true 。shutdown:await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 trueawait-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置# Spring 执行器配置,对应 TaskExecutionProperties 配置类。对于 Spring 异步任务,会使用该执行器。execution-two:thread-name-prefix: task-two- # 线程池的线程名的前缀。默认为 task- ,建议根据自己应用来设置pool: # 线程池相关core-size: 8 # 核心线程数,线程池创建时候初始化的线程数。默认为 8 。max-size: 20 # 最大线程数,线程池最大的线程数,只有在缓冲队列满了之后,才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUEkeep-alive: 60s # 允许线程的空闲时间,当超过了核心线程之外的线程,在空闲时间到达之后会被销毁。默认为 60 秒queue-capacity: 200 # 缓冲队列大小,用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。allow-core-thread-timeout: true # 是否允许核心线程超时,即开启线程池的动态增长和缩小。默认为 true 。shutdown:await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 trueawait-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置
在
spring.task
配置项下,我们新增了execution-one
和execution-two
两个执行器的配置。在格式上,我们保持和在「2.7 应用配置文件」看到的spring.task.exeuction
一致,方便我们后续复用 TaskExecutionProperties 属性配置类来映射。
5.3 AsyncConfig
在 cn.iocoder.springboot.lab29.asynctask.config
包路径,创建 AsyncConfig 类,配置两个执行器。代码如下:
// AsyncConfig.java@Configuration
@EnableAsync // 开启 @Async 的支持
public class AsyncConfig {public static final String EXECUTOR_ONE_BEAN_NAME = "executor-one";public static final String EXECUTOR_TWO_BEAN_NAME = "executor-two";@Configurationpublic static class ExecutorOneConfiguration {@Bean(name = EXECUTOR_ONE_BEAN_NAME + "-properties")@Primary@ConfigurationProperties(prefix = "spring.task.execution-one") // 读取 spring.task.execution-one 配置到 TaskExecutionProperties 对象public TaskExecutionProperties taskExecutionProperties() {return new TaskExecutionProperties();}@Bean(name = EXECUTOR_ONE_BEAN_NAME)public ThreadPoolTaskExecutor threadPoolTaskExecutor() {// 创建 TaskExecutorBuilder 对象TaskExecutorBuilder builder = createTskExecutorBuilder(this.taskExecutionProperties());// 创建 ThreadPoolTaskExecutor 对象return builder.build();}}@Configurationpublic static class ExecutorTwoConfiguration {@Bean(name = EXECUTOR_TWO_BEAN_NAME + "-properties")@ConfigurationProperties(prefix = "spring.task.execution-two") // 读取 spring.task.execution-two 配置到 TaskExecutionProperties 对象public TaskExecutionProperties taskExecutionProperties() {return new TaskExecutionProperties();}@Bean(name = EXECUTOR_TWO_BEAN_NAME)public ThreadPoolTaskExecutor threadPoolTaskExecutor() {// 创建 TaskExecutorBuilder 对象TaskExecutorBuilder builder = createTskExecutorBuilder(this.taskExecutionProperties());// 创建 ThreadPoolTaskExecutor 对象return builder.build();}}private static TaskExecutorBuilder createTskExecutorBuilder(TaskExecutionProperties properties) {// Pool 属性TaskExecutionProperties.Pool pool = properties.getPool();TaskExecutorBuilder builder = new TaskExecutorBuilder();builder = builder.queueCapacity(pool.getQueueCapacity());builder = builder.corePoolSize(pool.getCoreSize());builder = builder.maxPoolSize(pool.getMaxSize());builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());builder = builder.keepAlive(pool.getKeepAlive());// Shutdown 属性TaskExecutionProperties.Shutdown shutdown = properties.getShutdown();builder = builder.awaitTermination(shutdown.isAwaitTermination());builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());// 其它基本属性builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
// builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
// builder = builder.taskDecorator(taskDecorator.getIfUnique());return builder;}}
参考 Spring Boot TaskExecutionAutoConfiguration 自动化配置类,我们创建了 ExecutorOneConfiguration 和 ExecutorTwoConfiguration 配置类,来分别创建 Bean 名字为
executor-one
和executor-two
两个执行器。
5.4 DemoService
在 cn.iocoder.springboot.lab29.asynctask.service
包路径下,创建 DemoService 类。代码如下:
// DemoService.java@Service
public class DemoService {private Logger logger = LoggerFactory.getLogger(getClass());@Async(AsyncConfig.EXECUTOR_ONE_BEAN_NAME)public Integer execute01() {logger.info("[execute01]");return 1;}@Async(AsyncConfig.EXECUTOR_TWO_BEAN_NAME)public Integer execute02() {logger.info("[execute02]");return 2;}}
在
@Async
注解上,我们设置了其使用的执行器的 Bean 名字。
5.5 简单测试
创建 DemoServiceTest 测试类,编写 #testExecute()
方法,异步调用 DemoService 的上述两个方法。代码如下:
// DemoServiceTest.java@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class DemoServiceTest {@Autowiredprivate DemoService demoService;@Testpublic void testExecute() throws InterruptedException {demoService.execute01();demoService.execute02();// sleep 1 秒,保证异步调用的执行Thread.sleep(1000);}}
运行单元测试,执行日志如下:
2019-11-30 10:25:53.068 INFO 89290 --- [ task-one-1] c.i.s.l.asynctask.service.DemoService : [execute01]
2019-11-30 10:25:53.068 INFO 89290 --- [ task-two-1] c.i.s.l.asynctask.service.DemoService : [execute02]
从日志中,我们可以看到,
#execute01()
方法在executor-one
执行器中执行,而#execute02()
方法在executor-two
执行器中执行。符合预期~
666. 彩蛋
😈 发现自己真是一个啰嗦的老男孩,挺简单一东西,结果又写了老长一篇。不过最后还是要唠叨下,如果胖友使用 Spring Task 的异步任务,一定要注意两个点:
JVM 应用的正常优雅关闭,保证异步任务都被执行完成。
编写异步异常处理器 GlobalAsyncExceptionHandler ,记录异常日志,进行监控告警。
嗯~~~如果觉得不过瘾的胖友,可以再去看看 《Spring Framework Documentation —— Task Execution and Scheduling》 文档。
不过呢,Spring Task 异步任务,在项目中使用的并不多,更多的选择,还是可靠的分布式队列,嘿嘿。当然,艿艿在自己的开源项目 onemall 中,使用 AccessLogInterceptor 拦截器,记录访问日志到数据库。因为访问日志更多是用于监控和排查问题,所以即使有一定的丢失,影响也不大。
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- Python 的非正式介绍3.9版本
Python 的非正式介绍在下面的例子中,通过提示符 (>>> 与 ...) 的出现与否来区分输入和输出:如果你想复现这些例子,当提示符出现后,你必须在提示符后键入例子中的每一个词;不以提示符开头的那些行是解释器的输出。注意例子中某行中出现第二个提示符意味着你必须键…...
2024/4/23 15:29:14 - VMware提示此主机支持Intel VT-x,但Intel VT-x处于禁用状态解决方案
/出现这种情况解决办法就是进BIOS开启Intel Virtualization Technology /重启电脑,进入BIOS修改 <各种电脑进入BIOS按键> /找到 Intel Virtual Techno logy 改为 [Enabled] /按下 F10 进行重启即可...
2024/5/1 23:19:55 - 布尔适定性问题(2-SAT)
...
2024/4/23 15:29:10 - 如何使用kubeadm安装kubernetes(K8S)——3.集群可用性测试
=>返回首页<= 三、集群可用性测试 1. 创建nginx ds# 写入配置 $ cat > nginx-ds.yml <<EOF apiVersion: v1 kind: Service metadata:name: nginx-dslabels:app: nginx-ds spec:type: NodePortselector:app: nginx-dsports:- name: httpport: 80targetPort: 80 -…...
2024/4/23 15:29:16 - 长篇自动驾驶技术综述论文(下)
长篇自动驾驶技术综述论文(下) 三维目标检测 鉴于经济性,可用性和研究的广泛性,几乎所有的算法都使用相机作为主要的感知方式。把相机应用在ADS中,限制条件除了前面讨论到的光照等因素外,还有一个问题就是目标检测是在图像空间的,忽略了场景的尺度信息。而当需要进行避障…...
2024/4/23 15:29:11 - 01_SpringMVC
1. Spring与Web环境集成 1.1 ApplicationContext应用上下文获取方式 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) ,这样的弊端是配置文件加…...
2024/4/23 15:29:07 - Email营销列表的建立方式
EDM营销(Email Direct Marketing)也即:Email营销、电子邮件营销。企业可以通过EDM建立同目标顾客的沟通渠道,向其直接传达相关信息,用来促进销售。EDM有多种用途,可以发送电子广告、产品信息、销售信息、市场调查、市场推广活动信息等。可分为许可Email营销和非许可Email…...
2024/4/23 15:29:06 - Vmware WORKSTATION PRO 15.5安装winodws server 2019教程
Vmware WORKSTATION PRO 15.5安装winodws server 2019教程 步骤1: 下载系统镜像文件:Windows Server 2019 1809版本;步骤2: 下载VMWARE WORKSTATION PRO,显示为最新15.5版本,并永久激活。 激活密钥许可证:VMware Workstation Pro 15 激活许可证 UY758-0RXEQ-M81WP-8ZM7Z…...
2024/4/25 1:50:08 - vue项目打包为桌面应用程序
1、通过Vue-cli创建vue 项目vue create electron-vue-start2、Vue项目下,命令行执行 vue add electron-builder注意:执行过程中可能会出现错误,如果出现错误,他会提示哪条命令没有执行成功,推荐用cnpm(淘宝镜像)执行那条失败的命令 3、执行命令查看是否能运行项目 npm r…...
2024/5/1 21:48:04 - JS基础知识
JS组成核心 ECMAScript 文档对象模型DOM,让JS有能力与网页进行对话 浏览器对象模型BOM,让JS有能力与浏览器进行对话将JS脚本嵌入在HTML页面中执行的步骤将js代码卸载外部脚本文件中 **.js 在页面head中引入js文件 <script src=**.js></script>,引入外部js文件的标签…...
2024/4/23 15:29:10 - BMP图24位转换成1位单色
昨天在工作的时候又遇到了这个问题 所以在此记录一下 这是个纯c语言的 兼容性不错 24位BMP转1位BMP FILE* SetRGBQUAD(FILE *wfile) {int i;RGBQUAD rgbquad[2];for (i = 0; i<2; i++) {rgbquad[i].rgbBlue = i ? 0xFF : 0;rgbquad[i].rgbGreen = i ? 0xFF : 0;rgbquad[i…...
2024/5/1 22:14:05 - node启用服务器遇到events.js:287错误时如何解决
node服务器启用时出现下面错误这主要是因为80端口已经被其他程序所占用 所以下面我们只需要打开命令行工具,找到这个80端口的应用程序,然后结束进程即可(可直接输入命令行taskkill /f /im node.exe(后面加上所占用的应用程序即可)...
2024/4/20 23:58:56 - redis使用场景之set(二)---随机推荐类信息检索
在上一讲我们对set有了一个感性认识,明白了set存储空间,今天我们来讲解一下set类型数据的基本操作,然后再讲一个使用的业务场景从而引出并总结出set的使用场景之一。Set类型数据的基本操作添加数据sadd key member1 [member2]获取全部数据smembers key删除数据srem key memb…...
2024/4/19 15:18:29 - Linux中定时器对sleep的影响
先上结论,测试代码附文末: 现象及结论:如果定时器定时长度小于主函数中sleep的时间,那么sleep的睡眠时间将等于定时器定时时长。如果定时器定时长度大于主函数中sleep的时间,那么sleep的睡眠时间保持不变。原因分析:sleep函数是进程阻塞函数,工作时首先将进程阻塞掉,等…...
2024/5/1 21:33:07 - 恶意代码分析入门
一、恶意代码的定义恶意代码:也称恶意软件,在大多数计算机入侵事件中都扮演了重要角色。任何以某种方式来对用户、计算机或网络造成破坏的软件,都可以被认为是恶意代码,包括计算机病毒、木马、蠕虫、内核套件、勒索软件、间谍软件等。恶意代码分析是一种解剖恶意代码的艺术…...
2024/4/19 1:16:36 - 深入解读HTTP/3的原理及应用
背景 在万维网诞生之时,万维网仅仅是一群交换超文本文件的计算机。在计算机之间交换文件是一个简单的程序,包括请求和响应。在此基础上设计了一个简单的基于文本的协议。HTTP(超文本传输协议)应运而生。后来,它被起草成了一个标准化的IETF协议,定义在RFC 1945中,也被称为H…...
2024/4/15 7:59:20 - springcloud
springcloud springcloud eureka Eureka 介绍 Eureka主要由两个组件组成:Eureka服务器和Eureka客户端。 Eureka服务器用作服务注册服务器。 Eureka客户端是一个java客户端,用来简化与服务器的交互、作为轮询负载均衡器,并提供服务的故障切换支持。 demo pom文件 <depende…...
2024/5/1 23:47:06 - Java从入门到精通- Java VisualVM 插件介绍
官方和第三方插件可以轻松扩展VisualVM功能。使用工具| 插件| 可用插件可从VisualVM插件中心下载插件。IDE集成插件可在此处获得。要在离线环境中扩展VisualVM功能,请在“ 插件中心”页面上获取插件,然后使用“工具” |“工具”。插件| 下载以安装它们。由于VisualVM 2.0中的…...
2024/4/30 0:12:14 - 【编译原理笔记】第四章
第四章 语法分析 语法分析程序的功能和语法分析方法自顶向下语法分析法自底向上算符优先分析法LR分析法 4.1 语法分析程序的功能1. 自上而下的分析法从文法的开始符号出发,根据文法规则正向推导出给定句子的一种方法;或者说,从树根开始,往下构造语法树,直到建立每个叶的分…...
2024/4/23 15:29:09 - “太空发布”后,你知道星环科技推出的“联邦云”是个啥吗?
原文链接:https://www.qbitai.com/2020/05/14537.html充满时尚科技感的太空服神秘高端炫酷的飞船驾驶舱时不时跳出镜头并光亮闪闪的魔力球……处于如此星际迷航般的现场,本次疫情之下的星环科技新品发布着实有一种遨游太空的既视感;当然搭乘飞船发布的“联邦云”新特性也实实…...
2024/5/2 0:57:19
最新文章
- C++入门——基本概念与关键字(上)
兜兜转转终于来到C的学习,C作为一种更高级的语言,是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式,本节笔者旨在带领读者理解C是如何对C语言设计不合理的地方进行优化的&am…...
2024/5/2 0:58:34 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - Kafka入门到实战-第五弹
Kafka入门到实战 Kafka常见操作官网地址Kafka概述Kafka的基础操作更新计划 Kafka常见操作 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://kafka.apache.org/Kafka概述 Apache Kafka 是一个开源的分布式事件流平台&…...
2024/5/1 13:31:04 - 【ARM 嵌入式 C 文件操作系列 20 -- 文件删除函数 remove 详细介绍】
请阅读【嵌入式开发学习必备专栏 】 文章目录 文件删除函数 remove 文件删除函数 remove 在 C 语言中, 可以使用 remove 函数来删除一个文件,但在删除之前 可能想确认该文件是否存在。 可以使用 stat 函数来检查文件是否存在。 以下是如何实现这个功能…...
2024/5/1 18:15:01 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/1 17:30:59 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/4/30 18:14:14 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/29 2:29:43 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/4/30 18:21:48 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/27 17:58:04 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/27 14:22:49 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/28 1:28:33 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/30 9:43:09 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/27 17:59:30 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/4/25 18:39:16 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/28 1:34:08 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/26 19:03:37 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/29 20:46:55 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/30 22:21:04 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/1 4:32:01 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/4/27 23:24:42 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/28 5:48:52 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/30 9:42:22 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/4/30 9:43:22 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/30 9:42:49 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57