All my hard work is here, this way to talk about @Async principle, you don't understand Spring anymore

1 Introduction

I think you have used the @Async annotation for task asynchronous processing experience before reading this article. In the project development process, for non-main process, non-real-time, and time-consuming tasks, asynchronous processing is often performed. Affect the main process, but also improve the response time of the main process.

In the process of using @Async annotations for asynchronous processing, I believe you have also stepped on a lot of pits, such as: tasks are not executed asynchronously, due to the shared thread pool, the mutual influence between tasks, asynchronous tasks have exceptions and don’t know how to deal with it, etc. Wait. Today I will take you to understand its true colors, so that when you encounter problems next time, you can do your job well, and will not panic and be unable to start.

2. Quest

2.1 Implementation principle

2.1.1 Looking for asynchronous annotation post processor

You should know that in order to use the @Async annotation in the project to perform asynchronous tasks, we need to manually enable the asynchronous function. The way to enable it is to add @EnableAsync

@SpringBootApplication
@EnableAsync
public class SpringBootAsyncApplication {

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

Since the asynchronous function can be turned on through the @EnableAsync annotation, this annotation is the entrance to our exploration

Enter the @EnableAsync annotation, you will see another familiar annotation @Import, the function of this annotation is to introduce the configuration class corresponding to the relevant function in the program

@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {}

Click on AsyncConfigurationSelector, you can see that the ProxyAsyncConfiguration configuration class is introduced this time

public 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;
    }
}

Enter the ProxyAsyncConfiguration configuration class

@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("proxyTargetClass"));
    bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
    return bpp;
}

You can see that a Bean such as AsyncAnnotationBeanPostProcessor is declared in the ProxyAsyncConfiguration configuration class. From the literal meaning, you can also guess that the Bean should be the protagonist of asynchronous processing. Next, let’s take a look at what the protagonist does.

Enter AsyncAnnotationBeanPostProcessor, you can see that this class implements BeanFactoryAware, BeanPostProcessor, two interfaces that are closely related to the Bean life cycle. From the life cycle characteristics of the Bean, you can know that the implementation method of the BeanFactoryAware interface is executed before the implementation method of the BeanPostProcessor interface.

2.1.2 BeanFactoryAware implementation

2.1.2.1 Define the aspect

@Override
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;
}

The aspect object is defined in the setBeanFactory() implementation method. Seeing the two words aspect, I believe that two concepts related to it will immediately emerge in your mind: point of contact and notification

  • Cut point: used to declare the goal of cut
  • Notification: corresponding processing for cut-in targets

2.1.3 Define cut point

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) {
    // If EJB 3.1 API not present, simply ignore.
}
protected Pointcut buildPointcut(Set<Class<? extends Annotation>> asyncAnnotationTypes) {
    ComposablePointcut result = null;
    for (Class<? extends Annotation> asyncAnnotationType : asyncAnnotationTypes) {
        // 定义在类上标注@Async、@Asynchronous注解的切点
        Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);
        // 定义在方法上标注@Async、@Asynchronous注解的切点
        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);
}

2.1.4 Define notification

protected Advice buildAdvice(
   @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
 // 定义通知
    AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
    interceptor.configure(executor, exceptionHandler);
    return interceptor;
}

Notification is the final implementation, and it is also a very important part. Since it is very important, we need to look at the specific implementation.

public Object invoke(final MethodInvocation invocation) throws Throwable {
  Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
    Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
    final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

    // 获取异步任务线程池
    AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
    if (executor == null) {
        throw new IllegalStateException(
            "No executor specified and no default executor set on AsyncExecutionInterceptor either");
    }

    // 定义Callable对象
    Callable<Object> task = () -> {
        try {
            Object result = invocation.proceed();
            if (result instanceof Future) {
                return ((Future<?>) result).get();
            }
        }
  ...
        return null;
    };

    return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
    // 异步任务的返回值类型是CompletableFuture
    if (CompletableFuture.class.isAssignableFrom(returnType)) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return task.call();
            }
            catch (Throwable ex) {
                throw new CompletionException(ex);
            }
        }, executor);
    }
 // 异步任务的返回值类型是ListenableFuture
    else if (ListenableFuture.class.isAssignableFrom(returnType)) {
        return ((AsyncListenableTaskExecutor) executor).submitListenable(task);
    }
    // 异步任务的返回值类型是Future
    else if (Future.class.isAssignableFrom(returnType)) {
        return executor.submit(task);
    }
    // 否则交由线程池来处理,没有返回值
    else {
        executor.submit(task);
        return null;
    }
}

The specific implementation of the notification is as follows:

  • The first step is to obtain an asynchronous task thread pool to perform asynchronous tasks
  • Use Callable to wrap the target method
  • Perform asynchronous asynchronous tasks, and do corresponding processing according to different return value types

Through notifications, you can understand the principle of the final realization of asynchronous tasks. You may still have questions, that is, how to notify notifications to perform asynchronous tasks?

I don’t know, do you remember the BeanPostProcessor interface mentioned above, let’s take a look at its specific implementation

2.1.3 BeanPostProcessor implementation

Mention the BeanPostProcessor interface, you should immediately realize that its processing method must do some processing on the Bean, such as generating a proxy

After you have a basic awareness, let's take a look at the corresponding post-processing implementation here

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    // 判断当前Bean是否满足之前定义的切点,如果满足则生成代理对象
    if (isEligible(bean, beanName)) {
        ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
        if (!proxyFactory.isProxyTargetClass()) {
            evaluateProxyInterfaces(bean.getClass(), proxyFactory);
        }
        proxyFactory.addAdvisor(this.advisor);
        customizeProxyFactory(proxyFactory);
        return proxyFactory.getProxy(getProxyClassLoader());
    }

    // No proxy needed.
    return bean;
}

The post-processing of the BeanPostProcessor generates a proxy for the Bean that meets the cut point. When the target method is called, the notify() method will be executed

At this point, the principle of asynchronous implementation is over. In fact, the principle is very simple. What we need to do is to define pointcuts and notifications; if we want to achieve enhancements to the target method, we naturally think of reverse proxy; finally, how to change the original Bean? At this moment, you need to contact the BeanPostProcessor interface related to the Bean life cycle

2.2 Thread pool usage

The thread pool is still very important. Improper use may lead to unexpected problems, such as memory overflow, unlimited thread creation, mutual influence between businesses, etc.

* <p>By default, Spring will be searching for an associated thread pool definition: * either a unique {@link org.springframework.core.task.TaskExecutor} bean in the context, * or an {@link java.util.concurrent.Executor} bean named "taskExecutor" otherwise. 复制代码

According to the official documentation, it can be known that Spring will obtain the unique TaskExecutor or the bean named "taskExecutor" from the context as the thread pool. The default thread pool is defined in the TaskExecutionAutoConfiguration auto configuration class. The default thread pool related configuration is as follows

All my hard work is here, this way to talk about @Async principle, you don't understand Spring anymore

 

It can be seen that the queue size and the maximum number of threads of the default thread pool are both the maximum value of Integer, which will obviously leave certain risks to the system. Therefore, we need to customize the thread pool for each asynchronous task, and then use @Async() The Bean name of the corresponding thread pool is indicated on the annotation

2.3 Exception handling

The exception handling of asynchronous tasks will only print log information by default, and will not do any additional processing. The official documents also have relevant instructions.

Besides, annotated methods having a * {@code void} return type cannot transmit any exception back to the caller. By default, * such uncaught exceptions are only logged. 复制代码

SimpleAsyncUncaughtExceptionHandler is the default implementation of asynchronous task exception handling. If you want to customize exception handling, you only need the AsyncConfigurer interface.

2.4 Return value type

Regarding the return value type, first look at the official instructions

* <p>In terms of target method signatures, any parameter types are supported. * However, the return type is constrained to either {@code void} or * {@link java.util.concurrent.Future}. In the latter case, you may declare the * more specific {@link org.springframework.util.concurrent.ListenableFuture} or * {@link java.util.concurrent.CompletableFuture} types which allow for richer * interaction with the asynchronous task and for immediate composition with * further processing steps. 复制代码

From the official description, it can be seen that the return value type only supports 4 types:

  • void
  • Future
  • ListenableFuture
  • CompletableFuture

Let's take a look at the specific source code

All my hard work is here, this way to talk about @Async principle, you don't understand Spring anymore

 

Either from the official description or source code analysis, it can be concluded that asynchronous tasks only support 4 return types. In the future, there is no need to ask others why the String return type returns null.

Author: Black and white workers moving bricks

Link: https://juejin.im/post/6858854987280809997

Source: Nuggets

Guess you like

Origin blog.csdn.net/weixin_48612224/article/details/109139369