通过实例理解 JDK8 的 CompletableFuture

转载自 通过实例理解 JDK8 的 CompletableFuture

 

前言

Java 5 并发库主要关注于异步任务的处理,它采用了这样一种模式,producer 线程创建任务并且利用阻塞队列将其传递给任务的 consumer。这种模型在 Java 7 和 8 中进一步发展,并且开始支持另外一种风格的任务执行,那就是将任务的数据集分解为子集,每个子集都可以由独立且同质的子任务来负责处理。

这种风格的基础库也就是 fork/join 框架,它允许程序员规定数据集该如何进行分割,并且支持将子任务提交到默认的标准线程池中,也就是"通用的"ForkJoinPool。Java 8 中,fork/join 并行功能借助并行流的机制变得更加具有可用性。但是,不是所有的问题都适合这种风格的并行处理:所处理的元素必须是独立的,数据集要足够大,并且在并行加速方面,每个元素的处理成本要足够高,这样才能补偿建立 fork/join 框架所消耗的成本。CompletableFuture 类则是 Java 8 在并行流方面的创新。

准备知识

异步计算

所谓异步调用其实就是实现一个可无需等待被调用函数的返回值而让操作继续运行的方法。在 Java 语言中,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结果。但调用者仍需要取线程的计算结果。

回调函数

回调函数比较通用的解释是,它是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。

回调函数的机制:

(1)定义一个回调函数;

(2)提供函数实现的一方在初始化时候,将回调函数的函数指针注册给调用者;

(3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

回调函数通常与原始调用者处于同一层次,如图 1 所示:

图 1 回调函数示例图

Future 接口介绍

JDK5 新增了 Future 接口,用于描述一个异步计算的结果。虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式呢?即当计算结果完成及时通知监听者。

有一些开源框架实现了我们的设想,例如 Netty 的 ChannelFuture 类扩展了 Future 接口,通过提供 addListener 方法实现支持回调方式的异步编程。Netty 中所有的 I/O 操作都是异步的,这意味着任何的 I/O 调用都将立即返回,而不保证这些被请求的 I/O 操作在调用结束的时候已经完成。取而代之地,你会得到一个返回的 ChannelFuture 实例,这个实例将给你一些关于 I/O 操作结果或者状态的信息。当一个 I/O 操作开始的时候,一个新的 Future 对象就会被创建。在开始的时候,新的 Future 是未完成的状态--它既非成功、失败,也非被取消,因为 I/O 操作还没有结束。如果 I/O 操作以成功、失败或者被取消中的任何一种状态结束了,那么这个 Future 将会被标记为已完成,并包含更多详细的信息(例如:失败的原因)。请注意,即使是失败和被取消的状态,也是属于已完成的状态。阻塞方式的示例代码如清单 1 所示。

清单 1 阻塞方式示例代码

1

2

3

4

5

6

// Start the connection attempt.

ChannelFuture Future = bootstrap.connect(new InetSocketAddress(host, port));

// Wait until the connection is closed or the connection attempt fails.

Future.getChannel().getCloseFuture().awaitUninterruptibly();

// Shut down thread pools to exit.

bootstrap.releaseExternalResources();

上面代码使用的是 awaitUninterruptibly 方法,源代码如清单 2 所示。

清单 2 awaitUninterruptibly 源代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

publicChannelFutureawaitUninterruptibly() {

    boolean interrupted = false;

    synchronized (this) {

        //循环等待到完成

        while (!done) {

            checkDeadLock();

            waiters++;

        try {

            wait();

        } catch (InterruptedException e) {

            //不允许中断

            interrupted = true;

        } finally {

            waiters--;

        }

    }

}

    if (interrupted) {

    Thread.currentThread().interrupt();

}

return this;

}

清单 3 异步非阻塞方式示例代码

1

2

3

4

5

6

7

8

9

10

// Start the connection attempt.

ChannelFuture Future = bootstrap.connect(new InetSocketAddress(host, port));

Future.addListener(new ChannelFutureListener(){

    public void operationComplete(final ChannelFuture Future)

        throws Exception

        {          

    }

});

// Shut down thread pools to exit.

bootstrap.releaseExternalResources();

可以明显的看出,在异步模式下,上面这段代码没有阻塞,在执行 connect 操作后直接执行到 printTime("异步时间: "),随后 connect 完成,Future 的监听函数输出 connect 操作完成。

非阻塞则是添加监听类 ChannelFutureListener,通过覆盖 ChannelFutureListener 的 operationComplete 执行业务逻辑。

清单 4 异步非阻塞方式示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

public void addListener(final ChannelFutureListener listener) {

    if (listener == null) {

    throw new NullPointerException("listener");

}

    booleannotifyNow = false;

    synchronized (this) {

        if (done) {

        notifyNow = true;

    } else {

        if (firstListener == null) {

        //listener 链表头

        firstListener = listener;

    } else {

        if (otherListeners == null) {

        otherListeners = new ArrayList<ChannelFutureListener>(1);

        }

        //添加到 listener 链表中,以便操作完成后遍历操作

        otherListeners.add(listener);

    }

    ......

    if (notifyNow) {

        //通知 listener 进行处理

        notifyListener(listener);

        }

}

这部分代码的逻辑很简单,就是注册回调函数,当操作完成后自动调用回调函数,就达到了异步的效果。

CompletableFuture 类介绍 

Java 8 中, 新增加了一个包含 50 个方法左右的类--CompletableFuture,它提供了非常强大的 Future 的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。

对于阻塞或者轮询方式,依然可以通过 CompletableFuture 类的 CompletionStage 和 Future 接口方式支持。

CompletableFuture 类声明了 CompletionStage 接口,CompletionStage 接口实际上提供了同步或异步运行计算的舞台,所以我们可以通过实现多个 CompletionStage 命令,并且将这些命令串联在一起的方式实现多个命令之间的触发。

我们可以通过 CompletableFuture.supplyAsync(this::sendMsg); 这么一行代码创建一个简单的异步计算。在这行代码中,supplyAsync 支持异步地执行我们指定的方法,这个例子中的异步执行方法是 sendMsg。当然,我们也可以使用 Executor 执行异步程序,默认是 ForkJoinPool.commonPool()。

我们也可以在异步计算结束之后指定回调函数,例如 CompletableFuture.supplyAsync(this::sendMsg) .thenAccept(this::notify);这行代码中的 thenAccept 被用于增加回调函数,在我们的示例中 notify 就成了异步计算的消费者,它会处理计算结果。

CompletableFuture 类使用示例

接下来我们通过 20 个示例看看 CompletableFuture 类具体怎么用。

创建完整的 CompletableFuture

清单 5 示例代码

1

2

3

4

5

static void completedFutureExample() {

    CompletableFuture<String>cf = CompletableFuture.completedFuture("message");

    assertTrue(cf.isDone());

    assertEquals("message", cf.getNow(null));

}

以上代码一般来说被用于启动异步计算,getNow(null)返回计算结果或者 null。

运行简单的异步场景

清单 6 示例代码

1

2

3

4

5

6

7

8

9

static void runAsyncExample() {

    CompletableFuture<Void>cf = CompletableFuture.runAsync(() -> {

    assertTrue(Thread.currentThread().isDaemon());

    randomSleep();

});

    assertFalse(cf.isDone());

    sleepEnough();

    assertTrue(cf.isDone());

}

以上代码的关键点有两点:

  1. CompletableFuture 是异步执行方式;
  2. 使用 ForkJoinPool 实现异步执行,这种方式使用了 daemon 线程执行 Runnable 任务。

同步执行动作示例

清单 7 示例代码

1

2

3

4

5

6

7

static void thenApplyExample() {

    CompletableFuture<String>cf = CompletableFuture.completedFuture("message").thenApply(s -> {

    assertFalse(Thread.currentThread().isDaemon());

    returns.toUpperCase();

    });

    assertEquals("MESSAGE", cf.getNow(null));

}

以上代码在异步计算正常完成的前提下将执行动作(此处为转换成大写字母)。

异步执行动作示例 

相较前一个示例的同步方式,以下代码实现了异步方式,仅仅是在上面的代码里的多个方法增加"Async"这样的关键字。

清单 8 示例代码

1

2

3

4

5

6

7

8

9

static void thenApplyAsyncExample() {

    CompletableFuture<String>cf = CompletableFuture.completedFuture("message").thenApplyAsync(s -> {

    assertTrue(Thread.currentThread().isDaemon());

    randomSleep();

    returns.toUpperCase();

    });

    assertNull(cf.getNow(null));

    assertEquals("MESSAGE", cf.join());

}

使用固定的线程池完成异步执行动作示例 

我们可以通过使用线程池方式来管理异步动作申请,以下代码基于固定的线程池,也是做一个大写字母转换动作,代码如清单 9 所示。

清单 9 示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

staticExecutorService executor = Executors.newFixedThreadPool(3, new ThreadFactory() {

    int count = 1;

    @Override

    public Thread newThread(Runnable runnable) {

        return new Thread(runnable, "custom-executor-" + count++);

    }

    });

        static void thenApplyAsyncWithExecutorExample() {

            CompletableFuture<String>cf = CompletableFuture.completedFuture("message").thenApplyAsync(s -> {

            assertTrue(Thread.currentThread().getName().startsWith("custom-executor-"));

            assertFalse(Thread.currentThread().isDaemon());

            randomSleep();

            returns.toUpperCase();

        }, executor);

        assertNull(cf.getNow(null));

        assertEquals("MESSAGE", cf.join());

}

作为消费者消费计算结果示例 

假设我们本次计算只需要前一次的计算结果,而不需要返回本次计算结果,那就有点类似于生产者(前一次计算)-消费者(本次计算)模式了,示例代码如清单 10 所示。

清单 10 示例代码

1

2

3

4

5

6

static void thenAcceptExample() {

    StringBuilder result = new StringBuilder();

    CompletableFuture.completedFuture("thenAccept message")

    .thenAccept(s ->result.append(s));

    assertTrue("Result was empty", result.length() > 0);

}

消费者是同步执行的,所以不需要在 CompletableFuture 里对结果进行合并。

异步消费示例 

相较于前一个示例的同步方式,我们也对应有异步方式,代码如清单 11 所示。

清单 11 示例代码

1

2

3

4

5

6

7

static void thenAcceptAsyncExample() {

    StringBuilder result = new StringBuilder();

    CompletableFuture<Void>cf = CompletableFuture.completedFuture("thenAcceptAsync message")

    .thenAcceptAsync(s ->result.append(s));

    cf.join();

    assertTrue("Result was empty", result.length() > 0);

}

计算过程中的异常示例 

接下来介绍异步操作过程中的异常情况处理。下面这个示例中我们会在字符转换异步请求中刻意延迟 1 秒钟,然后才会提交到 ForkJoinPool 里面去执行。

清单 12 示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

static void completeExceptionallyExample() {

        CompletableFuture<String>cf = CompletableFuture.completedFuture("message").thenApplyAsync(String::toUpperCase,

        CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS));

        CompletableFuture<String>exceptionHandler = cf.handle((s, th) -> { return (th != null) ? "message upon cancel" : ""; });

        cf.completeExceptionally(new RuntimeException("completed exceptionally"));

        assertTrue("Was not completed exceptionally", cf.isCompletedExceptionally());

    try {

        cf.join();

        fail("Should have thrown an exception");

        } catch(CompletionException ex) { // just for testing

            assertEquals("completed exceptionally", ex.getCause().getMessage());

    }

     assertEquals("message upon cancel", exceptionHandler.join());

}

示例代码中,首先我们创建一个 CompletableFuture(计算完毕),然后调用 thenApplyAsync 返回一个新的 CompletableFuture,接着通过使用 delayedExecutor(timeout, timeUnit)方法延迟 1 秒钟执行。然后我们创建一个 handler(exceptionHandler),它会处理异常,返回另一个字符串"message upon cancel"。接下来进入 join()方法,执行大写转换操作,并且抛出 CompletionException 异常。

取消计算任务

与前面一个异常处理的示例类似,我们可以通过调用 cancel(boolean mayInterruptIfRunning)方法取消计算任务。此外,cancel()方法与 completeExceptionally(new CancellationException())等价。

清单 13 示例代码

1

2

3

4

5

6

7

8

static void cancelExample() {

    CompletableFuture cf = CompletableFuture.completedFuture("message").thenApplyAsync(String::toUpperCase,

    CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS));

    CompletableFuture cf2 = cf.exceptionally(throwable -> "canceled message");

    assertTrue("Was not canceled", cf.cancel(true));

    assertTrue("Was not completed exceptionally", cf.isCompletedExceptionally());

    assertEquals("canceled message", cf2.join());

}

一个 CompletableFuture VS 两个异步计算

我们可以创建一个 CompletableFuture 接收两个异步计算的结果,下面代码首先创建了一个 String 对象,接下来分别创建了两个 CompletableFuture 对象 cf1 和 cf2,cf2 通过调用 applyToEither 方法实现我们的需求。

清单 14 示例代码

1

2

3

4

5

6

7

8

9

static void applyToEitherExample() {

    String original = "Message";

    CompletableFuture cf1 = CompletableFuture.completedFuture(original)

    .thenApplyAsync(s -> delayedUpperCase(s));

    CompletableFuture cf2 = cf1.applyToEither(

    CompletableFuture.completedFuture(original).thenApplyAsync(s -> delayedLowerCase(s)),

    s -> s + " from applyToEither");

    assertTrue(cf2.join().endsWith(" from applyToEither"));

}

如果我们想要使用消费者替换清单 14 的方法方式用于处理异步计算结果,代码如清单 15 所示。

清单 15 示例代码

1

2

3

4

5

6

7

8

9

10

static void acceptEitherExample() {

    String original = "Message";

    StringBuilder result = new StringBuilder();

    CompletableFuture cf = CompletableFuture.completedFuture(original)

    .thenApplyAsync(s -> delayedUpperCase(s))

    .acceptEither(CompletableFuture.completedFuture(original).thenApplyAsync(s -> delayedLowerCase(s)),

    s -> result.append(s).append("acceptEither"));

    cf.join();

    assertTrue("Result was empty", result.toString().endsWith("acceptEither"));

}

运行两个阶段后执行

下面这个示例程序两个阶段执行完毕后返回结果,首先将字符转为大写,然后将字符转为小写,在两个计算阶段都结束之后触发 CompletableFuture。

清单 16 示例代码

1

2

3

4

5

6

7

8

static void runAfterBothExample() {

    String original = "Message";

    StringBuilder result = new StringBuilder();

    CompletableFuture.completedFuture(original).thenApply(String::toUpperCase).runAfterBoth(

    CompletableFuture.completedFuture(original).thenApply(String::toLowerCase),

    () -> result.append("done"));

    assertTrue("Result was empty", result.length() > 0);

}

也可以通过以下方式处理异步计算结果,

清单 17 示例代码

1

2

3

4

5

6

7

8

static void thenAcceptBothExample() {

    String original = "Message";

    StringBuilder result = new StringBuilder();

    CompletableFuture.completedFuture(original).thenApply(String::toUpperCase).thenAcceptBoth(

    CompletableFuture.completedFuture(original).thenApply(String::toLowerCase),

    (s1, s2) -> result.append(s1 + s2));

    assertEquals("MESSAGEmessage", result.toString());

}

整合两个计算结果

我们可以通过 thenCombine()方法整合两个异步计算的结果,注意,以下代码的整个程序过程是同步的,getNow()方法最终会输出整合后的结果,也就是说大写字符和小写字符的串联值。

清单 18 示例代码

1

2

3

4

5

6

7

static void thenCombineExample() {

    String original = "Message";

    CompletableFuture cf = CompletableFuture.completedFuture(original).thenApply(s -> delayedUpperCase(s))

    .thenCombine(CompletableFuture.completedFuture(original).thenApply(s -> delayedLowerCase(s)),

    (s1, s2) -> s1 + s2);

    assertEquals("MESSAGEmessage", cf.getNow(null));

}

上面这个示例是按照同步方式执行两个方法后再合成字符串,以下代码采用异步方式同步执行两个方法,由于异步方式情况下不能够确定哪一个方法最终执行完毕,所以我们需要调用 join()方法等待后一个方法结束后再合成字符串,这一点和线程的 join()方法是一致的,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到 join()方法了,即 join()的作用是:"等待该线程终止"。

清单 19 示例代码

1

2

3

4

5

6

7

8

static void thenCombineAsyncExample() {

    String original = "Message";

    CompletableFuture cf = CompletableFuture.completedFuture(original)

    .thenApplyAsync(s -> delayedUpperCase(s))

    .thenCombine(CompletableFuture.completedFuture(original).thenApplyAsync(s -> delayedLowerCase(s)),

    assertEquals("MESSAGEmessage", cf.join());

    (s1, s2) -> s1 + s2);

}

除了 thenCombine()方法以外,还有另外一种方法-thenCompose(),这个方法也会实现两个方法执行后的返回结果的连接。

清单 20 示例代码

1

2

3

4

5

6

7

static void thenComposeExample() {

    String original = "Message";

    CompletableFuture cf = CompletableFuture.completedFuture(original).thenApply(s -> delayedUpperCase(s))

    .thenCompose(upper -> CompletableFuture.completedFuture(original).thenApply(s -> delayedLowerCase(s))

    .thenApply(s -> upper + s));

    assertEquals("MESSAGEmessage", cf.join());

}

anyOf()方法

以下代码模拟了如何在几个计算过程中任意一个完成后创建 CompletableFuture,在这个例子中,我们创建了几个计算过程,然后转换字符串到大写字符。由于这些 CompletableFuture 是同步执行的(下面这个例子使用的是 thenApply()方法,而不是 thenApplyAsync()方法),使用 anyOf()方法后返回的任何一个值都会立即触发 CompletableFuture。然后我们使用 whenComplete(BiConsumer<? super Object, ? super Throwable> action)方法处理结果。

清单 21 示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

static void anyOfExample() {

    StringBuilder result = new StringBuilder();

    List messages = Arrays.asList("a", "b", "c");

    List<CompletableFuture> futures = messages.stream()

    .map(msg -> CompletableFuture.completedFuture(msg).thenApply(s -> delayedUpperCase(s)))

    .collect(Collectors.toList());

    CompletableFuture.anyOf(futures.toArray(new CompletableFuture[futures.size()])).whenComplete((res, th) -> {

        if(th == null) {

        assertTrue(isUpperCase((String) res));

        result.append(res);

    }

});

    assertTrue("Result was empty", result.length() > 0);

}

当所有的 CompletableFuture 完成后创建 CompletableFuture

清单 22 所示我们会以同步方式执行多个异步计算过程,在所有计算过程都完成后,创建一个 CompletableFuture。

清单 22 示例代码

1

2

3

4

5

6

7

8

9

10

11

12

static void allOfExample() {

    StringBuilder result = new StringBuilder();

    List messages = Arrays.asList("a", "b", "c");

    List<CompletableFuture> futures = messages.stream()

    .map(msg -> CompletableFuture.completedFuture(msg).thenApply(s -> delayedUpperCase(s)))

    .collect(Collectors.toList());

    CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).whenComplete((v, th) -> {

        futures.forEach(cf -> assertTrue(isUpperCase(cf.getNow(null))));

        result.append("done");

});

    assertTrue("Result was empty", result.length() > 0);

}

相较于前一个同步示例,我们也可以异步执行,如清单 23 所示。

清单 23 示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

static void allOfAsyncExample() {

    StringBuilder result = new StringBuilder();

    List messages = Arrays.asList("a", "b", "c");

    List<CompletableFuture> futures = messages.stream()

    .map(msg -> CompletableFuture.completedFuture(msg).thenApplyAsync(s -> delayedUpperCase(s)))

    .collect(Collectors.toList());

    CompletableFuture allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))

.whenComplete((v, th) -> {

    futures.forEach(cf -> assertTrue(isUpperCase(cf.getNow(null))));

    result.append("done");

});

    allOf.join();

    assertTrue("Result was empty", result.length() > 0);

}

实际案例

以下代码完成的操作包括:

  1. 首先异步地通过调用 cars()方法获取 Car 对象,返回一个 CompletionStage<List>实例。Cars()方法可以在内部使用调用远端服务器上的 REST 服务等类似场景。
  2. 然后和其他的 CompletionStage<List>组合,通过调用 rating(manufacturerId)方法异步地返回 CompletionStage 实例。
  3. 当所有的 Car 对象都被填充了 rating 后,调用 allOf()方法获取最终值。
  4. 调用 whenComplete()方法打印最终的评分(rating)。

清单 24 示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

cars().thenCompose(cars -> {

    List<CompletionStage> updatedCars = cars.stream()

    .map(car -> rating(car.manufacturerId).thenApply(r -> {

    car.setRating(r);

    return car;

     })).collect(Collectors.toList());

    CompletableFuture done = CompletableFuture

    .allOf(updatedCars.toArray(new CompletableFuture[updatedCars.size()]));

    return done.thenApply(v -> updatedCars.stream().map(CompletionStage::toCompletableFuture)

    .map(CompletableFuture::join).collect(Collectors.toList()));

    }).whenComplete((cars, th) -> {

    if (th == null) {

    cars.forEach(System.out::println);

    } else {

    throw new RuntimeException(th);

    }

}).toCompletableFuture().join();

结束语

Completable 类为我们提供了丰富的异步计算调用方式,我们可以通过上述基本操作描述及 20 个示例程序进一步了解如果使用 CompletableFuture 类实现我们的需求,期待 JDK10 会有持续更新。

参考资源

参考 developerWorks 上的 Java 8 文章,了解更多 Java 8 知识。

参考书籍 Java 8 in Action Raoul-Gabriel Urma

参考书籍 Mastering Lambdas: Java Programming in a Multicore World Maurice Naftalin

参考文章 Java 8 CompletableFutures,这篇文章从基础介绍了 CompletableFuture 类的使用方式。

 

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

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

相关文章

2021-10-24

今天考试结束&#xff0c;顺便来白漂个徽章&#xff0c;嘿嘿

ES6箭头函数总结

转自&#xff1a; https://www.cnblogs.com/mengff/p/9656486.html 1. 箭头函数基本形式 let func &#xff08;num) > num; let func () > num; let sum (num1,num2) > num1 num2; [1,2,3].map(x > x * x); 2. 箭头函数基本特点 (1). 箭头函数this为父作…

Spring bean - scope详解

转载自 Spring bean - scope详解 Scope是定义Spring如何创建bean的实例的。 在创建bean的时候可以带上scope属性&#xff0c;scope有下面几种类型。 Singleton 这也是Spring默认的scope&#xff0c;表示Spring容器只创建一个bean的实例&#xff0c;Spring在创建第一次后会缓存起…

Invalid bound statement (not found):出现的原因和解决方法

解决错误的步骤出现了什么错误可能导致的原因解决办法出现了什么错误 错误截图&#xff1a; //BindingException 数据绑定异常 not found 找不到 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.kuan.edu.mapper.CourseMapper.getP…

es6箭头函数(墙裂推荐)

转自&#xff1a; https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions 箭头函数表达式的语法比函数表达式更简洁&#xff0c;并且没有自己的this&#xff0c;arguments&#xff0c;super或 new.target。这些函数表达式更适用于那…

使用腾讯云发送短信API接口实现(完整流程)

步骤一、开通腾讯云短信服务二、申请签名前的准备&#xff08;申请公众号&#xff09;三、创建签名和模板四、腾讯云API实现发送短信五、使用springboot进行调用六、可能会报的错误一、开通腾讯云短信服务 在腾讯云官网找到短信服务&#xff0c;开通 二、申请签名前的准备&a…

获取Spring的ApplicationContext的几种方式

转载自 获取Spring的ApplicationContext的几种方式 Application Context定义 简单来说就是Spring中的高级容器&#xff0c;可以获取容器中的各种bean组件&#xff0c;注册监听事件&#xff0c;加载资源文件等功能。 具体定义可以参考官网&#xff1a;https://spring.io/underst…

'webpack-dev-server' 不是内部或外部命令,也不是可运行的程序 或批处理文件的解决方法(webpack热加载)

【README】webpack-dev-server 是一个支持热编译的服务器&#xff08;动态编译&#xff09; 【1】问题 webpack-dev-server 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件的解决方法 【2】安装 webpack 服务器&#xff0c; 如下&#xff1a; 参考指南&…

Spring开启方法异步执行

转载自 Spring开启方法异步执行 EnableAsync Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AsyncConfigurationSelector.class) public interface EnableAsync {Class<? extends Annotation> annotation() default Annotation.clas…

idea关闭页面显示的浏览器图标

每当我们打开HTML或者XML的时候老是弹出来&#xff0c;看着就烦&#xff01; 关闭方法&#xff1a; &#xff08;1&#xff09;点击文件 》设置 &#xff08;2&#xff09;工具 》web浏览器 这样就完成了&#xff0c;再也没有烦人的小图标了。

webpack打包器小结(1)

【1】intro to webpack 官网&#xff1a; webpack官网: http://webpack.github.io/ webpack3文档(英文): https://webpack.js.org/ webpack3文档(中文): https://doc.webpack-china.org/ 【2】 自动化构建工具webpack_打包js,json文件 step1、创建文件夹 mywebpack ; st…

Spring快速开启计划任务

转载自 Spring快速开启计划任务 Spring3.1开始让计划任务变得非常简单&#xff0c;只需要几个注解就能快速开启计划任务的支持。 EnableScheduling Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Import(SchedulingConfiguration.class) Documented public i…

ACM训练4

AB Problem INPUT Each line will contain two integers A and B. Process to end of file. OUTPUT For each case, output A B in one line. 问题连接&#xff1a;https://vjudge.net/problem/hdu-1000?tdsourcetags_pctim_aiomsg AC代码如下&#xff1a; #include &…

Spring Aware容器感知技术

转载自 Spring Aware容器感知技术 Spring Aware是什么 Spring提供Aware接口能让Bean感知Spring容器的存在&#xff0c;即让Bean可以使用Spring容器所提供的资源。 Spring Aware的分类 几种常用的Aware接口如下。 Aware接口说明ApplicationContextAware能获取Application Co…

json详解

JSON的全称是”JavaScript Object Notation”&#xff0c;意思是JavaScript对象表示法&#xff0c;它是一种基于文本&#xff0c;独立于语言的轻量级数据交换格式。XML也是一种数据交换格式&#xff0c;为什么没有选择XML呢&#xff1f;因为XML虽然可以作为跨平台的数据交换格式…

Error in nextTick: “TypeError: Cannot read properties of undefined (reading ‘clearValidate‘)“

我在打开对话框清除表单校验的时候报了这个错 我的解决办法是加个if判断有没有 这是原来的 加个if

ACM训练题6

ASCII码排序 Problem Description INPUT 输入三个字符后&#xff0c;按各字符的ASCII码从小到大的顺序输出这三个字符。 OUTPUT 对于每组输入数据&#xff0c;输出一行&#xff0c;字符中间用一个空格分开. 问题连接&#xff1a;https://vjudge.net/problem/hdu-2000 AC…

Spring Enable*高级应用及原理

转载自 Spring Enable*高级应用及原理 Enable* 之前的文章用到了一些Enable*开头的注解&#xff0c;比如EnableAsync、EnableScheduling、EnableAspectJAutoProxy、EnableCaching等&#xff0c;Enable表示开启/允许一项功能。 Enable*工作原理 我们只需要几个很简单的注解就…

ACM训练题7

计算球体积 Problem Description 根据输入半径输出球的体积 INPUT 输入数据有多组&#xff0c;每组占一行&#xff0c;每行包括一个实数&#xff0c;表示球的半径。 OUTPUT 输出对应的球的体积&#xff0c;对于每组输入数据&#xff0c;输出一行&#xff0c;计算结果保留…

JSON与JS对象的区别

和一些同学一样&#xff1a;总是感觉json对象(其实json不是对象)和js对象的字面量表示法相同&#xff0c;最近学习json&#xff0c;真心搞不懂&#xff0c;js对象和json有什么区别&#xff1f;就是感觉json的key要用” “括起来&#xff0c;但是js对象的字面量表示法不需要。  …