-
Scenario reduction
When I was looking at git submission records, I saw a piece of code submitted by my colleague. The simplified code is as follows:
@Configuration
public class BaseConfig implements AsyncConfigurer {
@Autowired
private AsyncService asyncService;
@Override
public Executor getAsyncExecutor(a) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("defaultExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(a) {
return ((Throwable var1, Method var2, Object... var3)->{
this.asyncService.Print();
log.info("You again!); }); }}Copy the code
@Slf4j
@Component
public class AsyncServiceImpl implements AsyncService {
@Async
@Override
public void asyncPrint(a) {
Thread thread = Thread.currentThread();
log.info("current thread id:[{}], name:[{}]", thread.getId(), thread.getName());
}
@Override
public void Print(a) {
log.info("Print..."); }}Copy the code
Code is implemented AsyncConfigurer this interface, and then rewrite the getAsyncExecutor, getAsyncUncaughtExceptionHandler these two methods, an asynchronous task are the custom before the thread pool, The latter can be caught when an exception occurs in an asynchronous task for special processing. The AsyncService code is very simple. It prints the identity of the current thread. Then I try to write a test class to test whether the handler method can catch the exception after the asynchronous asyncPrint execution. And then int I = 1/0 in the asynchronous method; Let the program throw an error:
@SpringBootTest(classes = EvtwboApplication.class)
@RunWith(SpringRunner.class)
public class SpringDemoTest {
@Autowired
private AsyncService asyncService;
@Test
public void testAsync(a){
this.asyncService.asyncPrint(); }}Copy the code
The following log is generated after the test method is executed:
2021-01-30 21:06:05.452 INFO 8320 --- [ main] com.demo.service.impl.AsyncServiceImpl : current thread id:[1], name:[main]
java.lang.ArithmeticException: / by zero
at com.demo.service.impl.AsyncServiceImpl.asyncPrint(AsyncServiceImpl.java:17)
at com.demo.config.SpringDemoTest.testAsync(SpringDemoTest.java:20)
Copy the code
It turns out that the exception is not caught by the exception handler we wrote earlier, and you’ll notice that the log printed by the asynchronous service prints the main thread, indicating that the main thread executed the code, and the @async annotation is not in effect.
-
Problem analysis
To understand why the @async annotation doesn’t work, we need to know how it works with our method. Before using the @async annotation, we add an @enableAsync annotation to a configuration class, something like this:
@EnableAsync
@SpringBootApplication
public class EvtwboApplication {
public static void main(String[] args) {
newSpringApplication(EvtwboApplication.class).run(args); }}Copy the code
Many frameworks now use such annotations, which look like a switch, and in fact are, to import beans to enable custom annotations like @async. Let’s click on that
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
AdviceMode mode(a) default AdviceMode.PROXY;
}
Copy the code
There are a couple of properties in this annotation that we don’t need right now, so if we don’t look at this, we can see that AdviceMode is advicemode.proxy by default, The yuan notes above @ Import (AsyncConfigurationSelector. Class) is it imported classes, we point into the class to look at
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
@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; }}}Copy the code
Here, because we didn’t assign to AdviceMode, we’re going to go to the upper branch and return a ProxyAsyncConfiguration, which is the class that it actually imports. Click on this class and see
@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(a) {
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"));
returnbpp; }}Copy the code
Can see that it is a configuration class, import the AsyncAnnotationBeanPostProcessor, and is beanname internalAsyncAnnotationProcessor, see the name this is clearly a BeanPostProcessor, Take a look at this interface
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
returnbean; }}Copy the code
Only two default method, the method name or well know postProcessBeforeInitialization this method really bean initialization before the call, can do some processing to create out of bean, And postProcessAfterInitialization nature is called after the bean is initialized, these two methods are object instances created initializeBean call initial method before and after the burial site, because the knowledge belongs to the refresh process of spring, Here, not a little mention the knowledge used in the following, the first is AbstractApplicationContext refresh method
@Override
public void refresh(a) throws BeansException, IllegalStateException {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
Copy the code
Some exception handling and extraneous comments have been removed from the code. You can read them directly in the source code. This refresh is the core startup code of our ApplicationContext container. Now let’s look at the two methods that leave comments. First is registerBeanPostProcessors, see comments can know this is in the process of registration BeanPostProcessors used in bean instantiation intercept buried some of the points, we can go in to look at, Is ultimately in PostProcessorRegistrationDelegate registerBeanPostProcessors method
public static void registerBeanPostProcessors( ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
// First, register the BeanPostProcessors that implement PriorityOrdered.
// Next, register the BeanPostProcessors that implement Ordered.
List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
for (String ppName : orderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, orderedPostProcessors);
// Now, register all regular BeanPostProcessors.
// Finally, re-register all internal BeanPostProcessors.
// Re-register post-processor for detecting inner beans as ApplicationListeners,
// moving it to the end of the processor chain (for picking up proxies etc).
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}
Copy the code
Can see he is the first registered realized PriorityOrdered interface classes, and then implements the Ordered interface, then the other, we can see AsyncAnnotationBeanPostProcessor is realized Ordered interface, So it’s registered in the second branch
Beanfactory.getbean (ppName, beanPostProcessor.class); beanFactory.getBean(ppName, beanPostProcessor.class); To create the first instance, and then registerBeanPostProcessors (the beanFactory, orderedPostProcessors); , it actually do is the beanFactory addBeanPostProcessor (postProcessor); I’m just going to add this instance to the container. We now finished the BeanPostProcessor has been registered, in have a look at where it is work, source code is in the inside of the refresh finishBeanFactoryInitialization method, see annotation can know here is actually initialize a lazy loading singleton, To follow, is ultimately to AbstractAutowireCapableBeanFactory doCreateBean method, actually before AsyncAnnotationBeanPostProcessor is created by this method
We can see this in this methodexposedObject = initializeBean(beanName, exposedObject, mbd);
This is where the bean is initialized. Click in
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
Object wrappedBean = bean;
if (mbd == null| |! mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } invokeInitMethods(beanName, wrappedBean, mbd);if (mbd == null| |! mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); }return wrappedBean;
}
Copy the code
Obviously applyBeanPostProcessorsBeforeInitialization, ApplyBeanPostProcessorsAfterInitialization these two methods is to call our AsyncAnnotationBeanPostProcessor method, in fact we post processor is made agent after initialization, We can follow you inside
You can see this sideAsyncAnnotationBeanPostProcessor
You can generate an object to replace the original object, which is called a proxy, and keep looking inside
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisor ! =null && !(bean instanceof AopInfrastructureBean)) {
// Add this notification to the notification chain for the already propped object
if (bean instanceof Advised) {
Advised advised = (Advised)bean;
if(! advised.isFrozen() &&this.isEligible(AopUtils.getTargetClass(bean))) {
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0.this.advisor);
} else {
advised.addAdvisor(this.advisor);
}
returnbean; }}if (this.isEligible(bean, beanName)) {
// a proxy object is generated here
ProxyFactory proxyFactory = this.prepareProxyFactory(bean, beanName);
if(! proxyFactory.isProxyTargetClass()) {this.evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
this.customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(this.getProxyClassLoader());
} else {
returnbean; }}else {
returnbean; }}Copy the code
Here you can see that a proxy object is generated with a notification, which is actually AsyncAnnotationAdvisor, so let’s look at the buildAdvice method of this class
protected Advice buildAdvice(
@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
// The two arguments here are clearly defined by the two asyncconfig methods
interceptor.configure(executor, exceptionHandler);
return interceptor;
}
Copy the code
This notice is essentially a blocker, we take a look at this interceptor AnnotationAsyncExecutionInterceptor invoke method, practical in class AsyncExecutionInterceptor his father
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable { Class<? > targetClass = invocation.getThis() ! =null ? AopUtils.getTargetClass(invocation.getThis()) : null;
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
AsyncTaskExecutor executor = this.determineAsyncExecutor(userDeclaredMethod);
if (executor == null) {
throw new IllegalStateException("No executor specified and no default executor set on AsyncExecutionInterceptor either");
} else {
Callable<Object> task = () -> {
try {
Object result = invocation.proceed();
if (result instanceof Future) {
return((Future)result).get(); }}catch (ExecutionException var4) {
this.handleError(var4.getCause(), userDeclaredMethod, invocation.getArguments());
} catch (Throwable var5) {
this.handleError(var5, userDeclaredMethod, invocation.getArguments());
}
return null;
};
return this.doSubmit(task, executor, invocation.getMethod().getReturnType()); }}Copy the code
The source code is well understood to create an asynchronous task to execute the specified method, and then hand it off to the thread pool to execute, which is what the @async annotation actually does.
See here, you may be in doubt, it looks like and the above problems has little to do ah, don’t worry, we’ll look back to the place where AsyncAnnotationBeanPostProcessor registration, Source address PostProcessorRegistrationDelegate# registerBeanPostProcessors, BeanPostProcessor pp = beanfactory.getBean (ppName, beanPostProcessor.class); Side, that is, to create AsyncAnnotationBeanPostProcessor the central object and register it to the ioc container, and then later when initializing AsyncServiceImpl has created instance can be replaced with a notification proxy objects, Then we debug step down again, something strange happened, source jumped to us before the debug method of AbstractAutowireCapableBeanFactory doCreateBean this place, And the object created is asyncServiceImpl
Let’s think about it, this time also in creating AsyncAnnotationBeanPostProcessor obviously, why the way to create asyncServiceImpl, and if this time is to create the entity, when it is instantiated, The backend processor is not registered with the beanFactory, so it does not carry the Async advisor in the proxy object, and the @async annotation does not work. The baseConfig object is created before asyncServiceImpl is created. This makes sense because baseConfig injected asyncServiceImpl. So when baseConfig instantiation will instantiate asyncServiceImpl first, the source code in AbstractAutowireCapableBeanFactory populateBean method, is the place where the dependency injection, before the bean initialization, Interested can look at, found in look forward and create the org. Springframework. Scheduling. The annotation. ProxyAsyncConfiguration this object, the object appears to be a little impression, It is import AsyncAnnotationBeanPostProcessor class, in fact, in the spring of the singleton initialization process will indeed get factory bean to through reflection is available through @ inject beans beans, Detailed code can see ConstructorResolver instantiateUsingFactoryMethod method, so now the situation is clear, When creating AsyncAnnotationBeanPostProcessor instantiate a ProxyAsyncConfiguration factory class, don’t know what’s going on then instantiate the baseConfig, because injection again, So asyncServiceImpl is created, now the question is why is the baseConfig object created when ProxyAsyncConfiguration is created? Let’s move on to the call stack, as shown below
If you look closely, you can see that baseConfig comes in through a method called setConfigurersautowire. Let’s open up the structure diagram of ProxyAsyncConfiguration and look at it
Can clearly see the setConfigurers this methods from its superclass AbstractAsyncConfiguration, we order in to have a look
Originally here setter injection baseConfig, to now the problem the reason is very clear, because of the continuous injection cause AsyncAnnotationBeanPostProcessor created created ProxyAsyncConfiguration, BaseConfig was created, asyncServiceImpl was created, Then in asyncServiceImpl after creating the instance initialization application BeanPostProcessor less time off also AsyncAnnotationBeanPostProcessor hasn’t been registered into the container, so make @ Async annotations failed.
-
Problem solving
Knowing the cause of the problem, the solution is obvious and we simply need to change the injected asyncServiceImpl to fetch it from the container inside the method
@Slf4j
@Configuration
public class BaseConfig implements AsyncConfigurer.ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public Executor getAsyncExecutor(a) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("defaultExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(a) {
return ((Throwable var1, Method var2, Object... var3) -> {
AsyncService bean = applicationContext.getBean(AsyncService.class);
bean.Print();
log.info("You again!);
});
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { BaseConfig.applicationContext = applicationContext; }}Copy the code
perform
2021-01-30 23:56:34.407 INFO 9304 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService
2021-01-30 23:56:34.420 INFO 9304 --- [faultExecutor-1] com.demo.service.impl.AsyncServiceImpl : current thread id:[17], name:[defaultExecutor-1]
2021-01-30 23:56:34.424 INFO 9304 --- [faultExecutor-1] com. Demo. Service. Impl. AsyncServiceImpl: print...2021-01-30 23:56:34.424 INFO 9304 --- [faultExecutor-1] com. Demo. Config. BaseConfig: how is you!Copy the code
Perfect!