Spring @Async 註解的使用以及原理(二)

       在上一篇中《Spring @Async 註解的使用以及原理(一)》簡單介紹了@Async的使用,本篇簡單分析一下原理,源碼版本:spring-context-5.0.5.RELEASE.

@EnableAsync註解:

/**
 * Enables Spring's asynchronous method execution capability, similar to functionality
 * found in Spring's {@code <task:*>} XML namespace.
 *
 * <p>To be used together with @{@link Configuration Configuration} classes as follows,
 * enabling annotation-driven async processing for an entire Spring application context:
 *
 * <pre class="code">
 * &#064;Configuration
 * &#064;EnableAsync
 * public class AppConfig {
 *
 * }</pre>
 *
 * {@code MyAsyncBean} is a user-defined type with one or more methods annotated with
 * either Spring's {@code @Async} annotation, the EJB 3.1 {@code @javax.ejb.Asynchronous}
 * annotation, or any custom annotation specified via the {@link #annotation} attribute.
 * The aspect is added transparently for any registered bean, for instance via this
 * configuration:
 *
 * <pre class="code">
 * &#064;Configuration
 * public class AnotherAppConfig {
 *
 *     &#064;Bean
 *     public MyAsyncBean asyncBean() {
 *         return new MyAsyncBean();
 *     }
 * }</pre>
 * 
 * (以上部分展示了與 @Configuration 註解搭配使用的場景)
 *
 * <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. If
 * neither of the two is resolvable, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}
 * will be used to process async method invocations. 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.
 *
 * (默認情況下,Spring尋找一個唯一的TaskExecutor類型的bean 或者 bean名稱是“taskExecutor”的Executor類型的bean。
 * 如果二者都不存在,則使用SimpleAsyncTaskExecutor進行異步方法的執行.) 
 * 返回類型爲void 無法將任何異常傳送回調用方。 默認情況下,僅記錄此類未捕獲的異常。
 *
 * <p>To customize all this, implement {@link AsyncConfigurer} and provide:
 * <ul>
 * <li>your own {@link java.util.concurrent.Executor Executor} through the
 * {@link AsyncConfigurer#getAsyncExecutor getAsyncExecutor()} method, and</li>
 * <li>your own {@link org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler
 * AsyncUncaughtExceptionHandler} through the {@link AsyncConfigurer#getAsyncUncaughtExceptionHandler
 * getAsyncUncaughtExceptionHandler()}
 * method.</li>
 * </ul>
 *
 * <pre class="code">
 * &#064;Configuration
 * &#064;EnableAsync
 * public class AppConfig implements AsyncConfigurer {
 *
 *     &#064;Override
 *     public Executor getAsyncExecutor() {
 *         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 *         executor.setCorePoolSize(7);
 *         executor.setMaxPoolSize(42);
 *         executor.setQueueCapacity(11);
 *         executor.setThreadNamePrefix("MyExecutor-");
 *         executor.initialize();
 *         return executor;
 *     }
 *
 *     &#064;Override
 *     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
 *         return MyAsyncUncaughtExceptionHandler();
 *     }
 * }</pre>
 *
 * <p>If only one item needs to be customized, {@code null} can be returned to
 * keep the default settings. Consider also extending from {@link AsyncConfigurerSupport}
 * when possible.
 *
 * <p>Note: In the above example the {@code ThreadPoolTaskExecutor} is not a fully managed
 * Spring bean. Add the {@code @Bean} annotation to the {@code getAsyncExecutor()} method
 * if you want a fully managed bean. In such circumstances it is no longer necessary to
 * manually call the {@code executor.initialize()} method as this will be invoked
 * automatically when the bean is initialized.
 *
 * <p>For reference, the example above can be compared to the following Spring XML
 * configuration:
 *
 * <pre class="code">
 * {@code
 * <beans>
 *
 *     <task:annotation-driven executor="myExecutor" exception-handler="exceptionHandler"/>
 *
 *     <task:executor id="myExecutor" pool-size="7-42" queue-capacity="11"/>
 *
 *     <bean id="asyncBean" class="com.foo.MyAsyncBean"/>
 *
 *     <bean id="exceptionHandler" class="com.foo.MyAsyncUncaughtExceptionHandler"/>
 *
 * </beans>
 * }</pre>
 *
 * The above XML-based and JavaConfig-based examples are equivalent except for the
 * setting of the <em>thread name prefix</em> of the {@code Executor}; this is because
 * the {@code <task:executor>} element does not expose such an attribute. This
 * demonstrates how the JavaConfig-based approach allows for maximum configurability
 * through direct access to actual componentry.
 *
 * <p>The {@link #mode} attribute controls how advice is applied: If the mode is
 * {@link AdviceMode#PROXY} (the default), then the other attributes control the behavior
 * of the proxying. Please note that proxy mode allows for interception of calls through
 * the proxy only; local calls within the same class cannot get intercepted that way.
 *
 *({@link#mode}屬性控制通知的應用方式:如果模式是{@link AdviceMode#PROXY}(默認值),則其他 
 * 屬性控制代理的行爲。請注意,代理模式只允許通過代理攔截調用;同一類中的本身(自)調用不能這樣被攔
 * 截。)
 * <p>Note that if the {@linkplain #mode} is set to {@link AdviceMode#ASPECTJ}, then the
 * value of the {@link #proxyTargetClass} attribute will be ignored. Note also that in
 * this case the {@code spring-aspects} module JAR must be present on the classpath, with
 * compile-time weaving or load-time weaving applying the aspect to the affected classes.
 * There is no proxy involved in such a scenario; local calls will be intercepted as well.
 *(注意,如果{@linkplain#mode}設置爲{@link AdviceMode#ASPECTJ},則
 * {@link#proxyTargetClass}屬性的值將被忽略。還要注意,在這種情況下{@code spring aspects} 
 * 模塊JAR必須出現在類路徑上,編譯時編織或加載時編織將方面應用於受影響的類。在這種情況下不涉及代 
 * 理;本地調用也將被攔截。)
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @author Sam Brannen
 * @since 3.1
 * @see Async
 * @see AsyncConfigurer
 * @see AsyncConfigurationSelector
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)//注意這個,重點
public @interface EnableAsync {

	/**
	 * Indicate the 'async' annotation type to be detected at either class
	 * or method level.
	 * <p>By default, both Spring's @{@link Async} annotation and the EJB 3.1
	 * {@code @javax.ejb.Asynchronous} annotation will be detected.
	 * <p>This attribute exists so that developers can provide their own
	 * custom annotation type to indicate that a method (or all methods of
	 * a given class) should be invoked asynchronously.
	 */
    /**
     * 指示要在類或方法級別檢測到的“異步”註解類型。
     * 默認情況下,Spring的@ {@Async}註解和EJB 3.1{@code @javax.ejb.Asynchronous} 
       註解將被檢測到。
     * 此屬性存在,以便開發人員可以提供自己的自定義註解類型,以指示一個方法(或的所有方法給定的類) 
     * 應該異步調用。
     */
	Class<? extends Annotation> annotation() default Annotation.class;

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies.
	 * <p><strong>Applicable only if the {@link #mode} is set to {@link AdviceMode#PROXY}</strong>.
	 * <p>The default is {@code false}.
	 * <p>Note that setting this attribute to {@code true} will affect <em>all</em>
	 * Spring-managed beans requiring proxying, not just those marked with {@code @Async}.
	 * For example, other beans marked with Spring's {@code @Transactional} annotation
	 * will be upgraded to subclass proxying at the same time. This approach has no
	 * negative impact in practice unless one is explicitly expecting one type of proxy
	 * vs. another &mdash; for example, in tests.
	 */
    /**
     * 指示與基於標準Java接口的代理相反,是否要創建基於子類(CGLIB)的代理。
     * 僅在{@link #mode}設置爲{@link AdviceMode#PROXY}時適用,默認false
     * 請注意,將此屬性設置爲{@code true}將影響所有需要代理的<em> all </ em> Spring管理的 
     * bean,而不僅僅是標記爲{@code @Async}的bean。 例如,其他標有Spring的{@code 
     * @Transactional}批註的bean將同時升級爲子類代理。 這種方法在實踐中不會產生負面影響,除非在 
     * 測試中明確期望一種代理相對於另一種代理。
     */
	boolean proxyTargetClass() default false;

	/**
	 * Indicate how async advice should be applied.
	 * <p><b>The default is {@link AdviceMode#PROXY}.</b>
	 * Please note that proxy mode allows for interception of calls through the proxy
	 * only. Local calls within the same class cannot get intercepted that way; an
	 * {@link Async} annotation on such a method within a local call will be ignored
	 * since Spring's interceptor does not even kick in for such a runtime scenario.
	 * For a more advanced mode of interception, consider switching this to
	 * {@link AdviceMode#ASPECTJ}.
	 */
    /** 指示應如何應用異步通知。
     * <p><b>默認值是{@link AdviceMode#PROXY}</b>
     * 請注意,代理模式只允許通過代理攔截調用。同一個類中的方法自調用不能以這種方式被攔截;本地自調用中 
     * 此類方法上的{@link Async}註解將被忽略,因爲Spring的攔截器甚至不啓動此類運行時場景。對於 
     * 更高級的攔截模式,請考慮將其切換到{@link AdviceMode#ASPECTJ}。
     */
	AdviceMode mode() default AdviceMode.PROXY;

	/**
	 * Indicate the order in which the {@link AsyncAnnotationBeanPostProcessor}
	 * should be applied.
	 * <p>The default is {@link Ordered#LOWEST_PRECEDENCE} in order to run
	 * after all other post-processors, so that it can add an advisor to
	 * existing proxies rather than double-proxy.
	 */
    /**
     * 指示應用{@link AsyncAnnotationBeanPostProcessor}的順序。
     * 默認值是{@link Ordered#LOWEST_PRECEDENCE},以便在所有其他後處理器之後運行,這樣它就可 
     * 以向現有代理添加一個advisor,而不是雙重代理。
     */
	int order() default Ordered.LOWEST_PRECEDENCE;

}

    @EnableAsync 註解中有一行重要的代碼:@Import(AsyncConfigurationSelector.class),引入了相關的配置類:

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

	private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";

	/**
	 * {@inheritDoc}
	 * @return {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration} for
	 * {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()}, respectively
	 */
	@Override
	@Nullable
	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;
		}
	}

}

ProxyAsyncConfiguration類:

     以AdviceMode.PROXY爲例,進入到ProxyAsyncConfiguration類中:

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類型的bean
		AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
		Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
        //設置自定義的異步註解類型
		if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
			bpp.setAsyncAnnotationType(customAsyncAnnotation);
		}
        //設置執行器
		if (this.executor != null) {
			bpp.setExecutor(this.executor);
		}
        //設置異常處理器
		if (this.exceptionHandler != null) {
			bpp.setExceptionHandler(this.exceptionHandler);
		}
        //讀取@EnableAsync註解的屬性值,見父類的setImportMetadata方法
		bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
		bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
		return bpp;
	}

}

AsyncAnnotationBeanPostProcessor類:

    看名稱有點像bean後置處理器,繼承實現接口比較複雜,可以用IDEA生成一下類圖:

     AsyncAnnotationBeanPostProcessor類的代碼比較多,需要清楚Spring bean的生命週期初始化過程,比如BeanFactoryAware、BeanPostProcessor等的執行順序,另外還要對Spring AOP APIs(編程式創建 @AspectJ 代理)有所瞭解。

     在AbstractAdvisingBeanPostProcessor類中的postProcessAfterInitialization方法會會生成目標類的代理類

public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (this.advisor == null || bean instanceof AopInfrastructureBean) {
			// Ignore AOP infrastructure such as scoped proxies.
			return bean;
		}
        // 添加advisor
		if (bean instanceof Advised) {
			Advised advised = (Advised) bean;
			if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
				// Add our local Advisor to the existing proxy's Advisor chain...
				if (this.beforeExistingAdvisors) {
					advised.addAdvisor(0, this.advisor);
				}
				else {
					advised.addAdvisor(this.advisor);
				}
				return bean;
			}
		}
        //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;
	}

 

     這裏展示一下實例化的AsyncAnnotationBeanPostProcessor類對應的bean的結構幫助理解:

     可以看到這個 bean中持有一個AsyncAnnotationAdvisor類的對象advisor:buildAdvice()方法生成通知,buildPointcut生成切點。

    protected Advice buildAdvice(@Nullable Executor executor, 
     AsyncUncaughtExceptionHandler exceptionHandler) {
		return new AnnotationAsyncExecutionInterceptor(executor, exceptionHandler);
	}

	/**
	 * Calculate a pointcut for the given async annotation types, if any.
	 * @param asyncAnnotationTypes the async annotation types to introspect
	 * @return the applicable Pointcut object, or {@code null} if none
	 */
	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);
	}

AsyncAnnotationAdvisor中的buildAdvice()方法,生成了AnnotationAsyncExecutionInterceptor對象,它的父類AsyncExecutionInterceptor重寫了AsyncExecutionInterceptor接口的invoke方法,通過委託實現@Async異步方法的調用。在invoke()方法中獲取執行器executor,創建Callable異步線程任務,提交到執行器executor(對應的線程池)中執行。

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

		return doSubmit(task, executor, invocation.getMethod().getReturnType());
	}

總結:

     @Async和@EnableAsync註解實現方法異步調用底層是通過AOP線程池實現的。

備註:AsyncAnnotationBeanPostProcessor類的實例化過程涉及到低級的AOP APIs有些複雜,需要對Spring bean生命週期解以及Spring AOP APIs的使用有所掌握。最好打斷點,調試,感到混亂時,可以先debugger出代碼塊(比如方法)的結果,再進到代碼中分析。

Spring bean生命週期大致流程:https://blog.csdn.net/qq_22076345/article/details/105580031

Spring AOP APIs官方文檔:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-api

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章